GraphicsLayerCA.mm   [plain text]


/*
 * Copyright (C) 2009 Apple Inc. 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. 
 */

#import "config.h"

#if USE(ACCELERATED_COMPOSITING)

#import "GraphicsLayerCA.h"

#import "Animation.h"
#import "BlockExceptions.h"
#import "CString.h"
#import "FloatConversion.h"
#import "FloatRect.h"
#import "Image.h"
#import "PlatformString.h"
#import <QuartzCore/QuartzCore.h>
#import "RotateTransformOperation.h"
#import "ScaleTransformOperation.h"
#import "SystemTime.h"
#import "TranslateTransformOperation.h"
#import "WebLayer.h"
#import "WebTiledLayer.h"
#import <wtf/CurrentTime.h>
#import <wtf/UnusedParam.h>

using namespace std;

#define HAVE_MODERN_QUARTZCORE (!defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD))

namespace WebCore {

// The threshold width or height above which a tiled layer will be used. This should be
// large enough to avoid tiled layers for most GraphicsLayers, but less than the OpenGL
// texture size limit on all supported hardware.
static const int cMaxPixelDimension = 2000;

// The width and height of a single tile in a tiled layer. Should be large enough to
// avoid lots of small tiles (and therefore lots of drawing callbacks), but small enough
// to keep the overall tile cost low.
static const int cTiledLayerTileSize = 512;

// If we send a duration of 0 to CA, then it will use the default duration
// of 250ms. So send a very small value instead.
static const float cAnimationAlmostZeroDuration = 1e-3f;

// CACurrentMediaTime() is a time since boot. These methods convert between that and
// WebCore time, which is system time (UTC).
static CFTimeInterval currentTimeToMediaTime(double t)
{
    return CACurrentMediaTime() + t - WTF::currentTime();
}

static double mediaTimeToCurrentTime(CFTimeInterval t)
{
    return WTF::currentTime() + t - CACurrentMediaTime();
}

} // namespace WebCore

static NSString* const WebAnimationCSSPropertyKey = @"GraphicsLayerCA_property";

@interface WebAnimationDelegate : NSObject {
    WebCore::GraphicsLayerCA* m_graphicsLayer;
}

- (void)animationDidStart:(CAAnimation *)anim;
- (WebCore::GraphicsLayerCA*)graphicsLayer;
- (void)setLayer:(WebCore::GraphicsLayerCA*)graphicsLayer;

@end

@implementation WebAnimationDelegate

- (void)animationDidStart:(CAAnimation *)animation
{
    if (!m_graphicsLayer)
        return;

    double startTime = WebCore::mediaTimeToCurrentTime([animation beginTime]);
    m_graphicsLayer->client()->notifyAnimationStarted(m_graphicsLayer, startTime);
}

- (WebCore::GraphicsLayerCA*)graphicsLayer
{
    return m_graphicsLayer;
}

- (void)setLayer:(WebCore::GraphicsLayerCA*)graphicsLayer
{
    m_graphicsLayer = graphicsLayer;
}

@end

