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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is nsTransitionManager.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2009
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : /* Code to start and animate CSS transitions. */
40 :
41 : #include "nsTransitionManager.h"
42 : #include "nsIContent.h"
43 : #include "nsStyleContext.h"
44 : #include "nsCSSProps.h"
45 : #include "mozilla/TimeStamp.h"
46 : #include "nsRefreshDriver.h"
47 : #include "nsRuleProcessorData.h"
48 : #include "nsIStyleRule.h"
49 : #include "nsRuleWalker.h"
50 : #include "nsRuleData.h"
51 : #include "gfxColor.h"
52 : #include "nsCSSPropertySet.h"
53 : #include "nsStyleAnimation.h"
54 : #include "nsEventDispatcher.h"
55 : #include "nsGUIEvent.h"
56 : #include "mozilla/dom/Element.h"
57 :
58 : using mozilla::TimeStamp;
59 : using mozilla::TimeDuration;
60 :
61 : namespace dom = mozilla::dom;
62 : namespace css = mozilla::css;
63 :
64 : /*****************************************************************************
65 : * Per-Element data *
66 : *****************************************************************************/
67 :
68 : struct ElementPropertyTransition
69 0 : {
70 : nsCSSProperty mProperty;
71 : nsStyleAnimation::Value mStartValue, mEndValue;
72 : TimeStamp mStartTime; // actual start plus transition delay
73 :
74 : // data from the relevant nsTransition
75 : TimeDuration mDuration;
76 : css::ComputedTimingFunction mTimingFunction;
77 :
78 : // This is the start value to be used for a check for whether a
79 : // transition is being reversed. Normally the same as mStartValue,
80 : // except when this transition started as the reversal of another
81 : // in-progress transition. Needed so we can handle two reverses in a
82 : // row.
83 : nsStyleAnimation::Value mStartForReversingTest;
84 : // Likewise, the portion (in value space) of the "full" reversed
85 : // transition that we're actually covering. For example, if a :hover
86 : // effect has a transition that moves the element 10px to the right
87 : // (by changing 'left' from 0px to 10px), and the mouse moves in to
88 : // the element (starting the transition) but then moves out after the
89 : // transition has advanced 4px, the second transition (from 10px/4px
90 : // to 0px) will have mReversePortion of 0.4. (If the mouse then moves
91 : // in again when the transition is back to 2px, the mReversePortion
92 : // for the third transition (from 0px/2px to 10px) will be 0.8.
93 : double mReversePortion;
94 :
95 : // Compute the portion of the *value* space that we should be through
96 : // at the given time. (The input to the transition timing function
97 : // has time units, the output has value units.)
98 : double ValuePortionFor(TimeStamp aRefreshTime) const;
99 :
100 0 : bool IsRemovedSentinel() const
101 : {
102 0 : return mStartTime.IsNull();
103 : }
104 :
105 0 : void SetRemovedSentinel()
106 : {
107 : // assign the null time stamp
108 0 : mStartTime = TimeStamp();
109 0 : }
110 : };
111 :
112 : double
113 0 : ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const
114 : {
115 : // Set |timePortion| to the portion of the way we are through the time
116 : // input to the transition's timing function (always within the range
117 : // 0-1).
118 0 : double duration = mDuration.ToSeconds();
119 0 : NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden");
120 : double timePortion;
121 0 : if (duration == 0.0) {
122 : // When duration is zero, we can still have a transition when delay
123 : // is nonzero. mStartTime already incorporates delay.
124 0 : if (aRefreshTime >= mStartTime) {
125 0 : timePortion = 1.0;
126 : } else {
127 0 : timePortion = 0.0;
128 : }
129 : } else {
130 0 : timePortion = (aRefreshTime - mStartTime).ToSeconds() / duration;
131 0 : if (timePortion < 0.0)
132 0 : timePortion = 0.0; // use start value during transition-delay
133 0 : if (timePortion > 1.0)
134 0 : timePortion = 1.0; // we might be behind on flushing
135 : }
136 :
137 0 : return mTimingFunction.GetValue(timePortion);
138 : }
139 :
140 : struct ElementTransitions : public mozilla::css::CommonElementAnimationData
141 0 : {
142 0 : ElementTransitions(dom::Element *aElement, nsIAtom *aElementProperty,
143 : nsTransitionManager *aTransitionManager)
144 : : CommonElementAnimationData(aElement, aElementProperty,
145 0 : aTransitionManager)
146 : {
147 0 : }
148 :
149 : void EnsureStyleRuleFor(TimeStamp aRefreshTime);
150 :
151 :
152 : // Either zero or one for each CSS property:
153 : nsTArray<ElementPropertyTransition> mPropertyTransitions;
154 :
155 : // This style rule overrides style data with the currently
156 : // transitioning value for an element that is executing a transition.
157 : // It only matches when styling with animation. When we style without
158 : // animation, we need to not use it so that we can detect any new
159 : // changes; if necessary we restyle immediately afterwards with
160 : // animation.
161 : nsRefPtr<css::AnimValuesStyleRule> mStyleRule;
162 : // The refresh time associated with mStyleRule.
163 : TimeStamp mStyleRuleRefreshTime;
164 : };
165 :
166 : static void
167 0 : ElementTransitionsPropertyDtor(void *aObject,
168 : nsIAtom *aPropertyName,
169 : void *aPropertyValue,
170 : void *aData)
171 : {
172 0 : ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue);
173 : #ifdef DEBUG
174 0 : NS_ABORT_IF_FALSE(!et->mCalledPropertyDtor, "can't call dtor twice");
175 0 : et->mCalledPropertyDtor = true;
176 : #endif
177 0 : delete et;
178 0 : }
179 :
180 : void
181 0 : ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
182 : {
183 0 : if (!mStyleRule || mStyleRuleRefreshTime != aRefreshTime) {
184 0 : mStyleRule = new css::AnimValuesStyleRule();
185 0 : mStyleRuleRefreshTime = aRefreshTime;
186 :
187 0 : for (PRUint32 i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i)
188 : {
189 0 : ElementPropertyTransition &pt = mPropertyTransitions[i];
190 0 : if (pt.IsRemovedSentinel()) {
191 0 : continue;
192 : }
193 :
194 0 : nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(pt.mProperty);
195 :
196 0 : double valuePortion = pt.ValuePortionFor(aRefreshTime);
197 : #ifdef DEBUG
198 : bool ok =
199 : #endif
200 : nsStyleAnimation::Interpolate(pt.mProperty,
201 : pt.mStartValue, pt.mEndValue,
202 0 : valuePortion, *val);
203 0 : NS_ABORT_IF_FALSE(ok, "could not interpolate values");
204 : }
205 : }
206 0 : }
207 :
208 : /*****************************************************************************
209 : * nsTransitionManager *
210 : *****************************************************************************/
211 :
212 : already_AddRefed<nsIStyleRule>
213 0 : nsTransitionManager::StyleContextChanged(dom::Element *aElement,
214 : nsStyleContext *aOldStyleContext,
215 : nsStyleContext *aNewStyleContext)
216 : {
217 0 : NS_PRECONDITION(aOldStyleContext->GetPseudo() ==
218 : aNewStyleContext->GetPseudo(),
219 : "pseudo type mismatch");
220 : // If we were called from ReparentStyleContext, this assertion would
221 : // actually fire. If we need to be called from there, we can probably
222 : // just remove it; the condition probably isn't critical, although
223 : // it's worth thinking about some more.
224 0 : NS_PRECONDITION(aOldStyleContext->HasPseudoElementData() ==
225 : aNewStyleContext->HasPseudoElementData(),
226 : "pseudo type mismatch");
227 :
228 : // NOTE: Things in this function (and ConsiderStartingTransition)
229 : // should never call PeekStyleData because we don't preserve gotten
230 : // structs across reframes.
231 :
232 : // Return sooner (before the startedAny check below) for the most
233 : // common case: no transitions specified or running.
234 0 : const nsStyleDisplay *disp = aNewStyleContext->GetStyleDisplay();
235 0 : nsCSSPseudoElements::Type pseudoType = aNewStyleContext->GetPseudoType();
236 0 : if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) {
237 0 : if (pseudoType != nsCSSPseudoElements::ePseudo_before &&
238 : pseudoType != nsCSSPseudoElements::ePseudo_after) {
239 0 : return nsnull;
240 : }
241 :
242 0 : NS_ASSERTION((pseudoType == nsCSSPseudoElements::ePseudo_before &&
243 : aElement->Tag() == nsGkAtoms::mozgeneratedcontentbefore) ||
244 : (pseudoType == nsCSSPseudoElements::ePseudo_after &&
245 : aElement->Tag() == nsGkAtoms::mozgeneratedcontentafter),
246 : "Unexpected aElement coming through");
247 :
248 : // Else the element we want to use from now on is the element the
249 : // :before or :after is attached to.
250 0 : aElement = aElement->GetParent()->AsElement();
251 : }
252 :
253 : ElementTransitions *et =
254 0 : GetElementTransitions(aElement, pseudoType, false);
255 0 : if (!et &&
256 : disp->mTransitionPropertyCount == 1 &&
257 0 : disp->mTransitions[0].GetDelay() == 0.0f &&
258 0 : disp->mTransitions[0].GetDuration() == 0.0f) {
259 0 : return nsnull;
260 : }
261 :
262 :
263 0 : if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) {
264 0 : return nsnull;
265 : }
266 :
267 0 : if (aNewStyleContext->GetParent() &&
268 0 : aNewStyleContext->GetParent()->HasPseudoElementData()) {
269 : // Ignore transitions on things that inherit properties from
270 : // pseudo-elements.
271 : // FIXME (Bug 522599): Add tests for this.
272 0 : return nsnull;
273 : }
274 :
275 : // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
276 : // I'll consider only the transitions from the number of items in
277 : // 'transition-property' on down, and later ones will override earlier
278 : // ones (tracked using |whichStarted|).
279 0 : bool startedAny = false;
280 0 : nsCSSPropertySet whichStarted;
281 0 : for (PRUint32 i = disp->mTransitionPropertyCount; i-- != 0; ) {
282 0 : const nsTransition& t = disp->mTransitions[i];
283 : // Check delay and duration first, since they default to zero, and
284 : // when they're both zero, we can ignore the transition.
285 0 : if (t.GetDelay() != 0.0f || t.GetDuration() != 0.0f) {
286 : // We might have something to transition. See if any of the
287 : // properties in question changed and are animatable.
288 : // FIXME: Would be good to find a way to share code between this
289 : // interpretation of transition-property and the one below.
290 0 : nsCSSProperty property = t.GetProperty();
291 0 : if (property == eCSSPropertyExtra_no_properties ||
292 : property == eCSSProperty_UNKNOWN) {
293 : // Nothing to do, but need to exclude this from cases below.
294 0 : } else if (property == eCSSPropertyExtra_all_properties) {
295 0 : for (nsCSSProperty p = nsCSSProperty(0);
296 : p < eCSSProperty_COUNT_no_shorthands;
297 : p = nsCSSProperty(p + 1)) {
298 : ConsiderStartingTransition(p, t, aElement, et,
299 : aOldStyleContext, aNewStyleContext,
300 0 : &startedAny, &whichStarted);
301 : }
302 0 : } else if (nsCSSProps::IsShorthand(property)) {
303 0 : CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
304 : ConsiderStartingTransition(*subprop, t, aElement, et,
305 : aOldStyleContext, aNewStyleContext,
306 0 : &startedAny, &whichStarted);
307 : }
308 : } else {
309 : ConsiderStartingTransition(property, t, aElement, et,
310 : aOldStyleContext, aNewStyleContext,
311 0 : &startedAny, &whichStarted);
312 : }
313 : }
314 : }
315 :
316 : // Stop any transitions for properties that are no longer in
317 : // 'transition-property'.
318 : // Also stop any transitions for properties that just changed (and are
319 : // still in the set of properties to transition), but we didn't just
320 : // start the transition because delay and duration are both zero.
321 0 : if (et) {
322 : bool checkProperties =
323 0 : disp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
324 0 : nsCSSPropertySet allTransitionProperties;
325 0 : if (checkProperties) {
326 0 : for (PRUint32 i = disp->mTransitionPropertyCount; i-- != 0; ) {
327 0 : const nsTransition& t = disp->mTransitions[i];
328 : // FIXME: Would be good to find a way to share code between this
329 : // interpretation of transition-property and the one above.
330 0 : nsCSSProperty property = t.GetProperty();
331 0 : if (property == eCSSPropertyExtra_no_properties ||
332 : property == eCSSProperty_UNKNOWN) {
333 : // Nothing to do, but need to exclude this from cases below.
334 0 : } else if (property == eCSSPropertyExtra_all_properties) {
335 0 : for (nsCSSProperty p = nsCSSProperty(0);
336 : p < eCSSProperty_COUNT_no_shorthands;
337 : p = nsCSSProperty(p + 1)) {
338 0 : allTransitionProperties.AddProperty(p);
339 : }
340 0 : } else if (nsCSSProps::IsShorthand(property)) {
341 0 : CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
342 0 : allTransitionProperties.AddProperty(*subprop);
343 : }
344 : } else {
345 0 : allTransitionProperties.AddProperty(property);
346 : }
347 : }
348 : }
349 :
350 0 : nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
351 0 : PRUint32 i = pts.Length();
352 0 : NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
353 0 : nsStyleAnimation::Value currentValue;
354 0 : do {
355 0 : --i;
356 0 : ElementPropertyTransition &pt = pts[i];
357 : // properties no longer in 'transition-property'
358 0 : if ((checkProperties &&
359 0 : !allTransitionProperties.HasProperty(pt.mProperty)) ||
360 : // properties whose computed values changed but delay and
361 : // duration are both zero
362 : !ExtractComputedValueForTransition(pt.mProperty, aNewStyleContext,
363 0 : currentValue) ||
364 0 : currentValue != pt.mEndValue) {
365 : // stop the transition
366 0 : pts.RemoveElementAt(i);
367 : }
368 : } while (i != 0);
369 :
370 0 : if (pts.IsEmpty()) {
371 0 : et->Destroy();
372 0 : et = nsnull;
373 : }
374 : }
375 :
376 0 : if (!startedAny) {
377 0 : return nsnull;
378 : }
379 :
380 0 : NS_ABORT_IF_FALSE(et, "must have element transitions if we started "
381 : "any transitions");
382 :
383 : // In the CSS working group discussion (2009 Jul 15 telecon,
384 : // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
385 : // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
386 : // the working group decided that a transition property on an
387 : // element should not cause any transitions if the property change
388 : // is itself inheriting a value that is transitioning on an
389 : // ancestor. So, to get the correct behavior, we continue the
390 : // restyle that caused this transition using a "covering" rule that
391 : // covers up any changes on which we started transitions, so that
392 : // descendants don't start their own transitions. (In the case of
393 : // negative transition delay, this covering rule produces different
394 : // results than applying the transition rule immediately would).
395 : // Our caller is responsible for restyling again using this covering
396 : // rule.
397 :
398 0 : nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule;
399 0 : if (!coverRule) {
400 0 : NS_WARNING("out of memory");
401 0 : return nsnull;
402 : }
403 :
404 0 : nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
405 0 : for (PRUint32 i = 0, i_end = pts.Length(); i < i_end; ++i) {
406 0 : ElementPropertyTransition &pt = pts[i];
407 0 : if (whichStarted.HasProperty(pt.mProperty)) {
408 0 : coverRule->AddValue(pt.mProperty, pt.mStartValue);
409 : }
410 : }
411 :
412 0 : return coverRule.forget();
413 : }
414 :
415 : void
416 0 : nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
417 : const nsTransition& aTransition,
418 : dom::Element *aElement,
419 : ElementTransitions *&aElementTransitions,
420 : nsStyleContext *aOldStyleContext,
421 : nsStyleContext *aNewStyleContext,
422 : bool *aStartedAny,
423 : nsCSSPropertySet *aWhichStarted)
424 : {
425 : // IsShorthand itself will assert if aProperty is not a property.
426 0 : NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
427 : "property out of range");
428 :
429 0 : if (aWhichStarted->HasProperty(aProperty)) {
430 : // A later item in transition-property already started a
431 : // transition for this property, so we ignore this one.
432 : // See comment above and
433 : // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
434 0 : return;
435 : }
436 :
437 0 : if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
438 0 : return;
439 : }
440 :
441 0 : ElementPropertyTransition pt;
442 0 : nsStyleAnimation::Value dummyValue;
443 : bool haveValues =
444 : ExtractComputedValueForTransition(aProperty, aOldStyleContext,
445 0 : pt.mStartValue) &&
446 : ExtractComputedValueForTransition(aProperty, aNewStyleContext,
447 0 : pt.mEndValue);
448 : bool shouldAnimate =
449 : haveValues &&
450 0 : pt.mStartValue != pt.mEndValue &&
451 : // Check that we can interpolate between these values
452 : // (If this is ever a performance problem, we could add a
453 : // CanInterpolate method, but it seems fine for now.)
454 : nsStyleAnimation::Interpolate(aProperty, pt.mStartValue, pt.mEndValue,
455 0 : 0.5, dummyValue);
456 :
457 0 : PRUint32 currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
458 0 : if (aElementTransitions) {
459 : nsTArray<ElementPropertyTransition> &pts =
460 0 : aElementTransitions->mPropertyTransitions;
461 0 : for (PRUint32 i = 0, i_end = pts.Length(); i < i_end; ++i) {
462 0 : if (pts[i].mProperty == aProperty) {
463 0 : currentIndex = i;
464 0 : break;
465 : }
466 : }
467 : }
468 :
469 0 : nsPresContext *presContext = aNewStyleContext->PresContext();
470 :
471 0 : if (!shouldAnimate) {
472 : nsTArray<ElementPropertyTransition> &pts =
473 0 : aElementTransitions->mPropertyTransitions;
474 0 : if (currentIndex != nsTArray<ElementPropertyTransition>::NoIndex &&
475 0 : (!haveValues || pts[currentIndex].mEndValue != pt.mEndValue)) {
476 : // We're in the middle of a transition, but just got a
477 : // non-transition style change changing to exactly the
478 : // current in-progress value. (This is quite easy to cause
479 : // using 'transition-delay'.)
480 : //
481 : // We also check that this current in-progress value is different
482 : // from the end value; we don't want to cancel a transition that
483 : // is almost done (and whose current value rounds to its end
484 : // value) just because we got an unrelated style change.
485 0 : pts.RemoveElementAt(currentIndex);
486 0 : if (pts.IsEmpty()) {
487 0 : aElementTransitions->Destroy();
488 : // |aElementTransitions| is now a dangling pointer!
489 0 : aElementTransitions = nsnull;
490 : }
491 : // WalkTransitionRule already called RestyleForAnimation.
492 : }
493 : return;
494 : }
495 :
496 : TimeStamp mostRecentRefresh =
497 0 : presContext->RefreshDriver()->MostRecentRefresh();
498 :
499 0 : const nsTimingFunction &tf = aTransition.GetTimingFunction();
500 0 : float delay = aTransition.GetDelay();
501 0 : float duration = aTransition.GetDuration();
502 0 : if (duration < 0.0) {
503 : // The spec says a negative duration is treated as zero.
504 0 : duration = 0.0;
505 : }
506 0 : pt.mStartForReversingTest = pt.mStartValue;
507 0 : pt.mReversePortion = 1.0;
508 :
509 : // We need to check two things if we have a currently running
510 : // transition for this property.
511 0 : if (currentIndex != nsTArray<ElementPropertyTransition>::NoIndex) {
512 : const ElementPropertyTransition &oldPT =
513 0 : aElementTransitions->mPropertyTransitions[currentIndex];
514 :
515 0 : if (oldPT.mEndValue == pt.mEndValue) {
516 : // If we got a style change that changed the value to the endpoint
517 : // of the currently running transition, we don't want to interrupt
518 : // its timing function.
519 : // WalkTransitionRule already called RestyleForAnimation.
520 : return;
521 : }
522 :
523 : // If the new transition reverses the old one, we'll need to handle
524 : // the timing differently.
525 0 : if (!oldPT.IsRemovedSentinel() &&
526 0 : oldPT.mStartForReversingTest == pt.mEndValue) {
527 : // Compute the appropriate negative transition-delay such that right
528 : // now we'd end up at the current position.
529 : double valuePortion =
530 0 : oldPT.ValuePortionFor(mostRecentRefresh) * oldPT.mReversePortion +
531 0 : (1.0 - oldPT.mReversePortion);
532 : // A timing function with negative y1 (or y2!) might make
533 : // valuePortion negative. In this case, we still want to apply our
534 : // reversing logic based on relative distances, not make duration
535 : // negative.
536 0 : if (valuePortion < 0.0)
537 0 : valuePortion = -valuePortion;
538 : // A timing function with y2 (or y1!) greater than one might
539 : // advance past its terminal value. It's probably a good idea to
540 : // clamp valuePortion to be at most one to preserve the invariant
541 : // that a transition will complete within at most its specified
542 : // time.
543 0 : if (valuePortion > 1.0)
544 0 : valuePortion = 1.0;
545 :
546 : // Negative delays are essentially part of the transition
547 : // function, so reduce them along with the duration, but don't
548 : // reduce positive delays.
549 0 : if (delay < 0.0f)
550 0 : delay *= valuePortion;
551 0 : duration *= valuePortion;
552 :
553 0 : pt.mStartForReversingTest = oldPT.mEndValue;
554 0 : pt.mReversePortion = valuePortion;
555 : }
556 : }
557 :
558 0 : pt.mProperty = aProperty;
559 0 : pt.mStartTime = mostRecentRefresh + TimeDuration::FromMilliseconds(delay);
560 0 : pt.mDuration = TimeDuration::FromMilliseconds(duration);
561 0 : pt.mTimingFunction.Init(tf);
562 :
563 0 : if (!aElementTransitions) {
564 : aElementTransitions =
565 : GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(),
566 0 : true);
567 0 : if (!aElementTransitions) {
568 0 : NS_WARNING("allocating ElementTransitions failed");
569 : return;
570 : }
571 : }
572 :
573 : nsTArray<ElementPropertyTransition> &pts =
574 0 : aElementTransitions->mPropertyTransitions;
575 : #ifdef DEBUG
576 0 : for (PRUint32 i = 0, i_end = pts.Length(); i < i_end; ++i) {
577 0 : NS_ABORT_IF_FALSE(i == currentIndex ||
578 : pts[i].mProperty != aProperty,
579 : "duplicate transitions for property");
580 : }
581 : #endif
582 0 : if (currentIndex != nsTArray<ElementPropertyTransition>::NoIndex) {
583 0 : pts[currentIndex] = pt;
584 : } else {
585 0 : if (!pts.AppendElement(pt)) {
586 0 : NS_WARNING("out of memory");
587 : return;
588 : }
589 : }
590 :
591 : nsRestyleHint hint =
592 0 : aNewStyleContext->GetPseudoType() ==
593 : nsCSSPseudoElements::ePseudo_NotPseudoElement ?
594 0 : eRestyle_Self : eRestyle_Subtree;
595 0 : presContext->PresShell()->RestyleForAnimation(aElement, hint);
596 :
597 0 : *aStartedAny = true;
598 0 : aWhichStarted->AddProperty(aProperty);
599 : }
600 :
601 : ElementTransitions*
602 0 : nsTransitionManager::GetElementTransitions(dom::Element *aElement,
603 : nsCSSPseudoElements::Type aPseudoType,
604 : bool aCreateIfNeeded)
605 : {
606 0 : if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
607 : // Early return for the most common case.
608 0 : return nsnull;
609 : }
610 :
611 : nsIAtom *propName;
612 0 : if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
613 0 : propName = nsGkAtoms::transitionsProperty;
614 0 : } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
615 0 : propName = nsGkAtoms::transitionsOfBeforeProperty;
616 0 : } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
617 0 : propName = nsGkAtoms::transitionsOfAfterProperty;
618 : } else {
619 0 : NS_ASSERTION(!aCreateIfNeeded,
620 : "should never try to create transitions for pseudo "
621 : "other than :before or :after");
622 0 : return nsnull;
623 : }
624 : ElementTransitions *et = static_cast<ElementTransitions*>(
625 0 : aElement->GetProperty(propName));
626 0 : if (!et && aCreateIfNeeded) {
627 : // FIXME: Consider arena-allocating?
628 0 : et = new ElementTransitions(aElement, propName, this);
629 0 : if (!et) {
630 0 : NS_WARNING("out of memory");
631 0 : return nsnull;
632 : }
633 : nsresult rv = aElement->SetProperty(propName, et,
634 0 : ElementTransitionsPropertyDtor, nsnull);
635 0 : if (NS_FAILED(rv)) {
636 0 : NS_WARNING("SetProperty failed");
637 0 : delete et;
638 0 : return nsnull;
639 : }
640 :
641 0 : AddElementData(et);
642 : }
643 :
644 0 : return et;
645 : }
646 :
647 : /*
648 : * nsIStyleRuleProcessor implementation
649 : */
650 :
651 : void
652 0 : nsTransitionManager::WalkTransitionRule(RuleProcessorData* aData,
653 : nsCSSPseudoElements::Type aPseudoType)
654 : {
655 : ElementTransitions *et =
656 0 : GetElementTransitions(aData->mElement, aPseudoType, false);
657 0 : if (!et) {
658 0 : return;
659 : }
660 :
661 0 : if (aData->mPresContext->IsProcessingRestyles() &&
662 0 : !aData->mPresContext->IsProcessingAnimationStyleChange()) {
663 : // If we're processing a normal style change rather than one from
664 : // animation, don't add the transition rule. This allows us to
665 : // compute the new style value rather than having the transition
666 : // override it, so that we can start transitioning differently.
667 :
668 : // We need to immediately restyle with animation
669 : // after doing this.
670 0 : if (et) {
671 : nsRestyleHint hint =
672 : aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ?
673 0 : eRestyle_Self : eRestyle_Subtree;
674 0 : mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint);
675 : }
676 0 : return;
677 : }
678 :
679 : et->EnsureStyleRuleFor(
680 0 : aData->mPresContext->RefreshDriver()->MostRecentRefresh());
681 :
682 0 : aData->mRuleWalker->Forward(et->mStyleRule);
683 : }
684 :
685 : /* virtual */ void
686 0 : nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData)
687 : {
688 0 : NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
689 : "pres context mismatch");
690 : WalkTransitionRule(aData,
691 0 : nsCSSPseudoElements::ePseudo_NotPseudoElement);
692 0 : }
693 :
694 : /* virtual */ void
695 0 : nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData* aData)
696 : {
697 0 : NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
698 : "pres context mismatch");
699 :
700 : // Note: If we're the only thing keeping a pseudo-element frame alive
701 : // (per ProbePseudoStyleContext), we still want to keep it alive, so
702 : // this is ok.
703 0 : WalkTransitionRule(aData, aData->mPseudoType);
704 0 : }
705 :
706 : /* virtual */ void
707 0 : nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData)
708 : {
709 0 : }
710 :
711 : #ifdef MOZ_XUL
712 : /* virtual */ void
713 0 : nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData)
714 : {
715 0 : }
716 : #endif
717 :
718 : /* virtual */ size_t
719 0 : nsTransitionManager::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const
720 : {
721 0 : return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
722 : }
723 :
724 : /* virtual */ size_t
725 0 : nsTransitionManager::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const
726 : {
727 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
728 : }
729 :
730 0 : struct TransitionEventInfo {
731 : nsCOMPtr<nsIContent> mElement;
732 : nsTransitionEvent mEvent;
733 :
734 0 : TransitionEventInfo(nsIContent *aElement, nsCSSProperty aProperty,
735 : TimeDuration aDuration)
736 : : mElement(aElement),
737 : mEvent(true, NS_TRANSITION_END,
738 0 : NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty)),
739 0 : aDuration.ToSeconds())
740 : {
741 0 : }
742 :
743 : // nsTransitionEvent doesn't support copy-construction, so we need
744 : // to ourselves in order to work with nsTArray
745 0 : TransitionEventInfo(const TransitionEventInfo &aOther)
746 : : mElement(aOther.mElement),
747 : mEvent(true, NS_TRANSITION_END,
748 0 : aOther.mEvent.propertyName, aOther.mEvent.elapsedTime)
749 : {
750 0 : }
751 : };
752 :
753 : /* virtual */ void
754 0 : nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
755 : {
756 0 : NS_ABORT_IF_FALSE(mPresContext,
757 : "refresh driver should not notify additional observers "
758 : "after pres context has been destroyed");
759 0 : if (!mPresContext->GetPresShell()) {
760 : // Someone might be keeping mPresContext alive past the point
761 : // where it has been torn down; don't bother doing anything in
762 : // this case. But do get rid of all our transitions so we stop
763 : // triggering refreshes.
764 0 : RemoveAllElementData();
765 0 : return;
766 : }
767 :
768 0 : nsTArray<TransitionEventInfo> events;
769 :
770 : // Trim transitions that have completed, and post restyle events for
771 : // frames that are still transitioning.
772 : {
773 0 : PRCList *next = PR_LIST_HEAD(&mElementData);
774 0 : while (next != &mElementData) {
775 0 : ElementTransitions *et = static_cast<ElementTransitions*>(next);
776 0 : next = PR_NEXT_LINK(next);
777 :
778 0 : NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() ==
779 : mPresContext->Document(),
780 : "nsGenericElement::UnbindFromTree should have "
781 : "destroyed the element transitions object");
782 :
783 0 : PRUint32 i = et->mPropertyTransitions.Length();
784 0 : NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
785 0 : do {
786 0 : --i;
787 0 : ElementPropertyTransition &pt = et->mPropertyTransitions[i];
788 0 : if (pt.IsRemovedSentinel()) {
789 : // Actually remove transitions one cycle after their
790 : // completion. See comment below.
791 0 : et->mPropertyTransitions.RemoveElementAt(i);
792 0 : } else if (pt.mStartTime + pt.mDuration <= aTime) {
793 : // This transition has completed.
794 :
795 : // Fire transitionend events only for transitions on elements
796 : // and not those on pseudo-elements, since we can't target an
797 : // event at pseudo-elements.
798 0 : if (et->mElementProperty == nsGkAtoms::transitionsProperty) {
799 0 : nsCSSProperty prop = pt.mProperty;
800 0 : if (nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_REPORT_OTHER_NAME))
801 : {
802 0 : prop = nsCSSProps::OtherNameFor(prop);
803 : }
804 : events.AppendElement(
805 0 : TransitionEventInfo(et->mElement, prop, pt.mDuration));
806 : }
807 :
808 : // Leave this transition in the list for one more refresh
809 : // cycle, since we haven't yet processed its style change, and
810 : // if we also have (already, or will have from processing
811 : // transitionend events or other refresh driver notifications)
812 : // a non-animation style change that would affect it, we need
813 : // to know not to start a new transition for the transition
814 : // from the almost-completed value to the final value.
815 0 : pt.SetRemovedSentinel();
816 : }
817 : } while (i != 0);
818 :
819 : // We need to restyle even if the transition rule no longer
820 : // applies (in which case we just made it not apply).
821 0 : NS_ASSERTION(et->mElementProperty == nsGkAtoms::transitionsProperty ||
822 : et->mElementProperty == nsGkAtoms::transitionsOfBeforeProperty ||
823 : et->mElementProperty == nsGkAtoms::transitionsOfAfterProperty,
824 : "Unexpected element property; might restyle too much");
825 : nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ?
826 0 : eRestyle_Self : eRestyle_Subtree;
827 0 : mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint);
828 :
829 0 : if (et->mPropertyTransitions.IsEmpty()) {
830 0 : et->Destroy();
831 : // |et| is now a dangling pointer!
832 0 : et = nsnull;
833 : }
834 : }
835 : }
836 :
837 : // We might have removed transitions above.
838 0 : ElementDataRemoved();
839 :
840 0 : for (PRUint32 i = 0, i_end = events.Length(); i < i_end; ++i) {
841 0 : TransitionEventInfo &info = events[i];
842 0 : nsEventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
843 :
844 0 : if (!mPresContext) {
845 0 : break;
846 : }
847 : }
848 : }
|