Home | History | Annotate | Download | only in animation
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "core/animation/CompositorAnimations.h"
     33 
     34 #include "core/animation/AnimatableDouble.h"
     35 #include "core/animation/AnimatableFilterOperations.h"
     36 #include "core/animation/AnimatableTransform.h"
     37 #include "core/animation/AnimatableValue.h"
     38 #include "core/animation/CompositorAnimationsImpl.h"
     39 #include "core/platform/animation/AnimationTranslationUtil.h"
     40 #include "core/rendering/CompositedLayerMapping.h"
     41 #include "core/rendering/RenderBoxModelObject.h"
     42 #include "core/rendering/RenderLayer.h"
     43 #include "core/rendering/RenderObject.h"
     44 #include "public/platform/Platform.h"
     45 #include "public/platform/WebAnimation.h"
     46 #include "public/platform/WebCompositorSupport.h"
     47 #include "public/platform/WebFilterAnimationCurve.h"
     48 #include "public/platform/WebFilterKeyframe.h"
     49 #include "public/platform/WebFloatAnimationCurve.h"
     50 #include "public/platform/WebFloatKeyframe.h"
     51 #include "public/platform/WebTransformAnimationCurve.h"
     52 #include "public/platform/WebTransformKeyframe.h"
     53 
     54 #include <algorithm>
     55 #include <cmath>
     56 
     57 namespace WebCore {
     58 
     59 namespace {
     60 
     61 void getKeyframeValuesForProperty(const KeyframeAnimationEffect* effect, CSSPropertyID id, double scale, bool reverse, KeyframeVector& values)
     62 {
     63     ASSERT(values.isEmpty());
     64     const KeyframeVector& group = effect->getPropertySpecificKeyframes(id);
     65 
     66     if (reverse) {
     67         for (size_t i = group.size(); i--;) {
     68             double offset = (1 - group[i]->offset()) * scale;
     69             values.append(group[i]->cloneWithOffset(offset));
     70         }
     71     } else {
     72         for (size_t i = 0; i < group.size(); ++i) {
     73             double offset = group[i]->offset() * scale;
     74             values.append(group[i]->cloneWithOffset(offset));
     75         }
     76     }
     77 }
     78 
     79 }
     80 
     81 // -----------------------------------------------------------------------
     82 // TimingFunctionReverser methods
     83 // -----------------------------------------------------------------------
     84 
     85 PassRefPtr<TimingFunction> CompositorAnimationsTimingFunctionReverser::reverse(const LinearTimingFunction* timefunc)
     86 {
     87     return const_cast<LinearTimingFunction*>(timefunc);
     88 }
     89 
     90 PassRefPtr<TimingFunction> CompositorAnimationsTimingFunctionReverser::reverse(const CubicBezierTimingFunction* timefunc)
     91 {
     92     switch (timefunc->subType()) {
     93     case CubicBezierTimingFunction::EaseIn:
     94         return CubicBezierTimingFunction::preset(CubicBezierTimingFunction::EaseOut);
     95     case CubicBezierTimingFunction::EaseOut:
     96         return CubicBezierTimingFunction::preset(CubicBezierTimingFunction::EaseIn);
     97     case CubicBezierTimingFunction::EaseInOut:
     98         return const_cast<CubicBezierTimingFunction*>(timefunc);
     99     case CubicBezierTimingFunction::Ease: // Ease is not symmetrical
    100     case CubicBezierTimingFunction::Custom:
    101         return CubicBezierTimingFunction::create(1 - timefunc->x2(), 1 - timefunc->y2(), 1 - timefunc->x1(), 1 - timefunc->y1());
    102     default:
    103         ASSERT_NOT_REACHED();
    104         return PassRefPtr<TimingFunction>();
    105     }
    106 }
    107 
    108 PassRefPtr<TimingFunction> CompositorAnimationsTimingFunctionReverser::reverse(const ChainedTimingFunction* timefunc)
    109 {
    110     RefPtr<ChainedTimingFunction> reversed = ChainedTimingFunction::create();
    111     for (size_t i = 0; i < timefunc->m_segments.size(); i++) {
    112         size_t index = timefunc->m_segments.size() - i - 1;
    113 
    114         RefPtr<TimingFunction> rtf = reverse(timefunc->m_segments[index].m_timingFunction.get());
    115         reversed->appendSegment(1 - timefunc->m_segments[index].m_min, rtf.get());
    116     }
    117     return reversed;
    118 }
    119 
    120 PassRefPtr<TimingFunction> CompositorAnimationsTimingFunctionReverser::reverse(const TimingFunction* timefunc)
    121 {
    122     switch (timefunc->type()) {
    123     case TimingFunction::LinearFunction: {
    124         const LinearTimingFunction* linear = toLinearTimingFunction(timefunc);
    125         return reverse(linear);
    126     }
    127     case TimingFunction::CubicBezierFunction: {
    128         const CubicBezierTimingFunction* cubic = toCubicBezierTimingFunction(timefunc);
    129         return reverse(cubic);
    130     }
    131     case TimingFunction::ChainedFunction: {
    132         const ChainedTimingFunction* chained = toChainedTimingFunction(timefunc);
    133         return reverse(chained);
    134     }
    135 
    136     // Steps function can not be reversed.
    137     case TimingFunction::StepsFunction:
    138     default:
    139         ASSERT_NOT_REACHED();
    140         return PassRefPtr<TimingFunction>();
    141     }
    142 }
    143 
    144 // -----------------------------------------------------------------------
    145 // CompositorAnimations public API
    146 // -----------------------------------------------------------------------
    147 
    148 bool CompositorAnimations::isCandidateForAnimationOnCompositor(const Timing& timing, const AnimationEffect& effect)
    149 {
    150     const KeyframeAnimationEffect& keyframeEffect = *toKeyframeAnimationEffect(&effect);
    151 
    152     // Are the keyframes convertible?
    153     const KeyframeAnimationEffect::KeyframeVector frames = keyframeEffect.getFrames();
    154     for (size_t i = 0; i < frames.size(); ++i) {
    155         // Only replace mode can be accelerated
    156         if (frames[i]->composite() != AnimationEffect::CompositeReplace)
    157             return false;
    158 
    159         // Check all the properties can be accelerated
    160         const PropertySet properties = frames[i]->properties(); // FIXME: properties creates a whole new PropertySet!
    161 
    162         if (properties.isEmpty())
    163             return false;
    164 
    165         for (PropertySet::const_iterator it = properties.begin(); it != properties.end(); ++it) {
    166             switch (*it) {
    167             case CSSPropertyOpacity:
    168                 continue;
    169             case CSSPropertyWebkitTransform:
    170                 if (toAnimatableTransform(frames[i]->propertyValue(CSSPropertyWebkitTransform))->transformOperations().dependsOnBoxSize())
    171                     return false;
    172                 continue;
    173             case CSSPropertyWebkitFilter: {
    174                 const FilterOperations& operations = toAnimatableFilterOperations(frames[i]->propertyValue(CSSPropertyWebkitFilter))->operations();
    175                 if (operations.hasFilterThatMovesPixels())
    176                     return false;
    177                 for (size_t i = 0; i < operations.size(); i++) {
    178                     const FilterOperation& op = *operations.at(i);
    179                     if (op.type() == FilterOperation::VALIDATED_CUSTOM || op.type() == FilterOperation::CUSTOM)
    180                         return false;
    181                 }
    182                 continue;
    183             }
    184             default:
    185                 return false;
    186             }
    187         }
    188     }
    189 
    190     // Is the timing object convertible?
    191     CompositorAnimationsImpl::CompositorTiming out;
    192     if (!CompositorAnimationsImpl::convertTimingForCompositor(timing, out))
    193         return false;
    194 
    195     // Is the timing function convertible?
    196     switch (timing.timingFunction->type()) {
    197     case TimingFunction::LinearFunction:
    198         break;
    199 
    200     case TimingFunction::CubicBezierFunction:
    201         // Can have a cubic if we don't have to split it (IE only have two frames).
    202         if (frames.size() != 2)
    203             return false;
    204 
    205         ASSERT(frames[0]->offset() == 0.0 && frames[1]->offset() == 1.0);
    206         break;
    207 
    208     case TimingFunction::StepsFunction:
    209         return false;
    210 
    211     case TimingFunction::ChainedFunction: {
    212         // Currently we only support chained segments in the form the CSS code
    213         // generates. These chained segments are only one level deep and have
    214         // one timing function per frame.
    215         const ChainedTimingFunction* chained = static_cast<const ChainedTimingFunction*>(timing.timingFunction.get());
    216         if (!chained->m_segments.size())
    217             return false;
    218 
    219         if (frames.size() != chained->m_segments.size() + 1)
    220             return false;
    221 
    222         for (size_t timeIndex = 0; timeIndex < chained->m_segments.size(); timeIndex++) {
    223             const ChainedTimingFunction::Segment& segment = chained->m_segments[timeIndex];
    224 
    225             if (frames[timeIndex]->offset() != segment.m_min || frames[timeIndex + 1]->offset() != segment.m_max)
    226                 return false;
    227 
    228             switch (segment.m_timingFunction->type()) {
    229             case TimingFunction::LinearFunction:
    230             case TimingFunction::CubicBezierFunction:
    231                 continue;
    232 
    233             case TimingFunction::StepsFunction:
    234             case TimingFunction::ChainedFunction:
    235             default:
    236                 return false;
    237             }
    238         }
    239 
    240         break;
    241     }
    242     default:
    243         ASSERT_NOT_REACHED();
    244         return false;
    245     }
    246 
    247     return true;
    248 }
    249 
    250 bool CompositorAnimations::canStartAnimationOnCompositor(const Element& element)
    251 {
    252     return element.renderer() && element.renderer()->compositingState() == PaintsIntoOwnBacking;
    253 }
    254 
    255 bool CompositorAnimations::startAnimationOnCompositor(const Element& element, const Timing& timing, const AnimationEffect& effect, Vector<int>& startedAnimationIds)
    256 {
    257     ASSERT(startedAnimationIds.isEmpty());
    258     ASSERT(isCandidateForAnimationOnCompositor(timing, effect));
    259     ASSERT(canStartAnimationOnCompositor(element));
    260 
    261     const KeyframeAnimationEffect& keyframeEffect = *toKeyframeAnimationEffect(&effect);
    262 
    263     RenderLayer* layer = toRenderBoxModelObject(element.renderer())->layer();
    264     ASSERT(layer);
    265 
    266     Vector<OwnPtr<blink::WebAnimation> > animations;
    267     CompositorAnimationsImpl::getAnimationOnCompositor(timing, keyframeEffect, animations);
    268     ASSERT(!animations.isEmpty());
    269     for (size_t i = 0; i < animations.size(); ++i) {
    270         int id = animations[i]->id();
    271         if (!layer->compositedLayerMapping()->mainGraphicsLayer()->addAnimation(animations[i].release())) {
    272             // FIXME: We should know ahead of time whether these animations can be started.
    273             for (size_t j = 0; j < startedAnimationIds.size(); ++j)
    274                 cancelAnimationOnCompositor(element, startedAnimationIds[j]);
    275             startedAnimationIds.clear();
    276             return false;
    277         }
    278         startedAnimationIds.append(id);
    279     }
    280     ASSERT(!startedAnimationIds.isEmpty());
    281     return true;
    282 }
    283 
    284 void CompositorAnimations::cancelAnimationOnCompositor(const Element& element, int id)
    285 {
    286     if (!canStartAnimationOnCompositor(element)) {
    287         ASSERT_NOT_REACHED();
    288         return;
    289     }
    290     toRenderBoxModelObject(element.renderer())->layer()->compositedLayerMapping()->mainGraphicsLayer()->removeAnimation(id);
    291 }
    292 
    293 void CompositorAnimations::pauseAnimationForTestingOnCompositor(const Element& element, int id, double pauseTime)
    294 {
    295     if (!canStartAnimationOnCompositor(element)) {
    296         ASSERT_NOT_REACHED();
    297         return;
    298     }
    299     toRenderBoxModelObject(element.renderer())->layer()->compositedLayerMapping()->mainGraphicsLayer()->pauseAnimation(id, pauseTime);
    300 }
    301 
    302 // -----------------------------------------------------------------------
    303 // CompositorAnimationsImpl
    304 // -----------------------------------------------------------------------
    305 
    306 bool CompositorAnimationsImpl::convertTimingForCompositor(const Timing& timing, CompositorTiming& out)
    307 {
    308     timing.assertValid();
    309 
    310     // All fill modes are supported (the calling code handles them).
    311 
    312     // FIXME: Support non-zero iteration start.
    313     if (timing.iterationStart)
    314         return false;
    315 
    316     // FIXME: Compositor only supports positive, integer iteration counts.
    317     // Zero iterations could be converted, but silly.
    318     if ((std::floor(timing.iterationCount) != timing.iterationCount) || timing.iterationCount <= 0)
    319         return false;
    320 
    321     if (!timing.iterationDuration)
    322         return false;
    323 
    324     // FIXME: Support other playback rates
    325     if (timing.playbackRate != 1)
    326         return false;
    327 
    328     // All directions are supported.
    329 
    330     // Now attempt an actual conversion
    331     out.scaledDuration = timing.iterationDuration;
    332     ASSERT(out.scaledDuration > 0);
    333 
    334     double scaledStartDelay = timing.startDelay;
    335     if (scaledStartDelay > 0 && scaledStartDelay > out.scaledDuration * timing.iterationCount)
    336         return false;
    337 
    338     out.reverse = (timing.direction == Timing::PlaybackDirectionReverse
    339         || timing.direction == Timing::PlaybackDirectionAlternateReverse);
    340     out.alternate = (timing.direction == Timing::PlaybackDirectionAlternate
    341         || timing.direction == Timing::PlaybackDirectionAlternateReverse);
    342 
    343     if (!std::isfinite(timing.iterationCount)) {
    344         out.adjustedIterationCount = -1;
    345     } else {
    346         out.adjustedIterationCount = std::floor(timing.iterationCount);
    347         ASSERT(out.adjustedIterationCount > 0);
    348     }
    349 
    350     // Compositor's time offset is positive for seeking into the animation.
    351     out.scaledTimeOffset = -scaledStartDelay;
    352     return true;
    353 }
    354 
    355 namespace {
    356 
    357 template<typename PlatformAnimationCurveType, typename PlatformAnimationKeyframeType>
    358 void addKeyframeWithTimingFunction(PlatformAnimationCurveType& curve, const PlatformAnimationKeyframeType& keyframe, const TimingFunction* timingFunction)
    359 {
    360     if (!timingFunction) {
    361         curve.add(keyframe);
    362         return;
    363     }
    364 
    365     switch (timingFunction->type()) {
    366     case TimingFunction::LinearFunction:
    367         curve.add(keyframe, blink::WebAnimationCurve::TimingFunctionTypeLinear);
    368         return;
    369 
    370     case TimingFunction::CubicBezierFunction: {
    371         const CubicBezierTimingFunction* cubic = toCubicBezierTimingFunction(timingFunction);
    372 
    373         if (cubic->subType() == CubicBezierTimingFunction::Custom) {
    374             curve.add(keyframe, cubic->x1(), cubic->y1(), cubic->x2(), cubic->y2());
    375         } else {
    376 
    377             blink::WebAnimationCurve::TimingFunctionType easeType;
    378             switch (cubic->subType()) {
    379             case CubicBezierTimingFunction::Ease:
    380                 easeType = blink::WebAnimationCurve::TimingFunctionTypeEase;
    381                 break;
    382             case CubicBezierTimingFunction::EaseIn:
    383                 easeType = blink::WebAnimationCurve::TimingFunctionTypeEaseIn;
    384                 break;
    385             case CubicBezierTimingFunction::EaseOut:
    386                 easeType = blink::WebAnimationCurve::TimingFunctionTypeEaseOut;
    387                 break;
    388             case CubicBezierTimingFunction::EaseInOut:
    389                 easeType = blink::WebAnimationCurve::TimingFunctionTypeEaseInOut;
    390                 break;
    391 
    392             // Custom Bezier are handled seperately.
    393             case CubicBezierTimingFunction::Custom:
    394             default:
    395                 ASSERT_NOT_REACHED();
    396                 return;
    397             }
    398 
    399             curve.add(keyframe, easeType);
    400         }
    401         return;
    402     }
    403 
    404     case TimingFunction::StepsFunction:
    405     case TimingFunction::ChainedFunction:
    406     default:
    407         ASSERT_NOT_REACHED();
    408         return;
    409     }
    410 }
    411 
    412 } // namespace anoymous
    413 
    414 void CompositorAnimationsImpl::addKeyframesToCurve(blink::WebAnimationCurve& curve, const KeyframeVector& keyframes, const TimingFunction& timingFunction)
    415 {
    416     for (size_t i = 0; i < keyframes.size(); i++) {
    417         const TimingFunction* keyframeTimingFunction = 0;
    418         if (i + 1 < keyframes.size()) { // Last keyframe has no timing function
    419             switch (timingFunction.type()) {
    420             case TimingFunction::LinearFunction:
    421             case TimingFunction::CubicBezierFunction:
    422                 keyframeTimingFunction = &timingFunction;
    423                 break;
    424 
    425             case TimingFunction::ChainedFunction: {
    426                 const ChainedTimingFunction& chained = toChainedTimingFunction(timingFunction);
    427                 // ChainedTimingFunction criteria was checked in isCandidate,
    428                 // assert it is valid.
    429                 ASSERT(keyframes.size() == chained.m_segments.size() + 1);
    430 
    431                 keyframeTimingFunction = chained.m_segments[i].m_timingFunction.get();
    432                 break;
    433             }
    434             case TimingFunction::StepsFunction:
    435             default:
    436                 ASSERT_NOT_REACHED();
    437             }
    438         }
    439 
    440         ASSERT(!keyframes[i]->value()->dependsOnUnderlyingValue());
    441         RefPtr<AnimatableValue> value = keyframes[i]->value()->compositeOnto(0);
    442 
    443         switch (curve.type()) {
    444         case blink::WebAnimationCurve::AnimationCurveTypeFilter: {
    445             OwnPtr<blink::WebFilterOperations> ops = adoptPtr(blink::Platform::current()->compositorSupport()->createFilterOperations());
    446             bool converted = toWebFilterOperations(toAnimatableFilterOperations(value.get())->operations(), ops.get());
    447             ASSERT_UNUSED(converted, converted);
    448 
    449             blink::WebFilterKeyframe filterKeyframe(keyframes[i]->offset(), ops.release());
    450             blink::WebFilterAnimationCurve* filterCurve = static_cast<blink::WebFilterAnimationCurve*>(&curve);
    451             addKeyframeWithTimingFunction(*filterCurve, filterKeyframe, keyframeTimingFunction);
    452             break;
    453         }
    454         case blink::WebAnimationCurve::AnimationCurveTypeFloat: {
    455             blink::WebFloatKeyframe floatKeyframe(keyframes[i]->offset(), toAnimatableDouble(value.get())->toDouble());
    456             blink::WebFloatAnimationCurve* floatCurve = static_cast<blink::WebFloatAnimationCurve*>(&curve);
    457             addKeyframeWithTimingFunction(*floatCurve, floatKeyframe, keyframeTimingFunction);
    458             break;
    459         }
    460         case blink::WebAnimationCurve::AnimationCurveTypeTransform: {
    461             OwnPtr<blink::WebTransformOperations> ops = adoptPtr(blink::Platform::current()->compositorSupport()->createTransformOperations());
    462             toWebTransformOperations(toAnimatableTransform(value.get())->transformOperations(), FloatSize(), ops.get());
    463 
    464             blink::WebTransformKeyframe transformKeyframe(keyframes[i]->offset(), ops.release());
    465             blink::WebTransformAnimationCurve* transformCurve = static_cast<blink::WebTransformAnimationCurve*>(&curve);
    466             addKeyframeWithTimingFunction(*transformCurve, transformKeyframe, keyframeTimingFunction);
    467             break;
    468         }
    469         default:
    470             ASSERT_NOT_REACHED();
    471         }
    472     }
    473 }
    474 
    475 void CompositorAnimationsImpl::getAnimationOnCompositor(
    476     const Timing& timing, const KeyframeAnimationEffect& effect, Vector<OwnPtr<blink::WebAnimation> >& animations)
    477 {
    478     ASSERT(animations.isEmpty());
    479     CompositorTiming compositorTiming;
    480     bool timingValid = convertTimingForCompositor(timing, compositorTiming);
    481     ASSERT_UNUSED(timingValid, timingValid);
    482 
    483     RefPtr<TimingFunction> timingFunction = timing.timingFunction;
    484     if (compositorTiming.reverse)
    485         timingFunction = CompositorAnimationsTimingFunctionReverser::reverse(timingFunction.get());
    486 
    487     PropertySet properties = effect.properties();
    488     ASSERT(!properties.isEmpty());
    489     for (PropertySet::iterator it = properties.begin(); it != properties.end(); ++it) {
    490 
    491         KeyframeVector values;
    492         getKeyframeValuesForProperty(&effect, *it, compositorTiming.scaledDuration, compositorTiming.reverse, values);
    493 
    494         blink::WebAnimation::TargetProperty targetProperty;
    495         OwnPtr<blink::WebAnimationCurve> curve;
    496         switch (*it) {
    497         case CSSPropertyOpacity: {
    498             targetProperty = blink::WebAnimation::TargetPropertyOpacity;
    499 
    500             blink::WebFloatAnimationCurve* floatCurve = blink::Platform::current()->compositorSupport()->createFloatAnimationCurve();
    501             addKeyframesToCurve(*floatCurve, values, *timingFunction.get());
    502             curve = adoptPtr(floatCurve);
    503             break;
    504         }
    505         case CSSPropertyWebkitFilter: {
    506             targetProperty = blink::WebAnimation::TargetPropertyFilter;
    507             blink::WebFilterAnimationCurve* filterCurve = blink::Platform::current()->compositorSupport()->createFilterAnimationCurve();
    508             addKeyframesToCurve(*filterCurve, values, *timingFunction);
    509             curve = adoptPtr(filterCurve);
    510             break;
    511         }
    512         case CSSPropertyWebkitTransform: {
    513             targetProperty = blink::WebAnimation::TargetPropertyTransform;
    514             blink::WebTransformAnimationCurve* transformCurve = blink::Platform::current()->compositorSupport()->createTransformAnimationCurve();
    515             addKeyframesToCurve(*transformCurve, values, *timingFunction.get());
    516             curve = adoptPtr(transformCurve);
    517             break;
    518         }
    519         default:
    520             ASSERT_NOT_REACHED();
    521             continue;
    522         }
    523         ASSERT(curve.get());
    524 
    525         OwnPtr<blink::WebAnimation> animation = adoptPtr(blink::Platform::current()->compositorSupport()->createAnimation(*curve, targetProperty));
    526 
    527         animation->setIterations(compositorTiming.adjustedIterationCount);
    528         animation->setTimeOffset(compositorTiming.scaledTimeOffset);
    529         animation->setAlternatesDirection(compositorTiming.alternate);
    530 
    531         animations.append(animation.release());
    532     }
    533     ASSERT(!animations.isEmpty());
    534 }
    535 
    536 } // namespace WebCore
    537