namespace WebCore {

static inline void copyTransform(CATransform3D& toT3D, const TransformationMatrix& t)
{
    toT3D.m11 = narrowPrecisionToFloat(t.m11());
    toT3D.m12 = narrowPrecisionToFloat(t.m12());
    toT3D.m13 = narrowPrecisionToFloat(t.m13());
    toT3D.m14 = narrowPrecisionToFloat(t.m14());
    toT3D.m21 = narrowPrecisionToFloat(t.m21());
    toT3D.m22 = narrowPrecisionToFloat(t.m22());
    toT3D.m23 = narrowPrecisionToFloat(t.m23());
    toT3D.m24 = narrowPrecisionToFloat(t.m24());
    toT3D.m31 = narrowPrecisionToFloat(t.m31());
    toT3D.m32 = narrowPrecisionToFloat(t.m32());
    toT3D.m33 = narrowPrecisionToFloat(t.m33());
    toT3D.m34 = narrowPrecisionToFloat(t.m34());
    toT3D.m41 = narrowPrecisionToFloat(t.m41());
    toT3D.m42 = narrowPrecisionToFloat(t.m42());
    toT3D.m43 = narrowPrecisionToFloat(t.m43());
    toT3D.m44 = narrowPrecisionToFloat(t.m44());
}

static NSValue* getTransformFunctionValue(const GraphicsLayer::TransformValue& transformValue, size_t index, const IntSize& size, TransformOperation::OperationType transformType)
{
    TransformOperation* op = (index >= transformValue.value()->operations().size()) ? 0 : transformValue.value()->operations()[index].get();
    
    switch (transformType) {
        case TransformOperation::ROTATE:
        case TransformOperation::ROTATE_X:
        case TransformOperation::ROTATE_Y:
            return [NSNumber numberWithDouble:op ? deg2rad(static_cast<RotateTransformOperation*>(op)->angle()) : 0];
        case TransformOperation::SCALE_X:
            return [NSNumber numberWithDouble:op ? static_cast<ScaleTransformOperation*>(op)->x() : 0];
        case TransformOperation::SCALE_Y:
            return [NSNumber numberWithDouble:op ? static_cast<ScaleTransformOperation*>(op)->y() : 0];
        case TransformOperation::SCALE_Z:
            return [NSNumber numberWithDouble:op ? static_cast<ScaleTransformOperation*>(op)->z() : 0];
        case TransformOperation::TRANSLATE_X:
            return [NSNumber numberWithDouble:op ? static_cast<TranslateTransformOperation*>(op)->x(size) : 0];
        case TransformOperation::TRANSLATE_Y:
            return [NSNumber numberWithDouble:op ? static_cast<TranslateTransformOperation*>(op)->y(size) : 0];
        case TransformOperation::TRANSLATE_Z:
            return [NSNumber numberWithDouble:op ? static_cast<TranslateTransformOperation*>(op)->z(size) : 0];
        case TransformOperation::SCALE:
        case TransformOperation::TRANSLATE:
        case TransformOperation::SKEW_X:
        case TransformOperation::SKEW_Y:
        case TransformOperation::SKEW:
        case TransformOperation::MATRIX:
        case TransformOperation::SCALE_3D:
        case TransformOperation::TRANSLATE_3D:
        case TransformOperation::ROTATE_3D:
        case TransformOperation::MATRIX_3D:
        case TransformOperation::PERSPECTIVE:
        case TransformOperation::IDENTITY:
        case TransformOperation::NONE: {
            TransformationMatrix t;
            if (op)
                op->apply(t, size);
            CATransform3D cat;
            copyTransform(cat, t);
            return [NSValue valueWithCATransform3D:cat];
        }
    }
    
    return 0;
}

#if HAVE_MODERN_QUARTZCORE
static NSString* getValueFunctionNameForTransformOperation(TransformOperation::OperationType transformType)
{
    // Use literal strings to avoid link-time dependency on those symbols.
    switch (transformType) {
        case TransformOperation::ROTATE_X:
            return @"rotateX"; // kCAValueFunctionRotateX;
        case TransformOperation::ROTATE_Y:
            return @"rotateY"; // kCAValueFunctionRotateY;
        case TransformOperation::ROTATE:
            return @"rotateZ"; // kCAValueFunctionRotateZ;
        case TransformOperation::SCALE_X:
            return @"scaleX"; // kCAValueFunctionScaleX;
        case TransformOperation::SCALE_Y:
            return @"scaleY"; // kCAValueFunctionScaleY;
        case TransformOperation::SCALE_Z:
            return @"scaleZ"; // kCAValueFunctionScaleZ;
        case TransformOperation::TRANSLATE_X:
            return @"translateX"; // kCAValueFunctionTranslateX;
        case TransformOperation::TRANSLATE_Y:
            return @"translateY"; // kCAValueFunctionTranslateY;
        case TransformOperation::TRANSLATE_Z:
            return @"translateZ"; // kCAValueFunctionTranslateZ;
        default:
            return nil;
    }
}
#endif

static CAMediaTimingFunction* getCAMediaTimingFunction(const TimingFunction& timingFunction)
{
    switch (timingFunction.type()) {
        case LinearTimingFunction:
            return [CAMediaTimingFunction functionWithName:@"linear"];
        case CubicBezierTimingFunction:
            return [CAMediaTimingFunction functionWithControlPoints:static_cast<float>(timingFunction.x1()) :static_cast<float>(timingFunction.y1())
                        :static_cast<float>(timingFunction.x2()) :static_cast<float>(timingFunction.y2())];
    }
    return 0;
}

#ifndef NDEBUG
static void setLayerBorderColor(PlatformLayer* layer, const Color& color)
{
    CGColorRef borderColor = createCGColor(color);
    [layer setBorderColor:borderColor];
    CGColorRelease(borderColor);
}

static void clearBorderColor(PlatformLayer* layer)
{
    [layer setBorderColor:nil];
}
#endif

static void setLayerBackgroundColor(PlatformLayer* layer, const Color& color)
{
    CGColorRef bgColor = createCGColor(color);
    [layer setBackgroundColor:bgColor];
    CGColorRelease(bgColor);
}

static void clearLayerBackgroundColor(PlatformLayer* layer)
{
    [layer setBackgroundColor:0];
}

static CALayer* getPresentationLayer(CALayer* layer)
{
    CALayer*  presLayer = [layer presentationLayer];
    if (!presLayer)
        presLayer = layer;

    return presLayer;
}

static bool caValueFunctionSupported()
{
    static bool sHaveValueFunction = [CAPropertyAnimation instancesRespondToSelector:@selector(setValueFunction:)];
    return sHaveValueFunction;
}

static bool forceSoftwareAnimation()
{
    static bool forceSoftwareAnimation = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebCoreForceSoftwareAnimation"];
    return forceSoftwareAnimation;
}

GraphicsLayer::CompositingCoordinatesOrientation GraphicsLayer::compositingCoordinatesOrientation()
{
    return CompositingCoordinatesBottomUp;
}

#ifndef NDEBUG
bool GraphicsLayer::showDebugBorders()
{
    static bool showDebugBorders = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebCoreLayerBorders"];
    return showDebugBorders;
}

bool GraphicsLayer::showRepaintCounter()
{
    static bool showRepaintCounter = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebCoreLayerRepaintCounter"];
    return showRepaintCounter;
}
#endif

static NSDictionary* nullActionsDictionary()
{
    NSNull* nullValue = [NSNull null];
    NSDictionary* actions = [NSDictionary dictionaryWithObjectsAndKeys:
                             nullValue, @"anchorPoint",
                             nullValue, @"bounds",
                             nullValue, @"contents",
                             nullValue, @"contentsRect",
                             nullValue, @"opacity",
                             nullValue, @"position",
                             nullValue, @"shadowColor",
                             nullValue, @"sublayerTransform",
                             nullValue, @"sublayers",
                             nullValue, @"transform",
#ifndef NDEBUG
                             nullValue, @"zPosition",
#endif
                             nil];
    return actions;
}

GraphicsLayer* GraphicsLayer::createGraphicsLayer(GraphicsLayerClient* client)
{
    return new GraphicsLayerCA(client);
}

GraphicsLayerCA::GraphicsLayerCA(GraphicsLayerClient* client)
: GraphicsLayer(client)
, m_contentLayerForImageOrVideo(false)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    m_layer.adoptNS([[WebLayer alloc] init]);
    [m_layer.get() setLayerOwner:this];

#ifndef NDEBUG
    updateDebugIndicators();
#endif

    m_animationDelegate.adoptNS([[WebAnimationDelegate alloc] init]);
    [m_animationDelegate.get() setLayer:this];
    
    END_BLOCK_OBJC_EXCEPTIONS
}

