GraphicsLayerClutter.cpp   [plain text]


/*
 * Copyright (C) 2010 Apple Inc. All rights reserved.
 * Copyright (C) 2011, 2012, 2013 Collabora Ltd.
 * Copyright (C) 2012, 2013 Intel Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#if USE(ACCELERATED_COMPOSITING)
#include "GraphicsLayerClutter.h"

#include "Animation.h"
#include "FloatConversion.h"
#include "FloatRect.h"
#include "GraphicsLayerActor.h"
#include "GraphicsLayerFactory.h"
#include "NotImplemented.h"
#include "RefPtrCairo.h"
#include "RotateTransformOperation.h"
#include "ScaleTransformOperation.h"
#include "TransformState.h"
#include "TransformationMatrix.h"
#include "TranslateTransformOperation.h"
#include <limits.h>
#include <wtf/text/CString.h>
#include <wtf/text/WTFString.h>

using namespace std;

namespace WebCore {

// If we send a duration of 0 to ClutterTimeline, then it will fail to set the duration. 
// So send a very small value instead.
static const float cAnimationAlmostZeroDuration = 1e-3f;

static bool isTransformTypeTransformationMatrix(TransformOperation::OperationType transformType)
{
    switch (transformType) {
    case TransformOperation::SKEW_X:
    case TransformOperation::SKEW_Y:
    case TransformOperation::SKEW:
    case TransformOperation::MATRIX:
    case TransformOperation::ROTATE_3D:
    case TransformOperation::MATRIX_3D:
    case TransformOperation::PERSPECTIVE:
    case TransformOperation::IDENTITY:
    case TransformOperation::NONE:
        return true;
    default:
        return false;
    }
}

static bool isTransformTypeFloatPoint3D(TransformOperation::OperationType transformType)
{
    switch (transformType) {
    case TransformOperation::SCALE:
    case TransformOperation::SCALE_3D:
    case TransformOperation::TRANSLATE:
    case TransformOperation::TRANSLATE_3D:
        return true;
    default:
        return false;
    }
}

static bool isTransformTypeNumber(TransformOperation::OperationType transformType)
{
    return !isTransformTypeTransformationMatrix(transformType) && !isTransformTypeFloatPoint3D(transformType);
}

static void getTransformFunctionValue(const TransformOperation* transformOp, TransformOperation::OperationType transformType, const IntSize& size, float& value)
{
    switch (transformType) {
    case TransformOperation::ROTATE:
    case TransformOperation::ROTATE_X:
    case TransformOperation::ROTATE_Y:
        value = transformOp ? narrowPrecisionToFloat(deg2rad(static_cast<const RotateTransformOperation*>(transformOp)->angle())) : 0;
        break;
    case TransformOperation::SCALE_X:
        value = transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->x()) : 1;
        break;
    case TransformOperation::SCALE_Y:
        value = transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->y()) : 1;
        break;
    case TransformOperation::SCALE_Z:
        value = transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->z()) : 1;
        break;
    case TransformOperation::TRANSLATE_X:
        value = transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->x(size)) : 0;
        break;
    case TransformOperation::TRANSLATE_Y:
        value = transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->y(size)) : 0;
        break;
    case TransformOperation::TRANSLATE_Z:
        value = transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->z(size)) : 0;
        break;
    default:
        break;
    }
}

static void getTransformFunctionValue(const TransformOperation* transformOp, TransformOperation::OperationType transformType, const IntSize& size, FloatPoint3D& value)
{
    switch (transformType) {
    case TransformOperation::SCALE:
    case TransformOperation::SCALE_3D:
        value.setX(transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->x()) : 1);
        value.setY(transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->y()) : 1);
        value.setZ(transformOp ? narrowPrecisionToFloat(static_cast<const ScaleTransformOperation*>(transformOp)->z()) : 1);
        break;
    case TransformOperation::TRANSLATE:
    case TransformOperation::TRANSLATE_3D:
        value.setX(transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->x(size)) : 0);
        value.setY(transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->y(size)) : 0);
        value.setZ(transformOp ? narrowPrecisionToFloat(static_cast<const TranslateTransformOperation*>(transformOp)->z(size)) : 0);
        break;
    default:
        break;
    }
}

static void getTransformFunctionValue(const TransformOperation* transformOp, TransformOperation::OperationType transformType, const IntSize& size, TransformationMatrix& value)
{
    switch (transformType) {
    case TransformOperation::SKEW_X:
    case TransformOperation::SKEW_Y:
    case TransformOperation::SKEW:
    case TransformOperation::MATRIX:
    case TransformOperation::ROTATE_3D:
    case TransformOperation::MATRIX_3D:
    case TransformOperation::PERSPECTIVE:
    case TransformOperation::IDENTITY:
    case TransformOperation::NONE:
        if (transformOp)
            transformOp->apply(value, size);
        else
            value.makeIdentity();
        break;
    default:
        break;
    }
}

static PlatformClutterAnimation::ValueFunctionType getValueFunctionNameForTransformOperation(TransformOperation::OperationType transformType)
{
    // Use literal strings to avoid link-time dependency on those symbols.
    switch (transformType) {
    case TransformOperation::ROTATE_X:
        return PlatformClutterAnimation::RotateX;
    case TransformOperation::ROTATE_Y:
        return PlatformClutterAnimation::RotateY;
    case TransformOperation::ROTATE:
        return PlatformClutterAnimation::RotateZ;
    case TransformOperation::SCALE_X:
        return PlatformClutterAnimation::ScaleX;
    case TransformOperation::SCALE_Y:
        return PlatformClutterAnimation::ScaleY;
    case TransformOperation::SCALE_Z:
        return PlatformClutterAnimation::ScaleZ;
    case TransformOperation::TRANSLATE_X:
        return PlatformClutterAnimation::TranslateX;
    case TransformOperation::TRANSLATE_Y:
        return PlatformClutterAnimation::TranslateY;
    case TransformOperation::TRANSLATE_Z:
        return PlatformClutterAnimation::TranslateZ;
    case TransformOperation::SCALE:
    case TransformOperation::SCALE_3D:
        return PlatformClutterAnimation::Scale;
    case TransformOperation::TRANSLATE:
    case TransformOperation::TRANSLATE_3D:
        return PlatformClutterAnimation::Translate;
    case TransformOperation::MATRIX_3D:
        return PlatformClutterAnimation::Matrix;
    default:
        return PlatformClutterAnimation::NoValueFunction;
    }
}

static String propertyIdToString(AnimatedPropertyID property)
{
    switch (property) {
    case AnimatedPropertyWebkitTransform:
        return "transform";
    case AnimatedPropertyOpacity:
        return "opacity";
    case AnimatedPropertyBackgroundColor:
        return "backgroundColor";
    case AnimatedPropertyWebkitFilter:
        ASSERT_NOT_REACHED();
    case AnimatedPropertyInvalid:
        ASSERT_NOT_REACHED();
    }
    ASSERT_NOT_REACHED();
    return "";
}

static String animationIdentifier(const String& animationName, AnimatedPropertyID property, int index)
{
    return animationName + '_' + String::number(property) + '_' + String::number(index);
}

static bool animationHasStepsTimingFunction(const KeyframeValueList& valueList, const Animation* anim)
{
    if (anim->timingFunction()->isStepsTimingFunction())
        return true;

    for (unsigned i = 0; i < valueList.size(); ++i) {
        const TimingFunction* timingFunction = valueList.at(i).timingFunction();
        if (timingFunction && timingFunction->isStepsTimingFunction())
            return true;
    }

    return false;
}

// This is the hook for WebCore compositor to know that the webKit clutter port implements
// compositing with GraphicsLayerClutter.
PassOwnPtr<GraphicsLayer> GraphicsLayer::create(GraphicsLayerFactory* factory, GraphicsLayerClient* client)
{
    if (!factory)
        return adoptPtr(new GraphicsLayerClutter(client));

    return factory->createGraphicsLayer(client);
}

PassOwnPtr<GraphicsLayer> GraphicsLayer::create(GraphicsLayerClient* client)
{
    return adoptPtr(new GraphicsLayerClutter(client));
}

GraphicsLayerClutter::GraphicsLayerClutter(GraphicsLayerClient* client)
    : GraphicsLayer(client)
    , m_contentsLayerPurpose(NoContentsLayer)
    , m_uncommittedChanges(0)
{
    // ClutterRectangle will be used to show the debug border.
    m_layer = graphicsLayerActorNewWithClient(LayerTypeWebLayer, this);
}

static gboolean idleDestroy(gpointer data)
{
    GRefPtr<ClutterActor> actor = adoptGRef(CLUTTER_ACTOR(data));
    ClutterActor* parent = clutter_actor_get_parent(actor.get());

    // We should remove child actors manually because the container of Clutter
    // seems to have a bug to remove its child actors when it is removed.
    if (GRAPHICS_LAYER_IS_ACTOR(GRAPHICS_LAYER_ACTOR(actor.get())))
        clutter_actor_remove_all_children(actor.get());

    if (parent)
        clutter_actor_remove_child(parent, actor.get());

    // FIXME: we should assert that the actor's ref count is 1 here, but some
    // of them are getting here with 2!
    // ASSERT((G_OBJECT(actor.get()))->ref_count == 1);

    return FALSE;
}

GraphicsLayerClutter::~GraphicsLayerClutter()
{
    if (graphicsLayerActorGetLayerType(m_layer.get()) == GraphicsLayerClutter::LayerTypeRootLayer)
        return;

    // Even though we call notifyFlushRequired to remove existing animations in removeAnimation(),
    // removeClutterAnimationFromLayer has been never reached since the root layer is destroyed.
    // It means that we haven't lost a change to remove actual animations from clutterActor. 
    // So, we call explictly updateAnimations once here to remove uncommitted animations. 
    if (m_uncommittedChanges & AnimationChanged)
        updateAnimations();

    willBeDestroyed();

    // We destroy the actors on an idle so that the main loop can run enough to
    // repaint the background that will replace the actor.
    if (m_layer) {
        graphicsLayerActorSetClient(m_layer.get(), 0);
        g_idle_add(idleDestroy, m_layer.leakRef());
    }

    if (m_structuralLayer) {
        graphicsLayerActorSetClient(m_structuralLayer.get(), 0);
        g_idle_add(idleDestroy, m_structuralLayer.leakRef());
    }

    if (m_contentsLayer) {
        graphicsLayerActorSetClient(m_contentsLayer.get(), 0);
        g_idle_add(idleDestroy, m_contentsLayer.leakRef());
    }
}

void GraphicsLayerClutter::setName(const String& name)
{
    String longName = String::format("Actor(%p) GraphicsLayer(%p) ", m_layer.get(), this) + name;
    GraphicsLayer::setName(longName);
    noteLayerPropertyChanged(NameChanged);
}

ClutterActor* GraphicsLayerClutter::platformLayer() const
{
    return CLUTTER_ACTOR(primaryLayer());
}

void GraphicsLayerClutter::setNeedsDisplay()
{
    FloatRect hugeRect(FloatPoint(), m_size);
    setNeedsDisplayInRect(hugeRect);
}

void GraphicsLayerClutter::setNeedsDisplayInRect(const FloatRect& r)
{
    if (!drawsContent())
        return;

    FloatRect rect(r);
    FloatRect layerBounds(FloatPoint(), m_size);
    rect.intersect(layerBounds);
    if (rect.isEmpty())
        return;

    const size_t maxDirtyRects = 32;

    for (size_t i = 0; i < m_dirtyRects.size(); ++i) {
        if (m_dirtyRects[i].contains(rect))
            return;
    }

    if (m_dirtyRects.size() < maxDirtyRects)
        m_dirtyRects.append(rect);
    else
        m_dirtyRects[0].unite(rect);

    noteLayerPropertyChanged(DirtyRectsChanged);
}

void GraphicsLayerClutter::setAnchorPoint(const FloatPoint3D& point)
{
    if (point == m_anchorPoint)
        return;

    GraphicsLayer::setAnchorPoint(point);
    noteLayerPropertyChanged(GeometryChanged);
}

void GraphicsLayerClutter::setOpacity(float opacity)
{
    float clampedOpacity = max(0.0f, min(opacity, 1.0f));
    if (clampedOpacity == m_opacity)
        return;

    GraphicsLayer::setOpacity(clampedOpacity);
    noteLayerPropertyChanged(OpacityChanged);
}

void GraphicsLayerClutter::setPosition(const FloatPoint& point)
{
    if (point == m_position)
        return;

    GraphicsLayer::setPosition(point);
    noteLayerPropertyChanged(GeometryChanged);
}

void GraphicsLayerClutter::setSize(const FloatSize& size)
{
    if (size == m_size)
        return;

    GraphicsLayer::setSize(size);
    noteLayerPropertyChanged(masksToBounds() ? GeometryChanged | MasksToBoundsChanged : GeometryChanged);
}
void GraphicsLayerClutter::setTransform(const TransformationMatrix& t)
{
    if (t == m_transform)
        return;

    GraphicsLayer::setTransform(t);
    noteLayerPropertyChanged(TransformChanged);
}

void GraphicsLayerClutter::moveOrCopyLayerAnimation(MoveOrCopy operation, const String& animationIdentifier, GraphicsLayerActor* fromLayer, GraphicsLayerActor* toLayer)
{
    RefPtr<PlatformClutterAnimation> anim = graphicsLayerActorGetAnimationForKey(fromLayer, animationIdentifier);
    if (!anim)
        return;

    switch (operation) {
    case Move:
        anim->removeAnimationForKey(fromLayer, animationIdentifier);
        anim->addAnimationForKey(toLayer, animationIdentifier);
        break;
    case Copy:
        anim->addAnimationForKey(toLayer, animationIdentifier);
        break;
    }
}

void GraphicsLayerClutter::moveOrCopyAnimations(MoveOrCopy operation, GraphicsLayerActor* fromLayer, GraphicsLayerActor* toLayer)
{
    // Look for running animations affecting this property.
    AnimationsMap::const_iterator end = m_runningAnimations.end();
    for (AnimationsMap::const_iterator it = m_runningAnimations.begin(); it != end; ++it) {
        const Vector<LayerPropertyAnimation>& propertyAnimations = it->value;
        size_t numAnimations = propertyAnimations.size();
        for (size_t i = 0; i < numAnimations; ++i) {
            const LayerPropertyAnimation& currAnimation = propertyAnimations[i];

            if (currAnimation.m_property == AnimatedPropertyWebkitTransform
                || currAnimation.m_property == AnimatedPropertyOpacity
                || currAnimation.m_property == AnimatedPropertyBackgroundColor)
                moveOrCopyLayerAnimation(operation, animationIdentifier(currAnimation.m_name, currAnimation.m_property, currAnimation.m_index), fromLayer, toLayer);
        }
    }
}

void GraphicsLayerClutter::setPreserves3D(bool preserves3D)
{
    if (preserves3D == m_preserves3D)
        return;

    GraphicsLayer::setPreserves3D(preserves3D);
    noteLayerPropertyChanged(Preserves3DChanged);
}

void GraphicsLayerClutter::setMasksToBounds(bool masksToBounds)
{
    if (masksToBounds == m_masksToBounds)
        return;

    GraphicsLayer::setMasksToBounds(masksToBounds);
    noteLayerPropertyChanged(MasksToBoundsChanged);
}

void GraphicsLayerClutter::setDrawsContent(bool drawsContent)
{
    if (drawsContent == m_drawsContent)
        return;

    GraphicsLayer::setDrawsContent(drawsContent);
    noteLayerPropertyChanged(DrawsContentChanged);
}


void GraphicsLayerClutter::setContentsToImage(Image* image)
{
    if (image) {
        RefPtr<cairo_surface_t> newImage = image->nativeImageForCurrentFrame();
        if (!newImage)
            return;

        m_pendingContentsImage = newImage;

        m_contentsLayerPurpose = ContentsLayerForImage;
        if (!m_contentsLayer)
            noteSublayersChanged();
    } else {
        m_pendingContentsImage = 0;
        m_contentsLayerPurpose = NoContentsLayer;
        if (m_contentsLayer)
            noteSublayersChanged();
    }

    noteLayerPropertyChanged(ContentsImageChanged);
}

void GraphicsLayerClutter::setContentsNeedsDisplay()
{
    noteLayerPropertyChanged(ContentsNeedsDisplay);
}

void GraphicsLayerClutter::setContentsRect(const IntRect& rect)
{
    if (rect == m_contentsRect)
        return;

    GraphicsLayer::setContentsRect(rect);
    noteLayerPropertyChanged(ContentsRectChanged);
}

void GraphicsLayerClutter::setParent(GraphicsLayer* childLayer)
{
    notImplemented();

    GraphicsLayer::setParent(childLayer);
}

bool GraphicsLayerClutter::setChildren(const Vector<GraphicsLayer*>& children)
{
    bool childrenChanged = GraphicsLayer::setChildren(children);
    if (childrenChanged)
        noteSublayersChanged();

    return childrenChanged;
}

void GraphicsLayerClutter::addChild(GraphicsLayer* childLayer)
{
    GraphicsLayer::addChild(childLayer);
    noteSublayersChanged();
}

void GraphicsLayerClutter::addChildAtIndex(GraphicsLayer* childLayer, int index)
{
    GraphicsLayer::addChildAtIndex(childLayer, index);
    noteSublayersChanged();
}

void GraphicsLayerClutter::addChildBelow(GraphicsLayer* childLayer, GraphicsLayer* sibling)
{
    GraphicsLayer::addChildBelow(childLayer, sibling);
    noteSublayersChanged();
}

void GraphicsLayerClutter::addChildAbove(GraphicsLayer* childLayer, GraphicsLayer* sibling)
{
    GraphicsLayer::addChildAbove(childLayer, sibling);
    noteSublayersChanged();
}

bool GraphicsLayerClutter::replaceChild(GraphicsLayer* oldChild, GraphicsLayer* newChild)
{
    if (GraphicsLayer::replaceChild(oldChild, newChild)) {
        noteSublayersChanged();
        return true;
    }
    return false;
}

void GraphicsLayerClutter::removeFromParent()
{
    if (m_parent)
        static_cast<GraphicsLayerClutter*>(m_parent)->noteSublayersChanged();
    GraphicsLayer::removeFromParent();
}

void GraphicsLayerClutter::platformClutterLayerPaintContents(GraphicsContext& context, const IntRect& clip)
{
    paintGraphicsLayerContents(context, clip);
}

void GraphicsLayerClutter::platformClutterLayerAnimationStarted(double startTime)
{
    if (m_client)
        m_client->notifyAnimationStarted(this, startTime);
}

void GraphicsLayerClutter::repaintLayerDirtyRects()
{
    if (!m_dirtyRects.size())
        return;

    for (size_t i = 0; i < m_dirtyRects.size(); ++i)
        graphicsLayerActorInvalidateRectangle(m_layer.get(), m_dirtyRects[i]);

    m_dirtyRects.clear();
}

void GraphicsLayerClutter::updateOpacityOnLayer()
{
    clutter_actor_set_opacity(CLUTTER_ACTOR(primaryLayer()), static_cast<guint8>(roundf(m_opacity * 255)));
}

void GraphicsLayerClutter::updateAnimations()
{
    if (m_animationsToProcess.size()) {
        AnimationsToProcessMap::const_iterator end = m_animationsToProcess.end();
        for (AnimationsToProcessMap::const_iterator it = m_animationsToProcess.begin(); it != end; ++it) {
            const String& currAnimationName = it->key;
            AnimationsMap::iterator animationIt = m_runningAnimations.find(currAnimationName);
            if (animationIt == m_runningAnimations.end())
                continue;

            const AnimationProcessingAction& processingInfo = it->value;
            const Vector<LayerPropertyAnimation>& animations = animationIt->value;
            for (size_t i = 0; i < animations.size(); ++i) {
                const LayerPropertyAnimation& currAnimation = animations[i];
                switch (processingInfo.action) {
                case Remove:
                    removeClutterAnimationFromLayer(currAnimation.m_property, currAnimationName, currAnimation.m_index);
                    break;
                case Pause:
                    pauseClutterAnimationOnLayer(currAnimation.m_property, currAnimationName, currAnimation.m_index, processingInfo.timeOffset);
                    break;
                }
            }

            if (processingInfo.action == Remove)
                m_runningAnimations.remove(currAnimationName);
        }

        m_animationsToProcess.clear();
    }

    size_t numAnimations;
    if ((numAnimations = m_uncomittedAnimations.size())) {
        for (size_t i = 0; i < numAnimations; ++i) {
            const LayerPropertyAnimation& pendingAnimation = m_uncomittedAnimations[i];
            setAnimationOnLayer(pendingAnimation.m_animation.get(), pendingAnimation.m_property, pendingAnimation.m_name, pendingAnimation.m_index, pendingAnimation.m_timeOffset);

            AnimationsMap::iterator it = m_runningAnimations.find(pendingAnimation.m_name);
            if (it == m_runningAnimations.end()) {
                Vector<LayerPropertyAnimation> animations;
                animations.append(pendingAnimation);
                m_runningAnimations.add(pendingAnimation.m_name, animations);
            } else {
                Vector<LayerPropertyAnimation>& animations = it->value;
                animations.append(pendingAnimation);
            }
        }

        m_uncomittedAnimations.clear();
    }
}

FloatPoint GraphicsLayerClutter::computePositionRelativeToBase(float& pageScale) const
{
    pageScale = 1;

    FloatPoint offset;
    for (const GraphicsLayer* currLayer = this; currLayer; currLayer = currLayer->parent()) {
        if (currLayer->appliesPageScale()) {
            if (currLayer->client())
                pageScale = currLayer->pageScaleFactor();
            return offset;
        }

        offset += currLayer->position();
    }

    return FloatPoint();
}

// called from void RenderLayerCompositor::flushPendingLayerChanges
void GraphicsLayerClutter::flushCompositingState(const FloatRect& clipRect)
{
    TransformState state(TransformState::UnapplyInverseTransformDirection, FloatQuad(clipRect));
    recursiveCommitChanges(CommitState(), state);
}

void GraphicsLayerClutter::recursiveCommitChanges(const CommitState& commitState, const TransformState& state, float pageScaleFactor, const FloatPoint& positionRelativeToBase, bool affectedByPageScale)
{
    if (appliesPageScale()) {
        pageScaleFactor = this->pageScaleFactor();
        affectedByPageScale = true;
    }

    // Accumulate an offset from the ancestral pixel-aligned layer.
    FloatPoint baseRelativePosition = positionRelativeToBase;
    if (affectedByPageScale)
        baseRelativePosition += m_position;

    commitLayerChangesBeforeSublayers(pageScaleFactor, baseRelativePosition);

    const Vector<GraphicsLayer*>& childLayers = children();
    size_t numChildren = childLayers.size();

    for (size_t i = 0; i < numChildren; ++i) {
        GraphicsLayerClutter* currentChild = static_cast<GraphicsLayerClutter*>(childLayers[i]);
        currentChild->recursiveCommitChanges(commitState, state, pageScaleFactor, baseRelativePosition, affectedByPageScale);
    }

    commitLayerChangesAfterSublayers();
}

void GraphicsLayerClutter::flushCompositingStateForThisLayerOnly()
{
    float pageScaleFactor;
    FloatPoint offset = computePositionRelativeToBase(pageScaleFactor);
    commitLayerChangesBeforeSublayers(pageScaleFactor, offset);
    commitLayerChangesAfterSublayers();
}

void GraphicsLayerClutter::commitLayerChangesAfterSublayers()
{
    if (!m_uncommittedChanges)
        return;

    if (m_uncommittedChanges & ChildrenChanged)
        updateSublayerList();

    m_uncommittedChanges = NoChange;
}
void GraphicsLayerClutter::noteSublayersChanged()
{
    noteLayerPropertyChanged(ChildrenChanged);
}

void GraphicsLayerClutter::noteLayerPropertyChanged(LayerChangeFlags flags)
{
    if (!m_uncommittedChanges && m_client)
        m_client->notifyFlushRequired(this); // call RenderLayerBacking::notifyFlushRequired

    m_uncommittedChanges |= flags;
}

void GraphicsLayerClutter::commitLayerChangesBeforeSublayers(float pageScaleFactor, const FloatPoint& positionRelativeToBase)
{
    if (!m_uncommittedChanges)
        return;

    // Need to handle Preserves3DChanged first, because it affects which layers subsequent properties are applied to
    if (m_uncommittedChanges & Preserves3DChanged)
        updateStructuralLayer();

    if (m_uncommittedChanges & NameChanged)
        updateLayerNames();

    if (m_uncommittedChanges & ContentsImageChanged) // Needs to happen before ChildrenChanged
        updateContentsImage();

    if (m_uncommittedChanges & ChildrenChanged)
        updateSublayerList();

    if (m_uncommittedChanges & GeometryChanged)
        updateGeometry(pageScaleFactor, positionRelativeToBase);

    if (m_uncommittedChanges & DrawsContentChanged)
        updateLayerDrawsContent(pageScaleFactor, positionRelativeToBase);

    if (m_uncommittedChanges & NameChanged)
        updateLayerNames();

    if (m_uncommittedChanges & TransformChanged)
        updateTransform();

    if (m_uncommittedChanges & MasksToBoundsChanged)
        updateMasksToBounds();

    if (m_uncommittedChanges & OpacityChanged)
        updateOpacityOnLayer();

    if (m_uncommittedChanges & AnimationChanged)
        updateAnimations();

    if (m_uncommittedChanges & DirtyRectsChanged)
        repaintLayerDirtyRects();

    if (m_uncommittedChanges & ContentsRectChanged)
        updateContentsRect();

    if (m_uncommittedChanges & ContentsNeedsDisplay)
        updateContentsNeedsDisplay();

    if (m_uncommittedChanges & ChildrenChanged) {
        updateSublayerList();
        // Sublayers may set this flag again, so clear it to avoid always updating sublayers in commitLayerChangesAfterSublayers().
        m_uncommittedChanges &= ~ChildrenChanged;
    }
}

void GraphicsLayerClutter::setupContentsLayer(GraphicsLayerActor* contentsLayer)
{
    graphicsLayerActorSetMasksToBounds(contentsLayer, true);
    graphicsLayerActorSetAnchorPoint(contentsLayer, 0.0, 0.0, 0.0);
}

void GraphicsLayerClutter::updateContentsImage()
{
    if (m_pendingContentsImage) {
        if (!m_contentsLayer) {
            m_contentsLayer = graphicsLayerActorNewWithClient(LayerTypeLayer, this);
            clutter_actor_set_name(CLUTTER_ACTOR(m_contentsLayer.get()), "Image Layer");
            setupContentsLayer(m_contentsLayer.get());
        }

        graphicsLayerActorSetSurface(GRAPHICS_LAYER_ACTOR(m_contentsLayer.get()), m_pendingContentsImage.get());
        m_pendingContentsImage = 0;

        updateContentsRect();
    } else {
        // No image.
        // m_contentsLayer will be removed via updateSublayerList.
        m_contentsLayer = 0;
    }
}

void GraphicsLayerClutter::updateContentsNeedsDisplay()
{
    if (m_contentsLayer)
        graphicsLayerActorInvalidateRectangle(m_contentsLayer.get(), FloatRect(FloatPoint(0, 0), size()));
}

void GraphicsLayerClutter::updateContentsRect()
{
    if (!m_contentsLayer)
        return;

    FloatPoint point(m_contentsRect.x(), m_contentsRect.y());
    FloatRect rect(0, 0, m_contentsRect.width(), m_contentsRect.height());

    clutter_actor_set_position(CLUTTER_ACTOR(m_contentsLayer.get()), point.x(), point.y());
    clutter_actor_set_size(CLUTTER_ACTOR(m_contentsLayer.get()), rect.width(), rect.height());
}


void GraphicsLayerClutter::updateGeometry(float pageScaleFactor, const FloatPoint& positionRelativeToBase)
{
    // FIXME: Need to support page scaling.
    if (m_structuralLayer) {
        clutter_actor_set_position(CLUTTER_ACTOR(m_structuralLayer.get()), m_position.x(), m_position.y());
        clutter_actor_set_size(CLUTTER_ACTOR(m_structuralLayer.get()), m_size.width(), m_size.height());
        graphicsLayerActorSetAnchorPoint(m_structuralLayer.get(), m_anchorPoint.x(), m_anchorPoint.y(), m_anchorPoint.z());
    }

    clutter_actor_set_position(CLUTTER_ACTOR(m_layer.get()), m_position.x(), m_position.y());
    clutter_actor_set_size(CLUTTER_ACTOR(m_layer.get()), m_size.width(), m_size.height());
    graphicsLayerActorSetAnchorPoint(m_layer.get(), m_anchorPoint.x(), m_anchorPoint.y(), m_anchorPoint.z());
}

// Each GraphicsLayer has the corresponding layer in the platform port.
// So whenever the list of child layer changes, the list of GraphicsLayerActor should be updated accordingly.
void GraphicsLayerClutter::updateSublayerList()
{
    GraphicsLayerActorList structuralLayerChildren;
    GraphicsLayerActorList primaryLayerChildren;

    GraphicsLayerActorList& childListForSublayers = m_structuralLayer ? structuralLayerChildren : primaryLayerChildren;

    if (m_structuralLayer)
        structuralLayerChildren.append(m_layer);

    if (m_contentsLayer)
        primaryLayerChildren.append(m_contentsLayer);

    const Vector<GraphicsLayer*>& childLayers = children();
    size_t numChildren = childLayers.size();
    for (size_t i = 0; i < numChildren; ++i) {
        GraphicsLayerClutter* currentChild = static_cast<GraphicsLayerClutter*>(childLayers[i]);
        GraphicsLayerActor* childLayer = currentChild->layerForSuperlayer();
        ASSERT(GRAPHICS_LAYER_IS_ACTOR(childLayer));
        childListForSublayers.append(childLayer);

        // The child layer only preserves 3D if either itself or its parent has preserves3D set.
        graphicsLayerActorSetFlatten(childLayer, !(preserves3D() || currentChild->preserves3D()));
    }

    if (m_structuralLayer)
        graphicsLayerActorSetSublayers(m_structuralLayer.get(), structuralLayerChildren);

    graphicsLayerActorSetSublayers(m_layer.get(), primaryLayerChildren);
}

void GraphicsLayerClutter::updateLayerNames()
{
    clutter_actor_set_name(CLUTTER_ACTOR(m_layer.get()), name().utf8().data());
}

void GraphicsLayerClutter::updateTransform()
{
    CoglMatrix matrix = m_transform;
    clutter_actor_set_transform(CLUTTER_ACTOR(primaryLayer()), &matrix);
}

void GraphicsLayerClutter::updateMasksToBounds()
{
    graphicsLayerActorSetMasksToBounds(m_layer.get(), m_masksToBounds);
}

void GraphicsLayerClutter::updateStructuralLayer()
{
    ensureStructuralLayer(structuralLayerPurpose());
}

void GraphicsLayerClutter::ensureStructuralLayer(StructuralLayerPurpose purpose)
{
    const LayerChangeFlags structuralLayerChangeFlags = NameChanged
        | GeometryChanged
        | TransformChanged
        | ChildrenTransformChanged
        | ChildrenChanged
        | BackfaceVisibilityChanged
        | OpacityChanged;

    if (purpose == NoStructuralLayer) {
        if (m_structuralLayer) {
            // Replace the transformLayer in the parent with this layer.
            graphicsLayerActorRemoveFromSuperLayer(m_layer.get());

            // If m_layer doesn't have a parent, it means it's the root layer and
            // is likely hosted by something that is not expecting to be changed
            ClutterActor* parentActor = clutter_actor_get_parent(CLUTTER_ACTOR(m_structuralLayer.get()));
            ASSERT(parentActor);
            clutter_actor_replace_child(parentActor, CLUTTER_ACTOR(m_structuralLayer.get()), CLUTTER_ACTOR(m_layer.get()));

            moveOrCopyAnimations(Move, m_structuralLayer.get(), m_layer.get());

            // Release the structural layer.
            m_structuralLayer = 0;

            m_uncommittedChanges |= structuralLayerChangeFlags;
        }
        return;
    }

    bool structuralLayerChanged = false;
    if (purpose == StructuralLayerForPreserves3D) {
        if (m_structuralLayer && (graphicsLayerActorGetLayerType(m_structuralLayer.get()) != GraphicsLayerClutter::LayerTypeTransformLayer))
            m_structuralLayer = 0;

        if (!m_structuralLayer) {
            m_structuralLayer = graphicsLayerActorNewWithClient(GraphicsLayerClutter::LayerTypeTransformLayer, this);
            structuralLayerChanged = true;
        }
    } else {
        if (m_structuralLayer && (graphicsLayerActorGetLayerType(m_structuralLayer.get()) != GraphicsLayerClutter::LayerTypeLayer))
            m_structuralLayer = 0;

        if (!m_structuralLayer) {
            m_structuralLayer = graphicsLayerActorNewWithClient(GraphicsLayerClutter::LayerTypeLayer, this);
            structuralLayerChanged = true;
        }
    }

    if (!structuralLayerChanged)
        return;

    m_uncommittedChanges |= structuralLayerChangeFlags;

    // We've changed the layer that our parent added to its sublayer list, so tell it to update
    // sublayers again in its commitLayerChangesAfterSublayers().
    static_cast<GraphicsLayerClutter*>(parent())->noteSublayersChanged();

    // Set properties of m_layer to their default values, since these are expressed on on the structural layer.
    FloatPoint point(0, 0);
    FloatPoint3D anchorPoint(0.5f, 0.5f, 0);
    clutter_actor_set_position(CLUTTER_ACTOR(m_layer.get()), point.x(), point.y());
    graphicsLayerActorSetAnchorPoint(m_layer.get(), anchorPoint.x(), anchorPoint.y(), anchorPoint.z());

    CoglMatrix matrix = TransformationMatrix();
    clutter_actor_set_transform(CLUTTER_ACTOR(m_layer.get()), &matrix);

    clutter_actor_set_opacity(CLUTTER_ACTOR(m_layer.get()), 255);

    moveOrCopyAnimations(Move, m_layer.get(), m_structuralLayer.get());
}

GraphicsLayerClutter::StructuralLayerPurpose GraphicsLayerClutter::structuralLayerPurpose() const
{
    if (preserves3D())
        return StructuralLayerForPreserves3D;

    return NoStructuralLayer;
}

void GraphicsLayerClutter::updateLayerDrawsContent(float pageScaleFactor, const FloatPoint& positionRelativeToBase)
{
    graphicsLayerActorSetDrawsContent(m_layer.get(), m_drawsContent);

    if (m_drawsContent)
        setNeedsDisplay();
    else
        graphicsLayerActorSetSurface(m_layer.get(), 0);

    updateDebugIndicators();
}

void GraphicsLayerClutter::setupAnimation(PlatformClutterAnimation* propertyAnim, const Animation* anim, bool additive)
{
    double duration = anim->duration();
    if (duration <= 0)
        duration = cAnimationAlmostZeroDuration;

    float repeatCount = anim->iterationCount();
    if (repeatCount == Animation::IterationCountInfinite)
        repeatCount = numeric_limits<float>::max();
    else if (anim->direction() == Animation::AnimationDirectionAlternate || anim->direction() == Animation::AnimationDirectionAlternateReverse)
        repeatCount /= 2;

    PlatformClutterAnimation::FillModeType fillMode = PlatformClutterAnimation::NoFillMode;
    switch (anim->fillMode()) {
    case AnimationFillModeNone:
        fillMode = PlatformClutterAnimation::Forwards; // Use "forwards" rather than "removed" because the style system will remove the animation when it is finished. This avoids a flash.
        break;
    case AnimationFillModeBackwards:
        fillMode = PlatformClutterAnimation::Both; // Use "both" rather than "backwards" because the style system will remove the animation when it is finished. This avoids a flash.
        break;
    case AnimationFillModeForwards:
        fillMode = PlatformClutterAnimation::Forwards;
        break;
    case AnimationFillModeBoth:
        fillMode = PlatformClutterAnimation::Both;
        break;
    }

    propertyAnim->setDuration(duration);
    propertyAnim->setRepeatCount(repeatCount);
    propertyAnim->setAutoreverses(anim->direction() == Animation::AnimationDirectionAlternate || anim->direction() == Animation::AnimationDirectionAlternateReverse);
    propertyAnim->setRemovedOnCompletion(false);
    propertyAnim->setAdditive(additive);
    propertyAnim->setFillMode(fillMode);
}

const TimingFunction* GraphicsLayerClutter::timingFunctionForAnimationValue(const AnimationValue& animValue, const Animation& anim)
{
    if (animValue.timingFunction())
        return animValue.timingFunction();
    if (anim.isTimingFunctionSet())
        return anim.timingFunction().get();

    return CubicBezierTimingFunction::defaultTimingFunction();
}

PassRefPtr<PlatformClutterAnimation> GraphicsLayerClutter::createBasicAnimation(const Animation* anim, const String& keyPath, bool additive)
{
    RefPtr<PlatformClutterAnimation> basicAnim = PlatformClutterAnimation::create(PlatformClutterAnimation::Basic, keyPath);
    setupAnimation(basicAnim.get(), anim, additive);
    return basicAnim;
}

PassRefPtr<PlatformClutterAnimation>GraphicsLayerClutter::createKeyframeAnimation(const Animation* anim, const String& keyPath, bool additive)
{
    RefPtr<PlatformClutterAnimation> keyframeAnim = PlatformClutterAnimation::create(PlatformClutterAnimation::Keyframe, keyPath);
    setupAnimation(keyframeAnim.get(), anim, additive);
    return keyframeAnim;
}

bool GraphicsLayerClutter::setTransformAnimationKeyframes(const KeyframeValueList& valueList, const Animation* animation, PlatformClutterAnimation* keyframeAnim, int functionIndex, TransformOperation::OperationType transformOpType, bool isMatrixAnimation, const IntSize& boxSize)
{
    Vector<float> keyTimes;
    Vector<float> floatValues;
    Vector<FloatPoint3D> floatPoint3DValues;
    Vector<TransformationMatrix> transformationMatrixValues;
    Vector<const TimingFunction*> timingFunctions;

    bool forwards = animation->directionIsForwards();

    for (unsigned i = 0; i < valueList.size(); ++i) {
        unsigned index = forwards ? i : (valueList.size() - i - 1);
        const TransformAnimationValue& curValue = static_cast<const TransformAnimationValue&>(valueList.at(index));
        keyTimes.append(forwards ? curValue.keyTime() : (1 - curValue.keyTime()));

        if (isMatrixAnimation) {
            TransformationMatrix transform;
            curValue.value().apply(boxSize, transform);

            // FIXME: In CoreAnimation case, if any matrix is singular, CA won't animate it correctly.
            // But I'm not sure clutter also does. Check it later, and then decide
            // whether removing following lines or not.
            if (!transform.isInvertible())
                return false;

            transformationMatrixValues.append(transform);
        } else {
            const TransformOperation* transformOp = curValue.value().at(functionIndex);
            if (isTransformTypeNumber(transformOpType)) {
                float value;
                getTransformFunctionValue(transformOp, transformOpType, boxSize, value);
                floatValues.append(value);
            } else if (isTransformTypeFloatPoint3D(transformOpType)) {
                FloatPoint3D value;
                getTransformFunctionValue(transformOp, transformOpType, boxSize, value);
                floatPoint3DValues.append(value);
            } else {
                TransformationMatrix value;
                getTransformFunctionValue(transformOp, transformOpType, boxSize, value);
                transformationMatrixValues.append(value);
            }
        }

        if (i < (valueList.size() - 1))
            timingFunctions.append(timingFunctionForAnimationValue(forwards ? curValue : valueList.at(index - 1), *animation));
    }

    keyframeAnim->setKeyTimes(keyTimes);

    if (isTransformTypeNumber(transformOpType))
        keyframeAnim->setValues(floatValues);
    else if (isTransformTypeFloatPoint3D(transformOpType))
        keyframeAnim->setValues(floatPoint3DValues);
    else
        keyframeAnim->setValues(transformationMatrixValues);

    keyframeAnim->setTimingFunctions(timingFunctions, !forwards);

    PlatformClutterAnimation::ValueFunctionType valueFunction = getValueFunctionNameForTransformOperation(transformOpType);
    if (valueFunction != PlatformClutterAnimation::NoValueFunction)
        keyframeAnim->setValueFunction(valueFunction);

    return true;
}

bool GraphicsLayerClutter::setTransformAnimationEndpoints(const KeyframeValueList& valueList, const Animation* animation, PlatformClutterAnimation* basicAnim, int functionIndex, TransformOperation::OperationType transformOpType, bool isMatrixAnimation, const IntSize& boxSize)
{
    ASSERT(valueList.size() == 2);

    bool forwards = animation->directionIsForwards();

    unsigned fromIndex = !forwards;
    unsigned toIndex = forwards;

    const TransformAnimationValue& startValue = static_cast<const TransformAnimationValue&>(valueList.at(fromIndex));
    const TransformAnimationValue& endValue = static_cast<const TransformAnimationValue&>(valueList.at(toIndex));

    if (isMatrixAnimation) {
        TransformationMatrix fromTransform, toTransform;
        startValue.value().apply(boxSize, fromTransform);
        endValue.value().apply(boxSize, toTransform);

        // FIXME: If any matrix is singular, CA won't animate it correctly.
        // So fall back to software animation, But it's not sure in clutter case.
        // We need to investigate it more.
        if (!fromTransform.isInvertible() || !toTransform.isInvertible())
            return false;

        basicAnim->setFromValue(fromTransform);
        basicAnim->setToValue(toTransform);
    } else {
        if (isTransformTypeNumber(transformOpType)) {
            float fromValue;
            getTransformFunctionValue(startValue.value().at(functionIndex), transformOpType, boxSize, fromValue);
            basicAnim->setFromValue(fromValue);

            float toValue;
            getTransformFunctionValue(endValue.value().at(functionIndex), transformOpType, boxSize, toValue);
            basicAnim->setToValue(toValue);
        } else if (isTransformTypeFloatPoint3D(transformOpType)) {
            FloatPoint3D fromValue;
            getTransformFunctionValue(startValue.value().at(functionIndex), transformOpType, boxSize, fromValue);
            basicAnim->setFromValue(fromValue);

            FloatPoint3D toValue;
            getTransformFunctionValue(endValue.value().at(functionIndex), transformOpType, boxSize, toValue);
            basicAnim->setToValue(toValue);
        } else {
            TransformationMatrix fromValue;
            getTransformFunctionValue(startValue.value().at(functionIndex), transformOpType, boxSize, fromValue);
            basicAnim->setFromValue(fromValue);

            TransformationMatrix toValue;
            getTransformFunctionValue(endValue.value().at(functionIndex), transformOpType, boxSize, toValue);
            basicAnim->setToValue(toValue);
        }
    }

    // This codepath is used for 2-keyframe animations, so we still need to look in the start
    // for a timing function. Even in the reversing animation case, the first keyframe provides the timing function.
    const TimingFunction* timingFunction = timingFunctionForAnimationValue(valueList.at(0), *animation);
    basicAnim->setTimingFunction(timingFunction, !forwards);

    PlatformClutterAnimation::ValueFunctionType valueFunction = getValueFunctionNameForTransformOperation(transformOpType);
    if (valueFunction != PlatformClutterAnimation::NoValueFunction)
        basicAnim->setValueFunction(valueFunction);

    return true;
}

bool GraphicsLayerClutter::appendToUncommittedAnimations(const KeyframeValueList& valueList, const TransformOperations* operations, const Animation* animation, const String& animationName, const IntSize& boxSize, int animationIndex, double timeOffset, bool isMatrixAnimation)
{
    TransformOperation::OperationType transformOp = isMatrixAnimation ? TransformOperation::MATRIX_3D : operations->operations().at(animationIndex)->getOperationType();
    bool additive = animationIndex > 0;
    bool isKeyframe = valueList.size() > 2;

    RefPtr<PlatformClutterAnimation> clutterAnimation;
    bool validMatrices = true;
    if (isKeyframe) {
        clutterAnimation = createKeyframeAnimation(animation, propertyIdToString(valueList.property()), additive);
        validMatrices = setTransformAnimationKeyframes(valueList, animation, clutterAnimation.get(), animationIndex, transformOp, isMatrixAnimation, boxSize);
    } else {
        clutterAnimation = createBasicAnimation(animation, propertyIdToString(valueList.property()), additive);
        validMatrices = setTransformAnimationEndpoints(valueList, animation, clutterAnimation.get(), animationIndex, transformOp, isMatrixAnimation, boxSize);
    }

    if (!validMatrices)
        return false;

    m_uncomittedAnimations.append(LayerPropertyAnimation(clutterAnimation, animationName, valueList.property(), animationIndex, timeOffset));
    return true;
}

bool GraphicsLayerClutter::createTransformAnimationsFromKeyframes(const KeyframeValueList& valueList, const Animation* animation, const String& animationName, double timeOffset, const IntSize& boxSize)
{
    ASSERT(valueList.property() == AnimatedPropertyWebkitTransform);

    bool hasBigRotation;
    int listIndex = validateTransformOperations(valueList, hasBigRotation);
    const TransformOperations* operations = (listIndex >= 0) ? &static_cast<const TransformAnimationValue&>(valueList.at(listIndex)).value() : 0;

    // We need to fall back to software animation if we don't have setValueFunction:, and
    // we would need to animate each incoming transform function separately. This is the
    // case if we have a rotation >= 180 or we have more than one transform function.
    if ((hasBigRotation || (operations && operations->size() > 1)) && !PlatformClutterAnimation::supportsValueFunction())
        return false;

    bool validMatrices = true;

    // If function lists don't match we do a matrix animation, otherwise we do a component hardware animation.
    // Also, we can't do component animation unless we have valueFunction, so we need to do matrix animation
    // if that's not true as well.
    bool isMatrixAnimation = listIndex < 0 || !PlatformClutterAnimation::supportsValueFunction() || (operations->size() >= 2 && !PlatformClutterAnimation::supportsAdditiveValueFunction());
    int numAnimations = isMatrixAnimation ? 1 : operations->size();

    for (int animationIndex = 0; animationIndex < numAnimations; ++animationIndex) {
        if (!appendToUncommittedAnimations(valueList, operations, animation, animationName, boxSize, animationIndex, timeOffset, isMatrixAnimation)) {
            validMatrices = false;
            break;
        }
    }

    return validMatrices;
}

bool GraphicsLayerClutter::createAnimationFromKeyframes(const KeyframeValueList& valueList, const Animation* animation, const String& animationName, double timeOffset)
{
    ASSERT(valueList.property() != AnimatedPropertyWebkitTransform);

    bool isKeyframe = valueList.size() > 2;
    bool valuesOK;

    bool additive = false;
    int animationIndex = 0;

    RefPtr<PlatformClutterAnimation> clutterAnimation;

    if (isKeyframe) {
        clutterAnimation = createKeyframeAnimation(animation, propertyIdToString(valueList.property()), additive);
        valuesOK = setAnimationKeyframes(valueList, animation, clutterAnimation.get());
    } else {
        clutterAnimation = createBasicAnimation(animation, propertyIdToString(valueList.property()), additive);
        valuesOK = setAnimationEndpoints(valueList, animation, clutterAnimation.get());
    }

    if (!valuesOK)
        return false;

    m_uncomittedAnimations.append(LayerPropertyAnimation(clutterAnimation, animationName, valueList.property(), animationIndex, timeOffset));

    return true;
}

bool GraphicsLayerClutter::addAnimation(const KeyframeValueList& valueList, const IntSize& boxSize, const Animation* anim, const String& animationName, double timeOffset)
{
    ASSERT(!animationName.isEmpty());

    if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2)
        return false;

    // FIXME: ClutterTimeline seems to support steps timing function. So we need to improve here.
    // See http://developer.gnome.org/clutter/stable/ClutterTimeline.html#ClutterAnimationMode
    if (animationHasStepsTimingFunction(valueList, anim))
        return false;

    bool createdAnimations = false;
    if (valueList.property() == AnimatedPropertyWebkitTransform)
        createdAnimations = createTransformAnimationsFromKeyframes(valueList, anim, animationName, timeOffset, boxSize);
    else
        createdAnimations = createAnimationFromKeyframes(valueList, anim, animationName, timeOffset);

    if (createdAnimations)
        noteLayerPropertyChanged(AnimationChanged);

    return createdAnimations;
}

void GraphicsLayerClutter::removeAnimation(const String& animationName)
{
    if (!animationIsRunning(animationName))
        return;

    m_animationsToProcess.add(animationName, AnimationProcessingAction(Remove));
    noteLayerPropertyChanged(AnimationChanged);
}

bool GraphicsLayerClutter::removeClutterAnimationFromLayer(AnimatedPropertyID property, const String& animationName, int index)
{
    GraphicsLayerActor* layer = animatedLayer(property);

    String animationID = animationIdentifier(animationName, property, index);

    PlatformClutterAnimation* existingAnimation = graphicsLayerActorGetAnimationForKey(layer, animationID);
    if (!existingAnimation)
        return false;

    existingAnimation->removeAnimationForKey(layer, animationID);
    return true;
}

void GraphicsLayerClutter::pauseClutterAnimationOnLayer(AnimatedPropertyID property, const String& animationName, int index, double timeOffset)
{
    notImplemented();
}

void GraphicsLayerClutter::setAnimationOnLayer(PlatformClutterAnimation* clutterAnim, AnimatedPropertyID property, const String& animationName, int index, double timeOffset)
{
    GraphicsLayerActor* layer = animatedLayer(property);

    if (timeOffset)
        clutterAnim->setBeginTime(g_get_real_time() - timeOffset);

    String animationID = animationIdentifier(animationName, property, index);

    PlatformClutterAnimation* existingAnimation = graphicsLayerActorGetAnimationForKey(layer, animationID);
    if (existingAnimation)
        existingAnimation->removeAnimationForKey(layer, animationID);

    clutterAnim->addAnimationForKey(layer, animationID);
}

bool GraphicsLayerClutter::setAnimationEndpoints(const KeyframeValueList& valueList, const Animation* animation, PlatformClutterAnimation* basicAnim)
{
    bool forwards = animation->directionIsForwards();

    unsigned fromIndex = !forwards;
    unsigned toIndex = forwards;

    switch (valueList.property()) {
    case AnimatedPropertyOpacity: {
        basicAnim->setFromValue(static_cast<const FloatAnimationValue&>(valueList.at(fromIndex)).value());
        basicAnim->setToValue(static_cast<const FloatAnimationValue&>(valueList.at(toIndex)).value());
        break;
    }
    default:
        ASSERT_NOT_REACHED(); // we don't animate color yet
        break;
    }

    // This codepath is used for 2-keyframe animations, so we still need to look in the start
    // for a timing function. Even in the reversing animation case, the first keyframe provides the timing function.
    const TimingFunction* timingFunction = timingFunctionForAnimationValue(valueList.at(0), *animation);
    if (timingFunction)
        basicAnim->setTimingFunction(timingFunction, !forwards);

    return true;
}

bool GraphicsLayerClutter::setAnimationKeyframes(const KeyframeValueList& valueList, const Animation* animation, PlatformClutterAnimation* keyframeAnim)
{
    Vector<float> keyTimes;
    Vector<float> values;
    Vector<const TimingFunction*> timingFunctions;

    bool forwards = animation->directionIsForwards();

    for (unsigned i = 0; i < valueList.size(); ++i) {
        unsigned index = forwards ? i : (valueList.size() - i - 1);
        const AnimationValue& curValue = valueList.at(index);
        keyTimes.append(forwards ? curValue.keyTime() : (1 - curValue.keyTime()));

        switch (valueList.property()) {
        case AnimatedPropertyOpacity: {
            const FloatAnimationValue& floatValue = static_cast<const FloatAnimationValue&>(curValue);
            values.append(floatValue.value());
            break;
        }
        default:
            ASSERT_NOT_REACHED(); // we don't animate color yet
            break;
        }

        if (i < (valueList.size() - 1))
            timingFunctions.append(timingFunctionForAnimationValue(forwards ? curValue : valueList.at(index - 1), *animation));
    }

    keyframeAnim->setKeyTimes(keyTimes);
    keyframeAnim->setValues(values);
    keyframeAnim->setTimingFunctions(timingFunctions, !forwards);

    return true;
}

GraphicsLayerActor* GraphicsLayerClutter::layerForSuperlayer() const
{
    return m_structuralLayer ? m_structuralLayer.get() : m_layer.get();
}

GraphicsLayerActor* GraphicsLayerClutter::animatedLayer(AnimatedPropertyID property) const
{
    return primaryLayer();
}

} // namespace WebCore

#endif // USE(ACCELERATED_COMPOSITING)