GraphicsLayerCA::~GraphicsLayerCA()
{
    // Remove a inner layer if there is one.
    clearContents();

    BEGIN_BLOCK_OBJC_EXCEPTIONS

    // Clean up the WK layer.
    if (m_layer) {
        WebLayer* layer = m_layer.get();
        [layer setLayerOwner:nil];
        [layer removeFromSuperlayer];
    }
    
    if (m_transformLayer)
        [m_transformLayer.get() removeFromSuperlayer];

    // animationDidStart: can fire after this, so we need to clear out the layer on the delegate.
    [m_animationDelegate.get() setLayer:0];

    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setName(const String& name)
{
    String longName = String::format("CALayer(%p) GraphicsLayer(%p) ", m_layer.get(), this) + name;
    GraphicsLayer::setName(longName);
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [m_layer.get() setName:name];
    END_BLOCK_OBJC_EXCEPTIONS
}

NativeLayer GraphicsLayerCA::nativeLayer() const
{
    return m_layer.get();
}

void GraphicsLayerCA::addChild(GraphicsLayer* childLayer)
{
    GraphicsLayer::addChild(childLayer);

    GraphicsLayerCA* childLayerCA = static_cast<GraphicsLayerCA*>(childLayer);
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [hostLayerForSublayers() addSublayer:childLayerCA->layerForSuperlayer()];
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::addChildAtIndex(GraphicsLayer* childLayer, int index)
{
    GraphicsLayer::addChildAtIndex(childLayer, index);

    GraphicsLayerCA* childLayerCA = static_cast<GraphicsLayerCA*>(childLayer);
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [hostLayerForSublayers() insertSublayer:childLayerCA->layerForSuperlayer() atIndex:index];
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::addChildBelow(GraphicsLayer* childLayer, GraphicsLayer* sibling)
{
    // FIXME: share code with base class
    ASSERT(childLayer != this);
    childLayer->removeFromParent();

    bool found = false;
    for (unsigned i = 0; i < m_children.size(); i++) {
        if (sibling == m_children[i]) {
            m_children.insert(i, childLayer);
            found = true;
            break;
        }
    }
    childLayer->setParent(this);

    BEGIN_BLOCK_OBJC_EXCEPTIONS

    GraphicsLayerCA* childLayerCA = static_cast<GraphicsLayerCA*>(childLayer);
    GraphicsLayerCA* siblingLayerCA = static_cast<GraphicsLayerCA*>(sibling);
    if (found)
        [hostLayerForSublayers() insertSublayer:childLayerCA->layerForSuperlayer() below:siblingLayerCA->layerForSuperlayer()];
    else {
        m_children.append(childLayer);
        [hostLayerForSublayers() addSublayer:childLayerCA->layerForSuperlayer()];
    }

    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::addChildAbove(GraphicsLayer* childLayer, GraphicsLayer* sibling)
{
    // FIXME: share code with base class
    ASSERT(childLayer != this);
    childLayer->removeFromParent();

    unsigned i;
    bool found = false;
    for (i = 0; i < m_children.size(); i++) {
        if (sibling == m_children[i]) {
            m_children.insert(i+1, childLayer);
            found = true;
            break;
        }
    }
    childLayer->setParent(this);

    BEGIN_BLOCK_OBJC_EXCEPTIONS

    GraphicsLayerCA* childLayerCA = static_cast<GraphicsLayerCA*>(childLayer);
    GraphicsLayerCA* siblingLayerCA = static_cast<GraphicsLayerCA*>(sibling);
    if (found) {
        [hostLayerForSublayers() insertSublayer:childLayerCA->layerForSuperlayer() above:siblingLayerCA->layerForSuperlayer()];
    } else {
        m_children.append(childLayer);
        [hostLayerForSublayers() addSublayer:childLayerCA->layerForSuperlayer()];
    }

    END_BLOCK_OBJC_EXCEPTIONS
}

bool GraphicsLayerCA::replaceChild(GraphicsLayer* oldChild, GraphicsLayer* newChild)
{
    // FIXME: share code with base class
    ASSERT(!newChild->parent());

    bool found = false;
    for (unsigned i = 0; i < m_children.size(); i++) {
        if (oldChild == m_children[i]) {
            m_children[i] = newChild;
            found = true;
            break;
        }
    }

    if (found) {
        oldChild->setParent(0);

        newChild->removeFromParent();
        newChild->setParent(this);

        BEGIN_BLOCK_OBJC_EXCEPTIONS
        GraphicsLayerCA* oldChildCA = static_cast<GraphicsLayerCA*>(oldChild);
        GraphicsLayerCA* newChildCA = static_cast<GraphicsLayerCA*>(newChild);
        [hostLayerForSublayers() replaceSublayer:oldChildCA->layerForSuperlayer() with:newChildCA->layerForSuperlayer()];
        END_BLOCK_OBJC_EXCEPTIONS
        return true;
    }
    return false;
}

void GraphicsLayerCA::removeFromParent()
{
    GraphicsLayer::removeFromParent();

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [layerForSuperlayer() removeFromSuperlayer];            
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setPosition(const FloatPoint& point)
{
    // Don't short-circuit here, because position and anchor point are inter-dependent.
    GraphicsLayer::setPosition(point);

    // Position is offset on the layer by the layer anchor point.
    CGPoint posPoint = CGPointMake(m_position.x() + m_anchorPoint.x() * m_size.width(),
                                   m_position.y() + m_anchorPoint.y() * m_size.height());
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [primaryLayer() setPosition:posPoint];
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setAnchorPoint(const FloatPoint3D& point)
{
    // Don't short-circuit here, because position and anchor point are inter-dependent.
    bool zChanged = (point.z() != m_anchorPoint.z());
    GraphicsLayer::setAnchorPoint(point);

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    // set the value on the layer to the new transform.
    [primaryLayer() setAnchorPoint:FloatPoint(point.x(), point.y())];

    if (zChanged) {
#if HAVE_MODERN_QUARTZCORE
        [primaryLayer() setAnchorPointZ:m_anchorPoint.z()];
#endif
    }

    // Position depends on anchor point, so update it now.
    setPosition(m_position);
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setSize(const FloatSize& size)
{
    GraphicsLayer::setSize(size);

    CGRect rect = CGRectMake(0.0f,
                             0.0f,
                             m_size.width(),
                             m_size.height());
    
    CGPoint centerPoint = CGPointMake(m_size.width() / 2.0f, m_size.height() / 2.0f);
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS

    if (m_transformLayer) {
        [m_transformLayer.get() setBounds:rect];
    
        // the anchor of the contents layer is always at 0.5, 0.5, so the position
        // is center-relative
        [m_layer.get() setPosition:centerPoint];
    }
    
    bool needTiledLayer = requiresTiledLayer(m_size);
    if (needTiledLayer != m_usingTiledLayer)
        swapFromOrToTiledLayer(needTiledLayer);
    
    [m_layer.get() setBounds:rect];

    // Note that we don't resize m_contentsLayer. It's up the caller to do that.

    END_BLOCK_OBJC_EXCEPTIONS

    // if we've changed the bounds, we need to recalculate the position
    // of the layer, taking anchor point into account
    setPosition(m_position);
}

void GraphicsLayerCA::setTransform(const TransformationMatrix& t)
{
    GraphicsLayer::setTransform(t);
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    CATransform3D transform;
    copyTransform(transform, t);
    [primaryLayer() setTransform:transform];
    END_BLOCK_OBJC_EXCEPTIONS
    
    // Remove any old transition entries for transform.
    removeAllAnimationsForProperty(AnimatedPropertyWebkitTransform);

    // Even if we don't have a transition in the list, the layer may still have one.
    // This happens when we are setting the final transform value after an animation or
    // transition has ended. In removeAnimation we toss the entry from the list but don't
    // remove it from the list. That way we aren't in danger of displaying a stale transform
    // in the time between removing the animation and setting the new unanimated value. We 
    // can't do this in removeAnimation because we don't know the new transform value there.
    String keyPath = propertyIdToString(AnimatedPropertyWebkitTransform);
    CALayer* layer = animatedLayer(AnimatedPropertyWebkitTransform);
    
    for (int i = 0; ; ++i) {
        String animName = keyPath + "_" + String::number(i);
        if (![layer animationForKey: animName])
            break;
        [layer removeAnimationForKey:animName];
    }
}

void GraphicsLayerCA::setChildrenTransform(const TransformationMatrix& t)
{
    if (t == m_childrenTransform)
        return;

    GraphicsLayer::setChildrenTransform(t);

    CATransform3D transform;
    copyTransform(transform, t);

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    // Set the value on the layer to the new transform.
    [primaryLayer() setSublayerTransform:transform];
    END_BLOCK_OBJC_EXCEPTIONS
}

static void moveAnimation(AnimatedPropertyID property, CALayer* fromLayer, CALayer* toLayer)
{
    String keyPath = GraphicsLayer::propertyIdToString(property);
    for (short index = 0; ; ++index) {
        String animName = keyPath + "_" + String::number(index);
        CAAnimation* anim = [fromLayer animationForKey:animName];
        if (!anim)
            break;

        [anim retain];
        [fromLayer removeAnimationForKey:animName];
        [toLayer addAnimation:anim forKey:animName];
        [anim release];
    }
}

static void moveSublayers(CALayer* fromLayer, CALayer* toLayer)
{
    NSArray* sublayersCopy = [[fromLayer sublayers] copy]; // Avoid mutation while enumerating, and keep the sublayers alive.
    NSEnumerator* childrenEnumerator = [sublayersCopy objectEnumerator];

    CALayer* layer;
    while ((layer = [childrenEnumerator nextObject]) != nil) {
        [layer removeFromSuperlayer];
        [toLayer addSublayer:layer];
    }
    [sublayersCopy release];
}

void GraphicsLayerCA::setPreserves3D(bool preserves3D)
{
    GraphicsLayer::setPreserves3D(preserves3D);

    CGPoint point = CGPointMake(m_size.width() / 2.0f, m_size.height() / 2.0f);
    CGPoint centerPoint = CGPointMake(0.5f, 0.5f);
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS

    Class transformLayerClass = NSClassFromString(@"CATransformLayer");
    if (preserves3D && !m_transformLayer && transformLayerClass) {
        // Create the transform layer.
        m_transformLayer.adoptNS([[transformLayerClass alloc] init]);

        // Turn off default animations.
        [m_transformLayer.get() setStyle:[NSDictionary dictionaryWithObject:nullActionsDictionary() forKey:@"actions"]];
        
#ifndef NDEBUG
        [m_transformLayer.get() setName:[NSString stringWithFormat:@"Transform Layer CATransformLayer(%p) GraphicsLayer(%p)", m_transformLayer.get(), this]];
#endif
        // Copy the position from this layer.
        [m_transformLayer.get() setBounds:[m_layer.get() bounds]];
        [m_transformLayer.get() setPosition:[m_layer.get() position]];
        [m_transformLayer.get() setAnchorPoint:[m_layer.get() anchorPoint]];
#if HAVE_MODERN_QUARTZCORE
        [m_transformLayer.get() setAnchorPointZ:[m_layer.get() anchorPointZ]];
#endif
        [m_transformLayer.get() setContentsRect:[m_layer.get() contentsRect]];
#ifndef NDEBUG
        [m_transformLayer.get() setZPosition:[m_layer.get() zPosition]];
#endif
        
        // The contents layer is positioned at (0,0) relative to the transformLayer.
        [m_layer.get() setPosition:point];
        [m_layer.get() setAnchorPoint:centerPoint];
#ifndef NDEBUG
        [m_layer.get() setZPosition:0.0f];
#endif
        
        // Transfer the transform over.
        [m_transformLayer.get() setTransform:[m_layer.get() transform]];
        [m_layer.get() setTransform:CATransform3DIdentity];
        
        // Transfer the opacity from the old layer to the transform layer.
        [m_transformLayer.get() setOpacity:m_opacity];
        [m_layer.get() setOpacity:1];

        // Move this layer to be a child of the transform layer.
        [[m_layer.get() superlayer] replaceSublayer:m_layer.get() with:m_transformLayer.get()];
        [m_transformLayer.get() addSublayer:m_layer.get()];

        moveAnimation(AnimatedPropertyWebkitTransform, m_layer.get(), m_transformLayer.get());
        moveSublayers(m_layer.get(), m_transformLayer.get());

    } else if (!preserves3D && m_transformLayer) {
        // Relace the transformLayer in the parent with this layer.
        [m_layer.get() removeFromSuperlayer];
        [[m_transformLayer.get() superlayer] replaceSublayer:m_transformLayer.get() with:m_layer.get()];

        moveAnimation(AnimatedPropertyWebkitTransform, m_transformLayer.get(), m_layer.get());
        moveSublayers(m_transformLayer.get(), m_layer.get());

        // Reset the layer position and transform.
        [m_layer.get() setPosition:[m_transformLayer.get() position]];
        [m_layer.get() setAnchorPoint:[m_transformLayer.get() anchorPoint]];
#if HAVE_MODERN_QUARTZCORE
        [m_layer.get() setAnchorPointZ:[m_transformLayer.get() anchorPointZ]];
#endif
        [m_layer.get() setContentsRect:[m_transformLayer.get() contentsRect]];
        [m_layer.get() setTransform:[m_transformLayer.get() transform]];
        [m_layer.get() setOpacity:[m_transformLayer.get() opacity]];
#ifndef NDEBUG
        [m_layer.get() setZPosition:[m_transformLayer.get() zPosition]];
#endif

        // Release the transform layer.
        m_transformLayer = 0;
    }

    END_BLOCK_OBJC_EXCEPTIONS
}

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

    GraphicsLayer::setMasksToBounds(masksToBounds);

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [m_layer.get() setMasksToBounds:masksToBounds];
    END_BLOCK_OBJC_EXCEPTIONS

#ifndef NDEBUG
    updateDebugIndicators();
#endif
}

void GraphicsLayerCA::setDrawsContent(bool drawsContent)
{
    if (drawsContent != m_drawsContent) {
        GraphicsLayer::setDrawsContent(drawsContent);

        bool needTiledLayer = requiresTiledLayer(m_size);
        if (needTiledLayer != m_usingTiledLayer)
            swapFromOrToTiledLayer(needTiledLayer);

        BEGIN_BLOCK_OBJC_EXCEPTIONS
        if (m_drawsContent)
            [m_layer.get() setNeedsDisplay];
        else
            [m_layer.get() setContents:nil];
        
#ifndef NDEBUG
        updateDebugIndicators();
#endif
        END_BLOCK_OBJC_EXCEPTIONS
    }
}

void GraphicsLayerCA::setBackgroundColor(const Color& color, const Animation* transition, double beginTime)
{
    GraphicsLayer::setBackgroundColor(color, transition, beginTime);

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    if (!m_contentsLayer.get()) {
        WebLayer* colorLayer = [WebLayer layer];
#ifndef NDEBUG
        [colorLayer setName:@"Color Layer"];
#endif
        setContentsLayer(colorLayer);
    }
    
    if (transition && !transition->isEmptyOrZeroDuration()) {
        CALayer* presLayer = [m_contentsLayer.get() presentationLayer];
        // If we don't have a presentationLayer, just use the CALayer
        if (!presLayer)
            presLayer = m_contentsLayer.get();

        // Get the current value of the background color from the layer
        CGColorRef fromBackgroundColor = [presLayer backgroundColor];

        CGColorRef bgColor = createCGColor(color);
        setBasicAnimation(AnimatedPropertyBackgroundColor, TransformOperation::NONE, 0, fromBackgroundColor, bgColor, true, transition, beginTime);
        CGColorRelease(bgColor);
    } else {
        removeAllAnimationsForProperty(AnimatedPropertyBackgroundColor);    
        setLayerBackgroundColor(m_contentsLayer.get(), m_backgroundColor);
    }

    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::clearBackgroundColor()
{
    if (!m_contentLayerForImageOrVideo)
        setContentsLayer(0);
    else
        clearLayerBackgroundColor(m_contentsLayer.get());
}

void GraphicsLayerCA::setContentsOpaque(bool opaque)
{
    GraphicsLayer::setContentsOpaque(opaque);

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [m_layer.get() setOpaque:m_contentsOpaque];
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setBackfaceVisibility(bool visible)
{
    if (m_backfaceVisibility == visible)
        return;
    
    GraphicsLayer::setBackfaceVisibility(visible);
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [m_layer.get() setDoubleSided:visible];
    END_BLOCK_OBJC_EXCEPTIONS
}

bool GraphicsLayerCA::setOpacity(float opacity, const Animation* transition, double beginTime)
{
    if (forceSoftwareAnimation())
        return false;
        
    float clampedOpacity = max(0.0f, min(opacity, 1.0f));
    
    bool opacitiesDiffer = (m_opacity != clampedOpacity);
    
    GraphicsLayer::setOpacity(clampedOpacity, transition, beginTime);
    
    int animIndex = findAnimationEntry(AnimatedPropertyOpacity, 0);
        
    // If we don't have a transition just set the opacity
    if (!transition || transition->isEmptyOrZeroDuration()) {
        // Three cases:
        //  1) no existing animation or transition: just set opacity
        //  2) existing transition: clear it and set opacity
        //  3) existing animation: just return
        //
        if (animIndex < 0 || m_animations[animIndex].isTransition()) {
            BEGIN_BLOCK_OBJC_EXCEPTIONS
            [primaryLayer() setOpacity:opacity];
            if (animIndex >= 0) {
                removeAllAnimationsForProperty(AnimatedPropertyOpacity);
                animIndex = -1;
            } else {
                String keyPath = propertyIdToString(AnimatedPropertyOpacity);
                
                // FIXME: using hardcoded '0' here. We should be clearing all animations. For now there is only 1.
                String animName = keyPath + "_0";
                [animatedLayer(AnimatedPropertyOpacity) removeAnimationForKey:animName];
            }
            END_BLOCK_OBJC_EXCEPTIONS
        } else {
            // We have an animation, so don't set the opacity directly.
            return false;
        }
    } else {
        // At this point, we know we have a transition. But if it is the same and
        // the opacity value has not changed, don't do anything.
        if (!opacitiesDiffer &&
                ((animIndex == -1) ||    
                 (m_animations[animIndex].isTransition() && *(m_animations[animIndex].animation()) == *transition))) {
            return false;
        }
    }
    
    // If an animation is running, ignore this transition, but still save the value.
    if (animIndex >= 0 && !m_animations[animIndex].isTransition())
        return false;

    bool didAnimate = false;
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    NSNumber* fromOpacityValue = nil;
    NSNumber* toOpacityValue = [NSNumber numberWithFloat:opacity];
    
    if (transition && !transition->isEmptyOrZeroDuration()) {
        CALayer* presLayer = getPresentationLayer(primaryLayer());
        float fromOpacity = [presLayer opacity];
        fromOpacityValue = [NSNumber numberWithFloat:fromOpacity];
        setBasicAnimation(AnimatedPropertyOpacity, TransformOperation::NONE, 0, fromOpacityValue, toOpacityValue, true, transition, beginTime);
        didAnimate = true;
    }

    END_BLOCK_OBJC_EXCEPTIONS
    
    return didAnimate;
}

void GraphicsLayerCA::setNeedsDisplay()
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    if (drawsContent())
        [m_layer.get() setNeedsDisplay];

    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setNeedsDisplayInRect(const FloatRect& rect)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    if (drawsContent())
        [m_layer.get() setNeedsDisplayInRect:rect];

    END_BLOCK_OBJC_EXCEPTIONS
}


bool GraphicsLayerCA::animateTransform(const TransformValueList& valueList, const IntSize& size, const Animation* anim, double beginTime, bool isTransition)
{
    if (forceSoftwareAnimation() || !anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2)
        return false;
        
    TransformValueList::FunctionList functionList;
    bool isValid, hasBigRotation;
    valueList.makeFunctionList(functionList, isValid, hasBigRotation);

    // 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 || functionList.size() > 1) && !caValueFunctionSupported())
        return false;
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    // Rules for animation:
    //
    //  1)  If functionList is empty or we don't have a big rotation, we do a matrix animation. We could
    //      use component animation for lists without a big rotation, but there is no need to, and this
    //      is more efficient.
    //
    //  2)  Otherwise we do a component hardware animation.
    bool isMatrixAnimation = !isValid || !hasBigRotation;
        
    // Set transform to identity since we are animating components and we need the base
    // to be the identity transform.
    TransformationMatrix t;
    CATransform3D toT3D;
    copyTransform(toT3D, t);
    [primaryLayer() setTransform:toT3D];
        
    bool isKeyframe = valueList.size() > 2;
        
    // Iterate through the transform functions, sending an animation for each one.
    for (int functionIndex = 0; ; ++functionIndex) {
        if (functionIndex >= static_cast<int>(functionList.size()) && !isMatrixAnimation)
            break;
            
        TransformOperation::OperationType opType = isMatrixAnimation ? TransformOperation::MATRIX_3D : functionList[functionIndex];

        if (isKeyframe) {
            NSMutableArray* timesArray = [[NSMutableArray alloc] init];
            NSMutableArray* valArray = [[NSMutableArray alloc] init];
            NSMutableArray* tfArray = [[NSMutableArray alloc] init];
            
            // Iterate through the keyframes, building arrays for the animation.
            for (Vector<TransformValue>::const_iterator it = valueList.values().begin(); it != valueList.values().end(); ++it) {
                const TransformValue& curValue = (*it);
                
                // fill in the key time and timing function
                [timesArray addObject:[NSNumber numberWithFloat:curValue.key()]];
                
                const TimingFunction* tf = 0;
                if (curValue.timingFunction())
                    tf = curValue.timingFunction();
                else if (anim->isTimingFunctionSet())
                    tf = &anim->timingFunction();
                
                CAMediaTimingFunction* timingFunction = getCAMediaTimingFunction(tf ? *tf : TimingFunction(LinearTimingFunction));
                [tfArray addObject:timingFunction];
                
                // fill in the function
                if (isMatrixAnimation) {
                    TransformationMatrix t;
                    curValue.value()->apply(size, t);
                    CATransform3D cat;
                    copyTransform(cat, t);
                    [valArray addObject:[NSValue valueWithCATransform3D:cat]];
                } else
                    [valArray addObject:getTransformFunctionValue(curValue, functionIndex, size, opType)];
            }
            
            // We toss the last tfArray value because it has to one shorter than the others.
            [tfArray removeLastObject];
            
            setKeyframeAnimation(AnimatedPropertyWebkitTransform, opType, functionIndex, timesArray, valArray, tfArray, isTransition, anim, beginTime);
            
            [timesArray release];
            [valArray release];
            [tfArray release];
        } else {
            // Is a transition
            id fromValue, toValue;
            
            if (isMatrixAnimation) {
                TransformationMatrix fromt, tot;
                valueList.at(0).value()->apply(size, fromt);
                valueList.at(1).value()->apply(size, tot);
                
                CATransform3D cat;
                copyTransform(cat, fromt);
                fromValue = [NSValue valueWithCATransform3D:cat];
                copyTransform(cat, tot);
                toValue = [NSValue valueWithCATransform3D:cat];
            } else {
                fromValue = getTransformFunctionValue(valueList.at(0), functionIndex, size, opType);
                toValue = getTransformFunctionValue(valueList.at(1), functionIndex, size, opType);
            }
            
            setBasicAnimation(AnimatedPropertyWebkitTransform, opType, functionIndex, fromValue, toValue, isTransition, anim, beginTime);
        }
        
        if (isMatrixAnimation)
            break;
    }

    END_BLOCK_OBJC_EXCEPTIONS
    return true;
}

bool GraphicsLayerCA::animateFloat(AnimatedPropertyID property, const FloatValueList& valueList, const Animation* animation, double beginTime)
{
    if (forceSoftwareAnimation() || valueList.size() < 2)
        return false;
        
    // if there is already is an animation for this property and it hasn't changed, ignore it.
    int i = findAnimationEntry(property, 0);
    if (i >= 0 && *m_animations[i].animation() == *animation) {
        m_animations[i].setIsCurrent();
        return false;
    }

    if (valueList.size() == 2) {
        float fromVal = valueList.at(0).value();
        float toVal   = valueList.at(1).value();
        if (isnan(toVal) && isnan(fromVal))
            return false;
    
        // initialize the property to 0
        [animatedLayer(property) setValue:0 forKeyPath:propertyIdToString(property)];
        setBasicAnimation(property, TransformOperation::NONE, 0, isnan(fromVal) ? nil : [NSNumber numberWithFloat:fromVal], isnan(toVal) ? nil : [NSNumber numberWithFloat:toVal], false, animation, beginTime);
        return true;
    }

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    NSMutableArray* timesArray = [[NSMutableArray alloc] init];
    NSMutableArray* valArray = [[NSMutableArray alloc] init];
    NSMutableArray* tfArray = [[NSMutableArray alloc] init];

    for (unsigned i = 0; i < valueList.values().size(); ++i) {
        const FloatValue& curValue = valueList.values()[i];
        [timesArray addObject:[NSNumber numberWithFloat:curValue.key()]];
        [valArray addObject:[NSNumber numberWithFloat:curValue.value()]];

        const TimingFunction* tf = 0;
        if (curValue.timingFunction())
            tf = curValue.timingFunction();
        else if (animation->isTimingFunctionSet())
            tf = &animation->timingFunction();

        CAMediaTimingFunction* timingFunction = getCAMediaTimingFunction(tf ? *tf : TimingFunction());
        [tfArray addObject:timingFunction];
    }
    
    // We toss the last tfArray value because it has to one shorter than the others.
    [tfArray removeLastObject];
    
    // Initialize the property to 0.
    [animatedLayer(property) setValue:0 forKeyPath:propertyIdToString(property)];
    // Then set the animation.
    setKeyframeAnimation(property, TransformOperation::NONE, 0, timesArray, valArray, tfArray, false, animation, beginTime);

    [timesArray release];
    [valArray release];
    [tfArray release];

    END_BLOCK_OBJC_EXCEPTIONS
    return true;
}

void GraphicsLayerCA::setContentsToImage(Image* image)
{
    if (image) {
        BEGIN_BLOCK_OBJC_EXCEPTIONS
        {
            if (!m_contentsLayer.get()) {
                WebLayer* imageLayer = [WebLayer layer];
#ifndef NDEBUG
                [imageLayer setName:@"Image Layer"];
#endif
                setContentsLayer(imageLayer);
            }

            // FIXME: maybe only do trilinear if the image is being scaled down,
            // but then what if the layer size changes?
#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
            [m_contentsLayer.get() setMinificationFilter:kCAFilterTrilinear];
#endif
            CGImageRef theImage = image->nativeImageForCurrentFrame();
            [m_contentsLayer.get() setContents:(id)theImage];
        }
        END_BLOCK_OBJC_EXCEPTIONS
    } else
        setContentsLayer(0);

    m_contentLayerForImageOrVideo = (image != 0);
}

void GraphicsLayerCA::setContentsToVideo(PlatformLayer* videoLayer)
{
    setContentsLayer(videoLayer);
    m_contentLayerForImageOrVideo = (videoLayer != 0);
}

void GraphicsLayerCA::clearContents()
{
    if (m_contentLayerForImageOrVideo) {
        setContentsLayer(0);
        m_contentLayerForImageOrVideo = false;
    }
}

void GraphicsLayerCA::updateContentsRect()
{
    if (m_client && m_contentsLayer) {
        IntRect contentRect = m_client->contentsBox(this);
        
        CGPoint point = CGPointMake(contentRect.x(),
                                    contentRect.y());
        CGRect rect = CGRectMake(0.0f,
                                 0.0f,
                                 contentRect.width(),
                                 contentRect.height());

        BEGIN_BLOCK_OBJC_EXCEPTIONS
        [m_contentsLayer.get() setPosition:point];
        [m_contentsLayer.get() setBounds:rect];
        END_BLOCK_OBJC_EXCEPTIONS
    }
}

void GraphicsLayerCA::setBasicAnimation(AnimatedPropertyID property, TransformOperation::OperationType operationType, short index, void* fromVal, void* toVal, bool isTransition, const Animation* transition, double beginTime)
{
    ASSERT(fromVal || toVal);

    WebLayer* layer = animatedLayer(property);
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS

    // add an entry for this animation
    addAnimationEntry(property, index, isTransition, transition);

    String keyPath = propertyIdToString(property);
    String animName = keyPath + "_" + String::number(index);

    CABasicAnimation* basicAnim = [CABasicAnimation animationWithKeyPath:keyPath];
    
    double duration = transition->duration();
    if (duration <= 0)
        duration = cAnimationAlmostZeroDuration;
        
    float repeatCount = transition->iterationCount();
    if (repeatCount == Animation::IterationCountInfinite)
        repeatCount = FLT_MAX;
    else if (transition->direction() == Animation::AnimationDirectionAlternate)
        repeatCount /= 2;
        
    [basicAnim setDuration:duration];
    [basicAnim setRepeatCount:repeatCount];
    [basicAnim setAutoreverses:transition->direction()];
    [basicAnim setRemovedOnCompletion:NO];

    // Note that currently transform is the only property which has animations
    // with an index > 0.
    [basicAnim setAdditive:property == AnimatedPropertyWebkitTransform];
    [basicAnim setFillMode:@"extended"];
#if HAVE_MODERN_QUARTZCORE
    if (NSString* valueFunctionName = getValueFunctionNameForTransformOperation(operationType))
        [basicAnim setValueFunction:[CAValueFunction functionWithName:valueFunctionName]];
#else
    UNUSED_PARAM(operationType);
#endif
    
    // Set the delegate (and property value).
    int prop = isTransition ? property : AnimatedPropertyInvalid;
    [basicAnim setValue:[NSNumber numberWithInt:prop] forKey:WebAnimationCSSPropertyKey];
    [basicAnim setDelegate:m_animationDelegate.get()];

    NSTimeInterval bt = beginTime ? [layer convertTime:currentTimeToMediaTime(beginTime) fromLayer:nil] : 0;
    [basicAnim setBeginTime:bt];
    
    if (fromVal)
        [basicAnim setFromValue:reinterpret_cast<id>(fromVal)];
    if (toVal)
        [basicAnim setToValue:reinterpret_cast<id>(toVal)];

    const TimingFunction* tf = 0;
    if (transition->isTimingFunctionSet())
        tf = &transition->timingFunction();

    CAMediaTimingFunction* timingFunction = getCAMediaTimingFunction(tf ? *tf : TimingFunction());
    [basicAnim setTimingFunction:timingFunction];

    // Send over the animation.
    [layer removeAnimationForKey:animName];
    [layer addAnimation:basicAnim forKey:animName];
    
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setKeyframeAnimation(AnimatedPropertyID property, TransformOperation::OperationType operationType, short index, void* keys, void* values, void* timingFunctions, 
                                    bool isTransition, const Animation* anim, double beginTime)
{
    PlatformLayer* layer = animatedLayer(property);

    // Add an entry for this animation (which may change beginTime).
    addAnimationEntry(property, index, isTransition, anim);

    String keyPath = propertyIdToString(property);
    String animName = keyPath + "_" + String::number(index);

    BEGIN_BLOCK_OBJC_EXCEPTIONS

    CAKeyframeAnimation* keyframeAnim = [CAKeyframeAnimation animationWithKeyPath:keyPath];

    double duration = anim->duration();
    if (duration <= 0)
        duration = cAnimationAlmostZeroDuration;

    float repeatCount = anim->iterationCount();
    if (repeatCount == Animation::IterationCountInfinite)
        repeatCount = FLT_MAX;
    else if (anim->direction() == Animation::AnimationDirectionAlternate)
        repeatCount /= 2;

    [keyframeAnim setDuration:duration];
    [keyframeAnim setRepeatCount:repeatCount];
    [keyframeAnim setAutoreverses:anim->direction()];
    [keyframeAnim setRemovedOnCompletion:NO];

    // The first animation is non-additive, all the rest are additive.
    // Note that currently transform is the only property which has animations
    // with an index > 0.
    [keyframeAnim setAdditive:(property == AnimatedPropertyWebkitTransform) ? YES : NO];
    [keyframeAnim setFillMode:@"extended"];
#if HAVE_MODERN_QUARTZCORE
    if (NSString* valueFunctionName = getValueFunctionNameForTransformOperation(operationType))
        [keyframeAnim setValueFunction:[CAValueFunction functionWithName:valueFunctionName]];
#else
    UNUSED_PARAM(operationType);
#endif

    [keyframeAnim setKeyTimes:reinterpret_cast<id>(keys)];
    [keyframeAnim setValues:reinterpret_cast<id>(values)];
    
    // Set the delegate (and property value).
    int prop = isTransition ? property : AnimatedPropertyInvalid;
    [keyframeAnim setValue:[NSNumber numberWithInt: prop] forKey:WebAnimationCSSPropertyKey];
    [keyframeAnim setDelegate:m_animationDelegate.get()];

    NSTimeInterval bt = beginTime ? [layer convertTime:currentTimeToMediaTime(beginTime) fromLayer:nil] : 0;
    [keyframeAnim setBeginTime:bt];
    
    // Set the timing functions, if any.
    if (timingFunctions != nil)
        [keyframeAnim setTimingFunctions:(id)timingFunctions];
        
    // Send over the animation.
    [layer removeAnimationForKey:animName];
    [layer addAnimation:keyframeAnim forKey:animName];
        
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::suspendAnimations()
{
    double t = currentTimeToMediaTime(currentTime());
    [primaryLayer() setSpeed:0];
    [primaryLayer() setTimeOffset:t];
}

void GraphicsLayerCA::resumeAnimations()
{
    [primaryLayer() setSpeed:1];
    [primaryLayer() setTimeOffset:0];
}

void GraphicsLayerCA::removeAnimation(int index, bool reset)
{
    ASSERT(index >= 0);

    AnimatedPropertyID property = m_animations[index].property();
    
    // Set the value of the property and remove the animation.
    String keyPath = propertyIdToString(property);
    String animName = keyPath + "_" + String::number(m_animations[index].index());
    CALayer* layer = animatedLayer(property);

    BEGIN_BLOCK_OBJC_EXCEPTIONS

    // If we are not resetting, it means we are pausing. So we need to get the current presentation
    // value into the property before we remove the animation.
    if (!reset) {
        // Put the current value into the property.
        CALayer* presLayer = [layer presentationLayer];
        if (presLayer)
            [layer setValue:[presLayer valueForKeyPath:keyPath] forKeyPath:keyPath];

        // Make sure the saved values accurately reflect the value in the layer.
        id val = [layer valueForKeyPath:keyPath];
        switch (property) {
            case AnimatedPropertyWebkitTransform:
                // FIXME: needs comment explaining why the m_transform is not obtained from the layer
                break;
            case AnimatedPropertyBackgroundColor:
                m_backgroundColor = Color(reinterpret_cast<CGColorRef>(val));
                break;
            case AnimatedPropertyOpacity:
                m_opacity = [val floatValue];
                break;
            case AnimatedPropertyInvalid:
                ASSERT_NOT_REACHED();
                break;
        }
    }
    
    // If we have reached the end of an animation, we don't want to actually remove the 
    // animation from the CALayer. At some point we will be setting the property to its
    // unanimated value and at that point we will remove the animation. That will avoid
    // any flashing between the time the animation is removed and the property is set.
    if (!reset || m_animations[index].isTransition())
        [layer removeAnimationForKey:animName];

    END_BLOCK_OBJC_EXCEPTIONS

    // Remove the animation entry.
    m_animations.remove(index);
}

PlatformLayer* GraphicsLayerCA::hostLayerForSublayers() const
{
    return m_transformLayer ? m_transformLayer.get() : m_layer.get();
}

PlatformLayer* GraphicsLayerCA::layerForSuperlayer() const
{
    if (m_transformLayer)
        return m_transformLayer.get();

    return m_layer.get();
}

PlatformLayer* GraphicsLayerCA::platformLayer() const
{
    return primaryLayer();
}

#ifndef NDEBUG
void GraphicsLayerCA::setDebugBackgroundColor(const Color& color)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    if (color.isValid())
        setLayerBackgroundColor(m_layer.get(), color);
    else
        clearLayerBackgroundColor(m_layer.get());
    
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setDebugBorder(const Color& color, float borderWidth)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    if (color.isValid()) {
        setLayerBorderColor(m_layer.get(), color);
        [m_layer.get() setBorderWidth:borderWidth];
    } else {
        clearBorderColor(m_layer.get());
        [m_layer.get() setBorderWidth:0];
    }
    
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::setZPosition(float position)
{
    GraphicsLayer::setZPosition(position);
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    [primaryLayer() setZPosition:position];
    END_BLOCK_OBJC_EXCEPTIONS
}
#endif

bool GraphicsLayerCA::requiresTiledLayer(const FloatSize& size) const
{
    if (!m_drawsContent)
        return false;

    // FIXME: catch zero-size height or width here (or earlier)?
    return size.width() > cMaxPixelDimension || size.height() > cMaxPixelDimension;
}

void GraphicsLayerCA::swapFromOrToTiledLayer(bool userTiledLayer)
{
    if (userTiledLayer == m_usingTiledLayer)
        return;

    CGSize tileSize = CGSizeMake(cTiledLayerTileSize, cTiledLayerTileSize);

    BEGIN_BLOCK_OBJC_EXCEPTIONS
    
    RetainPtr<CALayer> oldLayer = m_layer.get();
    
    Class layerClass = userTiledLayer ? [WebTiledLayer self] : [WebLayer self];
    m_layer.adoptNS([[layerClass alloc] init]);

    if (userTiledLayer) {
        WebTiledLayer* tiledLayer = (WebTiledLayer*)m_layer.get();
        [tiledLayer setTileSize:tileSize];
        [tiledLayer setLevelsOfDetail:1];
        [tiledLayer setLevelsOfDetailBias:0];

        if (GraphicsLayer::compositingCoordinatesOrientation() == GraphicsLayer::CompositingCoordinatesBottomUp)
            [tiledLayer setContentsGravity:@"bottomLeft"];
        else
            [tiledLayer setContentsGravity:@"topLeft"];
    }
    
    [m_layer.get() setLayerOwner:this];
    [m_layer.get() setSublayers:[oldLayer.get() sublayers]];
    
    [[oldLayer.get() superlayer] replaceSublayer:oldLayer.get() with:m_layer.get()];
    
    [m_layer.get() setBounds:[oldLayer.get() bounds]];
    [m_layer.get() setPosition:[oldLayer.get() position]];
    [m_layer.get() setAnchorPoint:[oldLayer.get() anchorPoint]];
    [m_layer.get() setOpaque:[oldLayer.get() isOpaque]];
    [m_layer.get() setOpacity:[oldLayer.get() opacity]];
    [m_layer.get() setTransform:[oldLayer.get() transform]];
    [m_layer.get() setSublayerTransform:[oldLayer.get() sublayerTransform]];
    [m_layer.get() setDoubleSided:[oldLayer.get() isDoubleSided]];
#ifndef NDEBUG
    [m_layer.get() setZPosition:[oldLayer.get() zPosition]];
#endif
    
#ifndef NDEBUG
    String name = String::format("CALayer(%p) GraphicsLayer(%p) ", m_layer.get(), this) + m_name;
    [m_layer.get() setName:name];
#endif

    // move over animations
    moveAnimation(AnimatedPropertyWebkitTransform, oldLayer.get(), m_layer.get());
    moveAnimation(AnimatedPropertyOpacity, oldLayer.get(), m_layer.get());
    moveAnimation(AnimatedPropertyBackgroundColor, oldLayer.get(), m_layer.get());
    
    // need to tell new layer to draw itself
    setNeedsDisplay();
    
    END_BLOCK_OBJC_EXCEPTIONS
    
    m_usingTiledLayer = userTiledLayer;
    
#ifndef NDEBUG
    updateDebugIndicators();
#endif
}

void GraphicsLayerCA::setContentsLayer(WebLayer* contentsLayer)
{
    if (contentsLayer == m_contentsLayer)
        return;

    BEGIN_BLOCK_OBJC_EXCEPTIONS

    if (m_contentsLayer) {
        [m_contentsLayer.get() removeFromSuperlayer];
        m_contentsLayer = 0;
    }
    
    if (contentsLayer) {
        // Turn off implicit animations on the inner layer.
        [contentsLayer setStyle:[NSDictionary dictionaryWithObject:nullActionsDictionary() forKey:@"actions"]];

        m_contentsLayer.adoptNS([contentsLayer retain]);

        bool needToFlip = GraphicsLayer::compositingCoordinatesOrientation() == GraphicsLayer::CompositingCoordinatesBottomUp;
        CGPoint anchorPoint = needToFlip ? CGPointMake(0.0f, 1.0f) : CGPointZero;

        // If the layer world is flipped, we need to un-flip the contents layer
        if (needToFlip) {
            CATransform3D flipper = {
                1.0f, 0.0f, 0.0f, 0.0f,
                0.0f, -1.0f, 0.0f, 0.0f,
                0.0f, 0.0f, 1.0f, 0.0f,
                0.0f, 0.0f, 0.0f, 1.0f};
            [m_contentsLayer.get() setTransform:flipper];
        }
        [m_contentsLayer.get() setAnchorPoint:anchorPoint];

        // Insert the content layer first. Video elements require this, because they have
        // shadow content that must display in front of the video.
        [m_layer.get() insertSublayer:m_contentsLayer.get() atIndex:0];

        updateContentsRect();

#ifndef NDEBUG
        if (showDebugBorders()) {
            setLayerBorderColor(m_contentsLayer.get(), Color(0, 0, 128, 180));
            [m_contentsLayer.get() setBorderWidth:1.0f];
        }
#endif
    }
#ifndef NDEBUG
    updateDebugIndicators();
#endif

    END_BLOCK_OBJC_EXCEPTIONS
}

} // namespace WebCore


#endif // USE(ACCELERATED_COMPOSITING)