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 "FloatConversion.h"
#import "FloatRect.h"
#import "Image.h"
#import "PlatformString.h"
#import <QuartzCore/QuartzCore.h>
#import "RotateTransformOperation.h"
#import "ScaleTransformOperation.h"
#import "StringBuilder.h"
#import "SystemTime.h"
#import "TranslateTransformOperation.h"
#import "WebLayer.h"
#import "WebTiledLayer.h"
#import <limits.h>
#import <objc/objc-auto.h>
#import <wtf/CurrentTime.h>
#import <wtf/UnusedParam.h>
#import <wtf/RetainPtr.h>

#import "WAKAppKitStubs.h"
#import "WebCoreThread.h"
#import "SystemMemory.h"
#import <QuartzCore/QuartzCorePrivate.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 = 1024;

// 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 = 480;

// 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

@interface CALayer(Private)
- (void)setContentsChanged;
@end

@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 (!WebThreadIsCurrent())
        WebThreadLock();
    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

#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
@interface CALayer(GraphicsLayerCAPrivate)
- (void)setBoundsWithValue:(NSValue*)value;
- (void)setPositionWithValue:(NSValue*)value;
@end

@implementation CALayer(GraphicsLayerCAPrivate)
- (void)setBoundsWithValue:(NSValue*)value;
{
    [self setBounds:[value rectValue]];
}

- (void)setPositionWithValue:(NSValue*)value
{
    [self setPosition:[value pointValue]];
}
@end
#endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO)

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 TransformOperation* transformOp, TransformOperation::OperationType transformType, const IntSize& size)
{
    switch (transformType) {
        case TransformOperation::ROTATE:
        case TransformOperation::ROTATE_X:
        case TransformOperation::ROTATE_Y:
            return [NSNumber numberWithDouble:transformOp ? deg2rad(static_cast<const RotateTransformOperation*>(transformOp)->angle()) : 0];
        case TransformOperation::SCALE_X:
            return [NSNumber numberWithDouble:transformOp ? static_cast<const ScaleTransformOperation*>(transformOp)->x() : 1];
        case TransformOperation::SCALE_Y:
            return [NSNumber numberWithDouble:transformOp ? static_cast<const ScaleTransformOperation*>(transformOp)->y() : 1];
        case TransformOperation::SCALE_Z:
            return [NSNumber numberWithDouble:transformOp ? static_cast<const ScaleTransformOperation*>(transformOp)->z() : 1];
        case TransformOperation::TRANSLATE_X:
            return [NSNumber numberWithDouble:transformOp ? static_cast<const TranslateTransformOperation*>(transformOp)->x(size) : 0];
        case TransformOperation::TRANSLATE_Y:
            return [NSNumber numberWithDouble:transformOp ? static_cast<const TranslateTransformOperation*>(transformOp)->y(size) : 0];
        case TransformOperation::TRANSLATE_Z:
            return [NSNumber numberWithDouble:transformOp ? static_cast<const TranslateTransformOperation*>(transformOp)->z(size) : 0];
        case TransformOperation::SCALE:
        case TransformOperation::SCALE_3D:
            return [NSArray arrayWithObjects:
                        [NSNumber numberWithDouble:transformOp ? static_cast<const ScaleTransformOperation*>(transformOp)->x() : 1],
                        [NSNumber numberWithDouble:transformOp ? static_cast<const ScaleTransformOperation*>(transformOp)->y() : 1],
                        [NSNumber numberWithDouble:transformOp ? static_cast<const ScaleTransformOperation*>(transformOp)->z() : 1],
                        nil];
        case TransformOperation::TRANSLATE:
        case TransformOperation::TRANSLATE_3D:
            return [NSArray arrayWithObjects:
                        [NSNumber numberWithDouble:transformOp ? static_cast<const TranslateTransformOperation*>(transformOp)->x(size) : 0],
                        [NSNumber numberWithDouble:transformOp ? static_cast<const TranslateTransformOperation*>(transformOp)->y(size) : 0],
                        [NSNumber numberWithDouble:transformOp ? static_cast<const TranslateTransformOperation*>(transformOp)->z(size) : 0],
                        nil];
        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: {
            TransformationMatrix transform;
            if (transformOp)
                transformOp->apply(transform, size);
            CATransform3D caTransform;
            copyTransform(caTransform, transform);
            return [NSValue valueWithCATransform3D:caTransform];
        }
    }
    
    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;
        case TransformOperation::SCALE:
        case TransformOperation::SCALE_3D:
            return @"scale"; // kCAValueFunctionScale;
        case TransformOperation::TRANSLATE:
        case TransformOperation::TRANSLATE_3D:
            return @"translate"; // kCAValueFunctionTranslate;
        default:
            return nil;
    }
}
#endif

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

static String animationIdentifier(AnimatedPropertyID property, const String& keyframesName, int index)
{
    StringBuilder builder;

    builder.append(propertyIdToString(property));
    builder.append("_");

    if (!keyframesName.isEmpty()) {
        builder.append(keyframesName);
        builder.append("_");
    }
    builder.append("_");
    builder.append(String::number(index));
    return builder.toString();
}

#if !HAVE_MODERN_QUARTZCORE
static TransformationMatrix flipTransform()
{
    TransformationMatrix flipper;
    flipper.flipY();
    return flipper;
}
#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;
}

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];
}

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 void safeSetSublayers(CALayer* layer, NSArray* sublayers)
{
    // Workaround for <rdar://problem/7390716>: -[CALayer setSublayers:] crashes if sublayers is an empty array, or nil, under GC.
    if (objc_collectingEnabled() && ![sublayers count]) {
        while ([[layer sublayers] count])
            [[[layer sublayers] objectAtIndex:0] removeFromSuperlayer];
        return;
    }
    
    [layer setSublayers:sublayers];
}

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 CompositingCoordinatesTopDown;
}

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",
                             nullValue, @"zPosition",
                             nil];
    return actions;
}

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

GraphicsLayerCA::GraphicsLayerCA(GraphicsLayerClient* client)
    : GraphicsLayer(client)
    , m_contentsLayerPurpose(NoContentsLayer)
    , m_contentsLayerHasBackgroundColor(false)
    , m_uncommittedChanges(NoChange)
    , m_contentsScale(1.0f)
{
    BEGIN_BLOCK_OBJC_EXCEPTIONS
    m_layer.adoptNS([[WebLayer alloc] init]);
    [m_layer.get() setLayerOwner:this];

#if !HAVE_MODERN_QUARTZCORE
    setContentsOrientation(defaultContentsOrientation());
#endif

    updateDebugIndicators();

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

GraphicsLayerCA::~GraphicsLayerCA()
{
    // We release our references to the CALayers here, but do not actively unparent them,
    // since that will cause a commit and break our batched commit model. The layers will
    // get released when the rootmost modified GraphicsLayerCA rebuilds its child layers.
    
    BEGIN_BLOCK_OBJC_EXCEPTIONS

    // Clean up the WK layer.
    if (m_layer) {
        WebLayer* layer = m_layer.get();
        [layer setLayerOwner:nil];
    }
    
    if (m_contentsLayer) {
        if ([m_contentsLayer.get() respondsToSelector:@selector(setLayerOwner:)])
            [(id)m_contentsLayer.get() setLayerOwner:nil];
    }
    
    // animationDidStart: can fire after this, so we need to clear out the layer on the delegate.
    [m_animationDelegate.get() setLayer:0];

    // Release the clone layers inside the exception-handling block.
    removeCloneLayers();
    
    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);
    noteLayerPropertyChanged(NameChanged);
}

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

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

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

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

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

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

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

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

void GraphicsLayerCA::setMaskLayer(GraphicsLayer* layer)
{
    if (layer == m_maskLayer)
        return;

    GraphicsLayer::setMaskLayer(layer);
    noteLayerPropertyChanged(MaskLayerChanged);

    propagateLayerChangeToReplicas();
    
    if (m_replicatedLayer)
        static_cast<GraphicsLayerCA*>(m_replicatedLayer)->propagateLayerChangeToReplicas();
}

void GraphicsLayerCA::setReplicatedLayer(GraphicsLayer* layer)
{
    if (layer == m_replicatedLayer)
        return;

    GraphicsLayer::setReplicatedLayer(layer);
    noteLayerPropertyChanged(ReplicatedLayerChanged);
}

void GraphicsLayerCA::setReplicatedByLayer(GraphicsLayer* layer)
{
    if (layer == m_replicaLayer)
        return;

    GraphicsLayer::setReplicatedByLayer(layer);
    noteSublayersChanged();
    noteLayerPropertyChanged(ReplicatedLayerChanged);
}

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

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

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

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

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

    GraphicsLayer::setSize(size);
    noteLayerPropertyChanged(SizeChanged);
}

void GraphicsLayerCA::setTransform(const TransformationMatrix& t)
{
    if (t == m_transform)
        return;

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

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

    GraphicsLayer::setChildrenTransform(t);
    noteLayerPropertyChanged(ChildrenTransformChanged);
}

void GraphicsLayerCA::moveOrCopyAllAnimationsForProperty(MoveOrCopy operation, AnimatedPropertyID property, const String& keyframesName, CALayer *fromLayer, CALayer *toLayer)
{
    for (int index = 0; ; ++index) {
        String animName = animationIdentifier(property, keyframesName, index);

        CAAnimation* anim = [fromLayer animationForKey:animName];
        if (!anim)
            break;

        switch (operation) {
            case Move:
                [anim retain];
                [fromLayer removeAnimationForKey:animName];
                [toLayer addAnimation:anim forKey:animName];
                [anim release];
                break;

            case Copy:
                [toLayer addAnimation:anim forKey:animName];
                break;
        }
    }
}

void GraphicsLayerCA::moveOrCopyAnimationsForProperty(MoveOrCopy operation, AnimatedPropertyID property, CALayer *fromLayer, CALayer *toLayer)
{
    // Move transitions for this property.
    moveOrCopyAllAnimationsForProperty(operation, property, "", fromLayer, toLayer);
    
    // Look for running animations affecting this property.
    KeyframeAnimationsMap::const_iterator end = m_runningKeyframeAnimations.end();
    for (KeyframeAnimationsMap::const_iterator it = m_runningKeyframeAnimations.begin(); it != end; ++it)
        moveOrCopyAllAnimationsForProperty(operation, property, it->first, fromLayer, toLayer);
}

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

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

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

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

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

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

void GraphicsLayerCA::setBackgroundColor(const Color& color)
{
    if (m_backgroundColorSet && m_backgroundColor == color)
        return;

    GraphicsLayer::setBackgroundColor(color);

    m_contentsLayerHasBackgroundColor = true;
    noteLayerPropertyChanged(BackgroundColorChanged);
}

void GraphicsLayerCA::clearBackgroundColor()
{
    if (!m_backgroundColorSet)
        return;

    GraphicsLayer::clearBackgroundColor();
    m_contentsLayerHasBackgroundColor = false;
    noteLayerPropertyChanged(BackgroundColorChanged);
}

void GraphicsLayerCA::setContentsOpaque(bool opaque)
{
    if (m_contentsOpaque == opaque)
        return;

    GraphicsLayer::setContentsOpaque(opaque);
    noteLayerPropertyChanged(ContentsOpaqueChanged);
}

void GraphicsLayerCA::setBackfaceVisibility(bool visible)
{
    if (m_backfaceVisibility == visible)
        return;
    
    GraphicsLayer::setBackfaceVisibility(visible);
    noteLayerPropertyChanged(BackfaceVisibilityChanged);
}

void GraphicsLayerCA::setOpacity(float opacity)
{
    float clampedOpacity = max(0.0f, min(opacity, 1.0f));

    if (clampedOpacity == m_opacity)
        return;

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

void GraphicsLayerCA::setNeedsDisplay()
{
    FloatRect hugeRect(-numeric_limits<float>::max() / 2, -numeric_limits<float>::max() / 2,
                       numeric_limits<float>::max(), numeric_limits<float>::max());

    setNeedsDisplayInRect(hugeRect);
}

void GraphicsLayerCA::setNeedsDisplayInRect(const FloatRect& rect)
{
    if (!drawsContent())
        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 GraphicsLayerCA::setContentsNeedsDisplay()
{
    noteLayerPropertyChanged(ContentsNeedsDisplay);
}

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

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

bool GraphicsLayerCA::addAnimation(const KeyframeValueList& valueList, const IntSize& boxSize, const Animation* anim, const String& keyframesName, double timeOffset)
{
    if (forceSoftwareAnimation() || !anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2)
        return false;

#if !HAVE_MODERN_QUARTZCORE
    // Older versions of QuartzCore do not handle opacity in transform layers properly, so we will
    // always do software animation in that case.
    if (valueList.property() == AnimatedPropertyOpacity)
        return false;
#endif

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

    if (createdAnimations)
        noteLayerPropertyChanged(AnimationChanged);
        
    return createdAnimations;
}

void GraphicsLayerCA::removeAnimationsForProperty(AnimatedPropertyID property)
{
    if (m_transitionPropertiesToRemove.find(property) != m_transitionPropertiesToRemove.end())
        return;

    m_transitionPropertiesToRemove.add(property);
    noteLayerPropertyChanged(AnimationChanged);
}

void GraphicsLayerCA::removeAnimationsForKeyframes(const String& animationName)
{
    if (!animationIsRunning(animationName))
        return;

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

void GraphicsLayerCA::pauseAnimation(const String& keyframesName, double timeOffset)
{
    if (!animationIsRunning(keyframesName))
        return;

    AnimationsToProcessMap::iterator it = m_keyframeAnimationsToProcess.find(keyframesName);
    if (it != m_keyframeAnimationsToProcess.end()) {
        AnimationProcessingAction& processingInfo = it->second;
        // If an animation is scheduled to be removed, don't change the remove to a pause.
        if (processingInfo.action != Remove)
            processingInfo.action = Pause;
    } else
        m_keyframeAnimationsToProcess.add(keyframesName, AnimationProcessingAction(Pause, timeOffset));

    noteLayerPropertyChanged(AnimationChanged);
}

void GraphicsLayerCA::setContentsToImage(Image* image)
{
    if (image) {
        CGImageRef newImage = image->nativeImageForCurrentFrame();
        if (!newImage)
            return;

        // Check to see if the image changed; we have to do this because the call to
        // CGImageCreateCopyWithColorSpace() below can create a new image every time.
        if (m_uncorrectedContentsImage && m_uncorrectedContentsImage.get() == newImage)
            return;
        
        m_uncorrectedContentsImage = newImage;
        m_pendingContentsImage = newImage;
        m_contentsLayerPurpose = ContentsLayerForImage;
        if (!m_contentsLayer)
            noteSublayersChanged();
    } else {
        m_uncorrectedContentsImage = 0;
        m_pendingContentsImage = 0;
        m_contentsLayerPurpose = NoContentsLayer;
        if (m_contentsLayer)
            noteSublayersChanged();
    }

    noteLayerPropertyChanged(ContentsImageChanged);
}

void GraphicsLayerCA::setContentsToMedia(PlatformLayer* mediaLayer)
{
    if (mediaLayer == m_contentsLayer)
        return;

    m_contentsLayer = mediaLayer;
    m_contentsLayerPurpose = mediaLayer ? ContentsLayerForMedia : NoContentsLayer;

    noteSublayersChanged();
    noteLayerPropertyChanged(ContentsMediaLayerChanged);
}

PlatformLayer* GraphicsLayerCA::contentsLayerForMedia() const
{
    return (m_contentsLayerPurpose == ContentsLayerForMedia) ? m_contentsLayer.get() : 0;
}

void GraphicsLayerCA::setGeometryOrientation(CompositingCoordinatesOrientation orientation)
{
    if (orientation == m_geometryOrientation)
        return;

    GraphicsLayer::setGeometryOrientation(orientation);
    noteLayerPropertyChanged(GeometryOrientationChanged);

#if !HAVE_MODERN_QUARTZCORE
    // Geometry orientation is mapped onto children transform in older QuartzCores.
    switch (m_geometryOrientation) {
        case CompositingCoordinatesTopDown:
            setChildrenTransform(TransformationMatrix());
            break;
        
        case CompositingCoordinatesBottomUp:
            setChildrenTransform(flipTransform());
            break;
    }
#endif
}

void GraphicsLayerCA::didDisplay(PlatformLayer* layer)
{
    CALayer* sourceLayer;
    LayerMap* layerCloneMap;

    if (layer == m_layer) {
        sourceLayer = m_layer.get();
        layerCloneMap = m_layerClones.get();
    } else if (layer == m_contentsLayer) {
        sourceLayer = m_contentsLayer.get();
        layerCloneMap = m_contentsLayerClones.get();
    } else
        return;

    if (layerCloneMap) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currClone = it->second.get();
            if (!currClone)
                continue;

            if ([currClone contents] != [sourceLayer contents])
                [currClone setContents:[sourceLayer contents]];
            else
                [currClone setContentsChanged];
        }
    }
}

void GraphicsLayerCA::syncCompositingState()
{
    recursiveCommitChanges();
}

void GraphicsLayerCA::recursiveCommitChanges()
{
    commitLayerChangesBeforeSublayers();

    if (m_maskLayer)
        static_cast<GraphicsLayerCA*>(m_maskLayer)->commitLayerChangesBeforeSublayers();

    const Vector<GraphicsLayer*>& childLayers = children();
    size_t numChildren = childLayers.size();
    for (size_t i = 0; i < numChildren; ++i) {
        GraphicsLayerCA* curChild = static_cast<GraphicsLayerCA*>(childLayers[i]);
        curChild->recursiveCommitChanges();
    }

    if (m_replicaLayer)
        static_cast<GraphicsLayerCA*>(m_replicaLayer)->recursiveCommitChanges();

    if (m_maskLayer)
        static_cast<GraphicsLayerCA*>(m_maskLayer)->commitLayerChangesAfterSublayers();

    commitLayerChangesAfterSublayers();
}

void GraphicsLayerCA::commitLayerChangesBeforeSublayers()
{
    if (!m_uncommittedChanges)
        return;

    BEGIN_BLOCK_OBJC_EXCEPTIONS

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

    if (m_uncommittedChanges & NameChanged)
        updateLayerNames();

    if (m_uncommittedChanges & ContentsImageChanged) // Needs to happen before ChildrenChanged
        updateContentsImage();
        
    if (m_uncommittedChanges & ContentsMediaLayerChanged) // Needs to happen before ChildrenChanged
        updateContentsMediaLayer();
    
#if ENABLE(3D_CANVAS)
    if (m_uncommittedChanges & ContentsWebGLLayerChanged) // Needs to happen before ChildrenChanged
        updateContentsWebGLLayer();
#endif
    
    if (m_uncommittedChanges & BackgroundColorChanged)  // Needs to happen before ChildrenChanged, and after updating image or video
        updateLayerBackgroundColor();

    if (m_uncommittedChanges & ChildrenChanged)
        updateSublayerList();

    if (m_uncommittedChanges & PositionChanged)
        updateLayerPosition();
    
    if (m_uncommittedChanges & AnchorPointChanged)
        updateAnchorPoint();
    
    if (m_uncommittedChanges & SizeChanged)
        updateLayerSize();

    if (m_uncommittedChanges & TransformChanged)
        updateTransform();

    if (m_uncommittedChanges & ChildrenTransformChanged)
        updateChildrenTransform();
    
    if (m_uncommittedChanges & MasksToBoundsChanged)
        updateMasksToBounds();
    
    if (m_uncommittedChanges & DrawsContentChanged)
        updateLayerDrawsContent();

    if (m_uncommittedChanges & ContentsOpaqueChanged)
        updateContentsOpaque();

    if (m_uncommittedChanges & BackfaceVisibilityChanged)
        updateBackfaceVisibility();

    if (m_uncommittedChanges & OpacityChanged)
        updateOpacityOnLayer();
    
    if (m_uncommittedChanges & AnimationChanged)
        updateLayerAnimations();
    
    if (m_uncommittedChanges & DirtyRectsChanged)
        repaintLayerDirtyRects();
    
    if (m_uncommittedChanges & ContentsRectChanged)
        updateContentsRect();

    if (m_uncommittedChanges & GeometryOrientationChanged)
        updateGeometryOrientation();

    if (m_uncommittedChanges & MaskLayerChanged)
        updateMaskLayer();

    if (m_uncommittedChanges & ContentsNeedsDisplay)
        updateContentsNeedsDisplay();

    END_BLOCK_OBJC_EXCEPTIONS
}

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

    BEGIN_BLOCK_OBJC_EXCEPTIONS

    if (m_uncommittedChanges & ReplicatedLayerChanged)
        updateReplicatedLayers();

    m_uncommittedChanges = NoChange;
    END_BLOCK_OBJC_EXCEPTIONS
}

void GraphicsLayerCA::updateLayerNames()
{
    switch (structuralLayerPurpose()) {
        case StructuralLayerForPreserves3D:
            [m_structuralLayer.get() setName:("Transform layer " + name())];
            break;
        case StructuralLayerForReplicaFlattening:
            [m_structuralLayer.get() setName:("Replica flattening layer " + name())];
            break;
        case NoStructuralLayer:
            break;
    }
    [m_layer.get() setName:name()];
}

void GraphicsLayerCA::updateSublayerList()
{
    NSMutableArray* newSublayers = nil;

    const Vector<GraphicsLayer*>& childLayers = children();

    if (m_structuralLayer || m_contentsLayer || childLayers.size() > 0) {
        newSublayers = [[NSMutableArray alloc] init];

        if (m_structuralLayer) {
            // Add the replica layer first.
            if (m_replicaLayer)
                [newSublayers addObject:static_cast<GraphicsLayerCA*>(m_replicaLayer)->primaryLayer()];
            // Add the primary layer. Even if we have negative z-order children, the primary layer always comes behind.
            [newSublayers addObject:m_layer.get()];
        } else if (m_contentsLayer) {
            // FIXME: add the contents layer in the correct order with negative z-order children.
            // This does not cause visible rendering issues because currently contents layers are only used
            // for replaced elements that don't have children.
            [newSublayers addObject:m_contentsLayer.get()];
        }
        
        size_t numChildren = childLayers.size();
        for (size_t i = 0; i < numChildren; ++i) {
            GraphicsLayerCA* curChild = static_cast<GraphicsLayerCA*>(childLayers[i]);
            CALayer *childLayer = curChild->layerForSuperlayer();
            [newSublayers addObject:childLayer];
        }

        [newSublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
    }

    if (m_structuralLayer) {
        safeSetSublayers(m_structuralLayer.get(), newSublayers);

        if (m_contentsLayer) {
            // If we have a transform layer, then the contents layer is parented in the 
            // primary layer (which is itself a child of the transform layer).
            safeSetSublayers(m_layer.get(), nil);
            [m_layer.get() addSublayer:m_contentsLayer.get()];
        }
    } else
        safeSetSublayers(m_layer.get(), newSublayers);

    [newSublayers release];
}

void GraphicsLayerCA::updateLayerPosition()
{
    FloatSize usedSize = m_usingTiledLayer ? constrainedSize() : m_size;

    // Position is offset on the layer by the layer anchor point.
    CGPoint posPoint = CGPointMake(m_position.x() + m_anchorPoint.x() * usedSize.width(),
                                   m_position.y() + m_anchorPoint.y() * usedSize.height());
    
#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    if (mediaLayerMustBeUpdatedOnMainThread() && WebThreadIsCurrent())
        [primaryLayer() performSelectorOnMainThread:@selector(setPositionWithValue:) withObject:[NSValue valueWithPoint:NSPointFromCGPoint(posPoint)] waitUntilDone:NO];
    else
#endif
    [primaryLayer() setPosition:posPoint];

    if (LayerMap* layerCloneMap = primaryLayerClones()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CGPoint clonePosition = posPoint;
            if (m_replicaLayer && isReplicatedRootClone(it->first)) {
                // Maintain the special-case position for the root of a clone subtree,
                // which we set up in replicatedLayerRoot().
                clonePosition = positionForCloneRootLayer();
            }
            CALayer *currLayer = it->second.get();
            [currLayer setPosition:clonePosition];
        }
    }
}

void GraphicsLayerCA::updateLayerSize()
{
    CGRect rect = CGRectMake(0, 0, m_size.width(), m_size.height());
    if (m_structuralLayer) {
        [m_structuralLayer.get() setBounds:rect];
        
        if (LayerMap* layerCloneMap = m_structuralLayerClones.get()) {
            LayerMap::const_iterator end = layerCloneMap->end();
            for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it)
                [it->second.get() setBounds:rect];
        }

        // The anchor of the contents layer is always at 0.5, 0.5, so the position is center-relative.
        CGPoint centerPoint = CGPointMake(m_size.width() / 2.0f, m_size.height() / 2.0f);
        [m_layer.get() setPosition:centerPoint];

        if (LayerMap* layerCloneMap = m_layerClones.get()) {
            LayerMap::const_iterator end = layerCloneMap->end();
            for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it)
                [it->second.get() setPosition:centerPoint];
        }
    }
    
    bool needTiledLayer = requiresTiledLayer(m_size);
    if (needTiledLayer != m_usingTiledLayer)
        swapFromOrToTiledLayer(needTiledLayer);
    
    if (m_usingTiledLayer) {
        FloatSize sizeToUse = constrainedSize();
        rect = CGRectMake(0, 0, sizeToUse.width(), sizeToUse.height());
    }
    
#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    if (mediaLayerMustBeUpdatedOnMainThread() && WebThreadIsCurrent())
        [m_layer.get() performSelectorOnMainThread:@selector(setBoundsWithValue:) withObject:[NSValue valueWithRect:NSRectFromCGRect(rect)] waitUntilDone:NO];
    else
#endif
    [m_layer.get() setBounds:rect];
    if (LayerMap* layerCloneMap = m_layerClones.get()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it)
            [it->second.get() setBounds:rect];
    }
    
    // Contents transform may depend on height.
    updateContentsTransform();

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

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

void GraphicsLayerCA::updateAnchorPoint()
{
    [primaryLayer() setAnchorPoint:FloatPoint(m_anchorPoint.x(), m_anchorPoint.y())];
#if HAVE_MODERN_QUARTZCORE
    [primaryLayer() setAnchorPointZ:m_anchorPoint.z()];
#endif

    if (LayerMap* layerCloneMap = primaryLayerClones()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {   
            CALayer *currLayer = it->second.get();
            [currLayer setAnchorPoint:FloatPoint(m_anchorPoint.x(), m_anchorPoint.y())];
#if HAVE_MODERN_QUARTZCORE
            [currLayer setAnchorPointZ:m_anchorPoint.z()];
#endif
        }
    }

    updateLayerPosition();
}

void GraphicsLayerCA::updateTransform()
{
    CATransform3D transform;
    copyTransform(transform, m_transform);
    [primaryLayer() setTransform:transform];

    if (LayerMap* layerCloneMap = primaryLayerClones()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            if (m_replicaLayer && isReplicatedRootClone(it->first)) {
                // Maintain the special-case transform for the root of a clone subtree,
                // which we set up in replicatedLayerRoot().
                [currLayer setTransform:CATransform3DIdentity];
            } else
                [currLayer setTransform:transform];
        }
    }
}

void GraphicsLayerCA::updateChildrenTransform()
{
    CATransform3D transform;
    copyTransform(transform, m_childrenTransform);
    [primaryLayer() setSublayerTransform:transform];

    if (LayerMap* layerCloneMap = primaryLayerClones()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            [currLayer setSublayerTransform:transform];
        }
    }
}

void GraphicsLayerCA::updateMasksToBounds()
{
    [m_layer.get() setMasksToBounds:m_masksToBounds];

    if (LayerMap* layerCloneMap = m_layerClones.get()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            [currLayer setMasksToBounds:m_masksToBounds];
        }
    }

    updateDebugIndicators();
}

void GraphicsLayerCA::updateContentsOpaque()
{
    [m_layer.get() setOpaque:m_contentsOpaque];

    if (LayerMap* layerCloneMap = m_layerClones.get()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            [currLayer setOpaque:m_contentsOpaque];
        }
    }
}

void GraphicsLayerCA::updateBackfaceVisibility()
{
    if (m_structuralLayer && structuralLayerPurpose() == StructuralLayerForReplicaFlattening) {
        [m_structuralLayer.get() setDoubleSided:m_backfaceVisibility];

        if (LayerMap* layerCloneMap = m_structuralLayerClones.get()) {
            LayerMap::const_iterator end = layerCloneMap->end();
            for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
                CALayer *currLayer = it->second.get();
                [currLayer setDoubleSided:m_backfaceVisibility];
            }
        }
    }

    [m_layer.get() setDoubleSided:m_backfaceVisibility];

    if (LayerMap* layerCloneMap = m_layerClones.get()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            [currLayer setDoubleSided:m_backfaceVisibility];
        }
    }
}

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

void GraphicsLayerCA::ensureStructuralLayer(StructuralLayerPurpose purpose)
{
    if (purpose == NoStructuralLayer) {
        if (m_structuralLayer) {
            // Replace the transformLayer in the parent with this layer.
            [m_layer.get() removeFromSuperlayer];
            [[m_structuralLayer.get() superlayer] replaceSublayer:m_structuralLayer.get() with:m_layer.get()];

            moveOrCopyAnimationsForProperty(Move, AnimatedPropertyWebkitTransform, m_structuralLayer.get(), m_layer.get());
            moveOrCopyAnimationsForProperty(Move, AnimatedPropertyOpacity, m_structuralLayer.get(), m_layer.get());

            // Release the structural layer.
            m_structuralLayer = 0;

            // Update the properties of m_layer now that we no longer have a structural layer.
            updateLayerPosition();
            updateLayerSize();
            updateAnchorPoint();
            updateTransform();
            updateChildrenTransform();

            updateSublayerList();
            updateOpacityOnLayer();
        }
        return;
    }
    
    bool structuralLayerChanged = false;
    
    if (purpose == StructuralLayerForPreserves3D) {
        Class transformLayerClass = NSClassFromString(@"CATransformLayer");
        if (!transformLayerClass)
            return;

        if (m_structuralLayer && ![m_structuralLayer.get() isKindOfClass:transformLayerClass])
            m_structuralLayer = 0;
        
        if (!m_structuralLayer) {
            m_structuralLayer.adoptNS([[transformLayerClass alloc] init]);
            structuralLayerChanged = true;
        }
    } else {
        if (m_structuralLayer && ![m_structuralLayer.get() isMemberOfClass:[CALayer self]])
            m_structuralLayer = 0;

        if (!m_structuralLayer) {
            m_structuralLayer.adoptNS([[CALayer alloc] init]);
            structuralLayerChanged = true;
        }
    }
    
    if (!structuralLayerChanged)
        return;
    
    // Turn off default animations.
    [m_structuralLayer.get() setStyle:[NSDictionary dictionaryWithObject:nullActionsDictionary() forKey:@"actions"]];

    updateLayerNames();

    // Update the properties of the structural layer.
    updateLayerPosition();
    updateLayerSize();
    updateAnchorPoint();
    updateTransform();
    updateChildrenTransform();
    updateBackfaceVisibility();
    
    // Set properties of m_layer to their default values, since these are expressed on on the structural layer.
    CGPoint point = CGPointMake(m_size.width() / 2.0f, m_size.height() / 2.0f);
    [m_layer.get() setPosition:point];
    [m_layer.get() setAnchorPoint:CGPointMake(0.5f, 0.5f)];
    [m_layer.get() setTransform:CATransform3DIdentity];
    [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_structuralLayer.get()];
    [m_structuralLayer.get() addSublayer:m_layer.get()];

    moveOrCopyAnimationsForProperty(Move, AnimatedPropertyWebkitTransform, m_layer.get(), m_structuralLayer.get());
    moveOrCopyAnimationsForProperty(Move, AnimatedPropertyOpacity, m_layer.get(), m_structuralLayer.get());
    
    updateSublayerList();
    updateOpacityOnLayer();
}

GraphicsLayerCA::StructuralLayerPurpose GraphicsLayerCA::structuralLayerPurpose() const
{
    if (preserves3D())
        return StructuralLayerForPreserves3D;
    
    if (isReplicated())
        return StructuralLayerForReplicaFlattening;
    
    return NoStructuralLayer;
}

void GraphicsLayerCA::updateLayerDrawsContent()
{
    bool needTiledLayer = requiresTiledLayer(m_size);
    if (needTiledLayer != m_usingTiledLayer)
        swapFromOrToTiledLayer(needTiledLayer);

    if (m_drawsContent)
        [m_layer.get() setNeedsDisplay];
    else
        [m_layer.get() setContents:nil];

    updateDebugIndicators();
}

void GraphicsLayerCA::updateLayerBackgroundColor()
{
    if (!m_contentsLayer)
        return;

    // We never create the contents layer just for background color yet.
    if (m_backgroundColorSet)
        setLayerBackgroundColor(m_contentsLayer.get(), m_backgroundColor);
    else
        clearLayerBackgroundColor(m_contentsLayer.get());
}

void GraphicsLayerCA::updateContentsImage()
{
    if (m_pendingContentsImage) {
        if (!m_contentsLayer.get()) {
            WebLayer* imageLayer = [WebLayer layer];
#ifndef NDEBUG
            [imageLayer setName:@"Image Layer"];
#endif
            setupContentsLayer(imageLayer);
            m_contentsLayer.adoptNS([imageLayer retain]);
            // m_contentsLayer will be parented by updateSublayerList
        }

        // FIXME: maybe only do trilinear if the image is being scaled down,
        // but then what if the layer size changes?
        [m_contentsLayer.get() setMinificationFilter:kCAFilterTrilinear];
        [m_contentsLayer.get() setContents:(id)m_pendingContentsImage.get()];
        m_pendingContentsImage = 0;

        if (m_contentsLayerClones) {
            LayerMap::const_iterator end = m_contentsLayerClones->end();
            for (LayerMap::const_iterator it = m_contentsLayerClones->begin(); it != end; ++it)
                [it->second.get() setContents:[m_contentsLayer.get() contents]];
        }
        
        updateContentsRect();
    } else {
        // No image.
        // m_contentsLayer will be removed via updateSublayerList.
        m_contentsLayer = 0;
    }
}

void GraphicsLayerCA::updateContentsMediaLayer()
{
    // Video layer was set as m_contentsLayer, and will get parented in updateSublayerList().
    if (m_contentsLayer) {
        setupContentsLayer(m_contentsLayer.get());
        updateContentsRect();
    }
}

#if ENABLE(3D_CANVAS)
void GraphicsLayerCA::updateContentsWebGLLayer()
{
    // WebGLLayer was set as m_contentsLayer, and will get parented in updateSublayerList().
    if (m_contentsLayer) {
        setupContentsLayer(m_contentsLayer.get());
        [m_contentsLayer.get() setNeedsDisplay];
        updateContentsRect();
    }
}
#endif
    
void GraphicsLayerCA::updateContentsRect()
{
    if (!m_contentsLayer)
        return;

    CGPoint point = CGPointMake(m_contentsRect.x(),
                                m_contentsRect.y());
    CGRect rect = CGRectMake(0.0f,
                             0.0f,
                             m_contentsRect.width(),
                             m_contentsRect.height());

#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
    if (mediaLayerMustBeUpdatedOnMainThread() && WebThreadIsCurrent()) {
        [m_contentsLayer.get() performSelectorOnMainThread:@selector(setPositionWithValue:) withObject:[NSValue valueWithPoint:NSPointFromCGPoint(point)] waitUntilDone:NO];
        [m_contentsLayer.get() performSelectorOnMainThread:@selector(setBoundsWithValue:) withObject:[NSValue valueWithRect:NSRectFromCGRect(rect)] waitUntilDone:NO];
    } else {
        [m_contentsLayer.get() setPosition:point];
        [m_contentsLayer.get() setBounds:rect];
    }
#else
    [m_contentsLayer.get() setPosition:point];
    [m_contentsLayer.get() setBounds:rect];
#endif

    if (m_contentsLayerClones) {
        LayerMap::const_iterator end = m_contentsLayerClones->end();
        for (LayerMap::const_iterator it = m_contentsLayerClones->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            [currLayer setPosition:point];
            [currLayer setBounds:rect];
        }
    }
}

void GraphicsLayerCA::updateGeometryOrientation()
{
#if HAVE_MODERN_QUARTZCORE
    switch (geometryOrientation()) {
        case CompositingCoordinatesTopDown:
            [m_layer.get() setGeometryFlipped:NO];
            break;
        
        case CompositingCoordinatesBottomUp:
            [m_layer.get() setGeometryFlipped:YES];
            break;
    }
    // Geometry orientation is mapped onto children transform in older QuartzCores,
    // so is handled via setGeometryOrientation().
#endif
}

void GraphicsLayerCA::updateMaskLayer()
{
    CALayer *maskCALayer = m_maskLayer ? m_maskLayer->platformLayer() : 0;
    [m_layer.get() setMask:maskCALayer];

    LayerMap* maskLayerCloneMap = m_maskLayer ? static_cast<GraphicsLayerCA*>(m_maskLayer)->primaryLayerClones() : 0;
    
    if (LayerMap* layerCloneMap = m_layerClones.get()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            
            CALayer *maskClone = maskLayerCloneMap ? maskLayerCloneMap->get(it->first).get() : 0;
            [currLayer setMask:maskClone];
        }
    }
}

void GraphicsLayerCA::updateReplicatedLayers()
{
    // Clone the descendants of the replicated layer, and parent under us.
    ReplicaState replicaState(ReplicaState::ReplicaBranch);

    CALayer *replicaRoot = replicatedLayerRoot(replicaState);
    if (!replicaRoot)
        return;

    if (m_structuralLayer)
        [m_structuralLayer.get() insertSublayer:replicaRoot atIndex:0];
    else
        [m_layer.get() insertSublayer:replicaRoot atIndex:0];
}

// For now, this assumes that layers only ever have one replica, so replicaIndices contains only 0 and 1.
GraphicsLayerCA::CloneID GraphicsLayerCA::ReplicaState::cloneID() const
{
    size_t depth = m_replicaBranches.size();

    const size_t bitsPerUChar = sizeof(UChar) * 8;
    size_t vectorSize = (depth + bitsPerUChar - 1) / bitsPerUChar;
    
    Vector<UChar> result(vectorSize);
    result.fill(0);

    // Create a string from the bit sequence which we can use to identify the clone.
    // Note that the string may contain embedded nulls, but that's OK.
    for (size_t i = 0; i < depth; ++i) {
        UChar& currChar = result[i / bitsPerUChar];
        currChar = (currChar << 1) | m_replicaBranches[i];
    }
    
    return String::adopt(result);
}

CALayer *GraphicsLayerCA::replicatedLayerRoot(ReplicaState& replicaState)
{
    // Limit replica nesting, to avoid 2^N explosion of replica layers.
    if (!m_replicatedLayer || replicaState.replicaDepth() == ReplicaState::maxReplicaDepth)
        return nil;

    GraphicsLayerCA* replicatedLayer = static_cast<GraphicsLayerCA*>(m_replicatedLayer);
    
    CALayer *clonedLayerRoot = replicatedLayer->fetchCloneLayers(this, replicaState, RootCloneLevel);
    FloatPoint cloneRootPosition = replicatedLayer->positionForCloneRootLayer();

    // Replica root has no offset or transform
    [clonedLayerRoot setPosition:cloneRootPosition];
    [clonedLayerRoot setTransform:CATransform3DIdentity];

    return clonedLayerRoot;
}

void GraphicsLayerCA::updateLayerAnimations()
{
    if (m_transitionPropertiesToRemove.size()) {
        HashSet<int>::const_iterator end = m_transitionPropertiesToRemove.end();
        for (HashSet<AnimatedProperty>::const_iterator it = m_transitionPropertiesToRemove.begin(); it != end; ++it) {
            AnimatedPropertyID currProperty = static_cast<AnimatedPropertyID>(*it);
            // Remove all animations with this property in the key.
            for (int index = 0; ; ++index) {
                if (!removeAnimationFromLayer(currProperty, "", index))
                    break;
            }
        }

        m_transitionPropertiesToRemove.clear();
    }

    if (m_keyframeAnimationsToProcess.size()) {
        AnimationsToProcessMap::const_iterator end = m_keyframeAnimationsToProcess.end();
        for (AnimationsToProcessMap::const_iterator it = m_keyframeAnimationsToProcess.begin(); it != end; ++it) {
            const String& currKeyframeName = it->first;
            KeyframeAnimationsMap::iterator animationIt = m_runningKeyframeAnimations.find(currKeyframeName);
            if (animationIt == m_runningKeyframeAnimations.end())
                continue;

            const AnimationProcessingAction& processingInfo = it->second;
            const Vector<AnimationPair>& animations = animationIt->second;
            for (size_t i = 0; i < animations.size(); ++i) {
                const AnimationPair& currPair = animations[i];
                switch (processingInfo.action) {
                    case Remove:
                        removeAnimationFromLayer(static_cast<AnimatedPropertyID>(currPair.first), currKeyframeName, currPair.second);
                        break;
                    case Pause:
                        pauseAnimationOnLayer(static_cast<AnimatedPropertyID>(currPair.first), currKeyframeName, currPair.second, processingInfo.timeOffset);
                        break;
                }
            }

            if (processingInfo.action == Remove)
                m_runningKeyframeAnimations.remove(currKeyframeName);
        }
    
        m_keyframeAnimationsToProcess.clear();
    }
    
    size_t numAnimations;
    if ((numAnimations = m_uncomittedAnimations.size())) {
        for (size_t i = 0; i < numAnimations; ++i) {
            const LayerAnimation& pendingAnimation = m_uncomittedAnimations[i];
            setAnimationOnLayer(pendingAnimation.m_animation.get(), pendingAnimation.m_property, pendingAnimation.m_keyframesName, pendingAnimation.m_index, pendingAnimation.m_timeOffset);
            
            if (!pendingAnimation.m_keyframesName.isEmpty()) {
                // If this is a keyframe anim, we have to remember the association of keyframes name to property/index pairs,
                // so we can remove the animations later if needed.
                // For transitions, we can just generate animation names with property and index.
                KeyframeAnimationsMap::iterator it = m_runningKeyframeAnimations.find(pendingAnimation.m_keyframesName);
                if (it == m_runningKeyframeAnimations.end()) {
                    Vector<AnimationPair> firstPair;
                    firstPair.append(AnimationPair(pendingAnimation.m_property, pendingAnimation.m_index));
                    m_runningKeyframeAnimations.add(pendingAnimation.m_keyframesName, firstPair);
                } else {
                    Vector<AnimationPair>& animPairs = it->second;
                    animPairs.append(AnimationPair(pendingAnimation.m_property, pendingAnimation.m_index));
                }
            }
        }
        
        m_uncomittedAnimations.clear();
    }
}

void GraphicsLayerCA::setAnimationOnLayer(CAPropertyAnimation* caAnim, AnimatedPropertyID property, const String& keyframesName, int index, double timeOffset)
{
    PlatformLayer* layer = animatedLayer(property);

    [caAnim setTimeOffset:timeOffset];
    
    String animationName = animationIdentifier(property, keyframesName, index);
    
    [layer removeAnimationForKey:animationName];
    [layer addAnimation:caAnim forKey:animationName];

    if (LayerMap* layerCloneMap = animatedLayerClones(property)) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            // Skip immediate replicas, since they move with the original.
            if (m_replicaLayer && isReplicatedRootClone(it->first))
                continue;
            CALayer *currLayer = it->second.get();
            [currLayer removeAnimationForKey:animationName];
            [currLayer addAnimation:caAnim forKey:animationName];
        }
    }
}

// Workaround for <rdar://problem/7311367>
static void bug7311367Workaround(CALayer* transformLayer, const TransformationMatrix& transform)
{
    if (!transformLayer)
        return;

    CATransform3D caTransform;
    copyTransform(caTransform, transform);
    caTransform.m41 += 1;
    [transformLayer setTransform:caTransform];

    caTransform.m41 -= 1;
    [transformLayer setTransform:caTransform];
}

bool GraphicsLayerCA::removeAnimationFromLayer(AnimatedPropertyID property, const String& keyframesName, int index)
{
    PlatformLayer* layer = animatedLayer(property);

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

    if (![layer animationForKey:animationName])
        return false;
    
    [layer removeAnimationForKey:animationName];
    bug7311367Workaround(m_structuralLayer.get(), m_transform);

    if (LayerMap* layerCloneMap = animatedLayerClones(property)) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            // Skip immediate replicas, since they move with the original.
            if (m_replicaLayer && isReplicatedRootClone(it->first))
                continue;

            CALayer *currLayer = it->second.get();
            [currLayer removeAnimationForKey:animationName];
        }
    }
    return true;
}

static void copyAnimationProperties(CAPropertyAnimation* from, CAPropertyAnimation* to)
{
    [to setBeginTime:[from beginTime]];
    [to setDuration:[from duration]];
    [to setRepeatCount:[from repeatCount]];
    [to setAutoreverses:[from autoreverses]];
    [to setFillMode:[from fillMode]];
    [to setRemovedOnCompletion:[from isRemovedOnCompletion]];
    [to setAdditive:[from isAdditive]];
    [to setTimingFunction:[from timingFunction]];

#if HAVE_MODERN_QUARTZCORE
    [to setValueFunction:[from valueFunction]];
#endif
}

void GraphicsLayerCA::pauseAnimationOnLayer(AnimatedPropertyID property, const String& keyframesName, int index, double timeOffset)
{
    PlatformLayer* layer = animatedLayer(property);

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

    CAAnimation* caAnim = [layer animationForKey:animationName];
    if (!caAnim)
        return;

    // Animations on the layer are immutable, so we have to clone and modify.
    CAPropertyAnimation* pausedAnim = nil;
    if ([caAnim isKindOfClass:[CAKeyframeAnimation class]]) {
        CAKeyframeAnimation* existingKeyframeAnim = static_cast<CAKeyframeAnimation*>(caAnim);
        CAKeyframeAnimation* newAnim = [CAKeyframeAnimation animationWithKeyPath:[existingKeyframeAnim keyPath]];
        copyAnimationProperties(existingKeyframeAnim, newAnim);
        [newAnim setValues:[existingKeyframeAnim values]];
        [newAnim setKeyTimes:[existingKeyframeAnim keyTimes]];
        [newAnim setTimingFunctions:[existingKeyframeAnim timingFunctions]];
        pausedAnim = newAnim;
    } else if ([caAnim isKindOfClass:[CABasicAnimation class]]) {
        CABasicAnimation* existingPropertyAnim = static_cast<CABasicAnimation*>(caAnim);
        CABasicAnimation* newAnim = [CABasicAnimation animationWithKeyPath:[existingPropertyAnim keyPath]];
        copyAnimationProperties(existingPropertyAnim, newAnim);
        [newAnim setFromValue:[existingPropertyAnim fromValue]];
        [newAnim setToValue:[existingPropertyAnim toValue]];
        pausedAnim = newAnim;
    }

    // pausedAnim has the beginTime of caAnim already.
    [pausedAnim setSpeed:0];
    [pausedAnim setTimeOffset:timeOffset];
    
    [layer addAnimation:pausedAnim forKey:animationName];  // This will replace the running animation.

    // Pause the animations on the clones too.
    if (LayerMap* layerCloneMap = animatedLayerClones(property)) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            // Skip immediate replicas, since they move with the original.
            if (m_replicaLayer && isReplicatedRootClone(it->first))
                continue;
            CALayer *currLayer = it->second.get();
            [currLayer addAnimation:pausedAnim forKey:animationName];
        }
    }
}

#if ENABLE(3D_CANVAS)
void GraphicsLayerCA::setContentsToWebGL(PlatformLayer* webglLayer)
{
    if (webglLayer == m_contentsLayer)
        return;
        
    m_contentsLayer = webglLayer;
    if (m_contentsLayer && [m_contentsLayer.get() respondsToSelector:@selector(setLayerOwner:)])
        [(id)m_contentsLayer.get() setLayerOwner:this];
    
    m_contentsLayerPurpose = webglLayer ? ContentsLayerForWebGL : NoContentsLayer;

    noteSublayersChanged();
    noteLayerPropertyChanged(ContentsWebGLLayerChanged);
}
#endif
    
void GraphicsLayerCA::repaintLayerDirtyRects()
{
    if (!m_dirtyRects.size())
        return;

    for (size_t i = 0; i < m_dirtyRects.size(); ++i)
        [m_layer.get() setNeedsDisplayInRect:m_dirtyRects[i]];
    
    m_dirtyRects.clear();
}

void GraphicsLayerCA::updateContentsNeedsDisplay()
{
    if (m_contentsLayer)
        [m_contentsLayer.get() setNeedsDisplay];
}

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

    BEGIN_BLOCK_OBJC_EXCEPTIONS;
    
    bool isKeyframe = valueList.size() > 2;
    bool valuesOK;
    
    bool additive = false;
    int animationIndex = 0;
    
    CAPropertyAnimation* caAnimation;
    if (isKeyframe) {
        CAKeyframeAnimation* keyframeAnim = createKeyframeAnimation(animation, valueList.property(), additive);
        valuesOK = setAnimationKeyframes(valueList, animation, keyframeAnim);
        caAnimation = keyframeAnim;
    } else {
        CABasicAnimation* basicAnim = createBasicAnimation(animation, valueList.property(), additive);
        valuesOK = setAnimationEndpoints(valueList, animation, basicAnim);
        caAnimation = basicAnim;
    }
    
    if (!valuesOK)
        return false;

    m_uncomittedAnimations.append(LayerAnimation(caAnimation, keyframesName, valueList.property(), animationIndex, timeOffset));
    
    END_BLOCK_OBJC_EXCEPTIONS;

    return true;
}

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

    TransformOperationList functionList;
    bool listsMatch, hasBigRotation;
    fetchTransformOperationList(valueList, functionList, listsMatch, 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;

    bool validMatrices = true;

    BEGIN_BLOCK_OBJC_EXCEPTIONS;

    // If functionLists 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 = !listsMatch || !caValueFunctionSupported();
    
    size_t numAnimations = isMatrixAnimation ? 1 : functionList.size();
    bool isKeyframe = valueList.size() > 2;
    
    // Iterate through the transform functions, sending an animation for each one.
    for (size_t animationIndex = 0; animationIndex < numAnimations; ++animationIndex) {
        TransformOperation::OperationType transformOp = isMatrixAnimation ? TransformOperation::MATRIX_3D : functionList[animationIndex];
        CAPropertyAnimation* caAnimation;

        bool additive = animationIndex > 0;
        if (isKeyframe) {
            CAKeyframeAnimation* keyframeAnim = createKeyframeAnimation(animation, valueList.property(), additive);
            validMatrices = setTransformAnimationKeyframes(valueList, animation, keyframeAnim, animationIndex, transformOp, isMatrixAnimation, boxSize);
            caAnimation = keyframeAnim;
        } else {
            CABasicAnimation* basicAnim = createBasicAnimation(animation, valueList.property(), additive);
            validMatrices = setTransformAnimationEndpoints(valueList, animation, basicAnim, animationIndex, transformOp, isMatrixAnimation, boxSize);
            caAnimation = basicAnim;
        }
        
        if (!validMatrices)
            break;
    
        m_uncomittedAnimations.append(LayerAnimation(caAnimation, keyframesName, valueList.property(), animationIndex, timeOffset));
    }

    END_BLOCK_OBJC_EXCEPTIONS;

    return validMatrices;
}

CABasicAnimation* GraphicsLayerCA::createBasicAnimation(const Animation* anim, AnimatedPropertyID property, bool additive)
{
    CABasicAnimation* basicAnim = [CABasicAnimation animationWithKeyPath:propertyIdToString(property)];
    setupAnimation(basicAnim, anim, additive);
    return basicAnim;
}

CAKeyframeAnimation* GraphicsLayerCA::createKeyframeAnimation(const Animation* anim, AnimatedPropertyID property, bool additive)
{
    CAKeyframeAnimation* keyframeAnim = [CAKeyframeAnimation animationWithKeyPath:propertyIdToString(property)];
    setupAnimation(keyframeAnim, anim, additive);
    return keyframeAnim;
}

void GraphicsLayerCA::setupAnimation(CAPropertyAnimation* propertyAnim, const Animation* anim, bool additive)
{
    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;

    NSString* fillMode = 0;
    switch (anim->fillMode()) {
    case AnimationFillModeNone:
        fillMode = kCAFillModeForwards; // 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 = kCAFillModeBoth; // 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 = kCAFillModeForwards;
       break;
    case AnimationFillModeBoth:
       fillMode = kCAFillModeBoth;
       break;
    }

    [propertyAnim setDuration:duration];
    [propertyAnim setRepeatCount:repeatCount];
    [propertyAnim setAutoreverses:anim->direction()];
    [propertyAnim setRemovedOnCompletion:NO];
    [propertyAnim setAdditive:additive];
    [propertyAnim setFillMode:fillMode];

    [propertyAnim setDelegate:m_animationDelegate.get()];
}

CAMediaTimingFunction* GraphicsLayerCA::timingFunctionForAnimationValue(const AnimationValue* animValue, const Animation* anim)
{
    const TimingFunction* tf = 0;
    if (animValue->timingFunction())
        tf = animValue->timingFunction();
    else if (anim->isTimingFunctionSet())
        tf = &anim->timingFunction();

    return getCAMediaTimingFunction(tf ? *tf : TimingFunction());
}

bool GraphicsLayerCA::setAnimationEndpoints(const KeyframeValueList& valueList, const Animation* anim, CABasicAnimation* basicAnim)
{
    id fromValue = nil;
    id toValue = nil;

    switch (valueList.property()) {
        case AnimatedPropertyOpacity: {
            const FloatAnimationValue* startVal = static_cast<const FloatAnimationValue*>(valueList.at(0));
            const FloatAnimationValue* endVal = static_cast<const FloatAnimationValue*>(valueList.at(1));
            fromValue = [NSNumber numberWithFloat:startVal->value()];
            toValue = [NSNumber numberWithFloat:endVal->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.
    CAMediaTimingFunction* timingFunction = timingFunctionForAnimationValue(valueList.at(0), anim);
    [basicAnim setTimingFunction:timingFunction];

    [basicAnim setFromValue:fromValue];
    [basicAnim setToValue:toValue];

    return true;
}

bool GraphicsLayerCA::setAnimationKeyframes(const KeyframeValueList& valueList, const Animation* anim, CAKeyframeAnimation* keyframeAnim)
{
    RetainPtr<NSMutableArray> keyTimes(AdoptNS, [[NSMutableArray alloc] init]);
    RetainPtr<NSMutableArray> values(AdoptNS, [[NSMutableArray alloc] init]);
    RetainPtr<NSMutableArray> timingFunctions(AdoptNS, [[NSMutableArray alloc] init]);

    for (unsigned i = 0; i < valueList.size(); ++i) {
        const AnimationValue* curValue = valueList.at(i);
        [keyTimes.get() addObject:[NSNumber numberWithFloat:curValue->keyTime()]];

        switch (valueList.property()) {
            case AnimatedPropertyOpacity: {
                const FloatAnimationValue* floatValue = static_cast<const FloatAnimationValue*>(curValue);
                [values.get() addObject:[NSNumber numberWithFloat:floatValue->value()]];
                break;
            }
            default:
                ASSERT_NOT_REACHED();     // we don't animate color yet
                break;
        }

        CAMediaTimingFunction* timingFunction = timingFunctionForAnimationValue(curValue, anim);
        [timingFunctions.get() addObject:timingFunction];
    }
    
    // We toss the last tfArray value because it has to one shorter than the others.
    [timingFunctions.get() removeLastObject];

    [keyframeAnim setKeyTimes:keyTimes.get()];
    [keyframeAnim setValues:values.get()];
    [keyframeAnim setTimingFunctions:timingFunctions.get()];
    
    return true;
}

bool GraphicsLayerCA::setTransformAnimationEndpoints(const KeyframeValueList& valueList, const Animation* anim, CABasicAnimation* basicAnim, int functionIndex, TransformOperation::OperationType transformOp, bool isMatrixAnimation, const IntSize& boxSize)
{
    id fromValue;
    id toValue;
    
    ASSERT(valueList.size() == 2);
    const TransformAnimationValue* startValue = static_cast<const TransformAnimationValue*>(valueList.at(0));
    const TransformAnimationValue* endValue = static_cast<const TransformAnimationValue*>(valueList.at(1));
    
    if (isMatrixAnimation) {
        TransformationMatrix fromTransform, toTransform;
        startValue->value()->apply(boxSize, fromTransform);
        endValue->value()->apply(boxSize, toTransform);

        // If any matrix is singular, CA won't animate it correctly. So fall back to software animation
        if (!fromTransform.isInvertible() || !toTransform.isInvertible())
            return false;

        CATransform3D caTransform;
        copyTransform(caTransform, fromTransform);
        fromValue = [NSValue valueWithCATransform3D:caTransform];

        copyTransform(caTransform, toTransform);
        toValue = [NSValue valueWithCATransform3D:caTransform];
    } else {
        fromValue = getTransformFunctionValue(startValue->value()->at(functionIndex), transformOp, boxSize);
        toValue = getTransformFunctionValue(endValue->value()->at(functionIndex), transformOp, boxSize);
    }

    // This codepath is used for 2-keyframe animations, so we still need to look in the start
    // for a timing function.
    CAMediaTimingFunction* timingFunction = timingFunctionForAnimationValue(valueList.at(0), anim);
    [basicAnim setTimingFunction:timingFunction];

    [basicAnim setFromValue:fromValue];
    [basicAnim setToValue:toValue];

#if HAVE_MODERN_QUARTZCORE
    if (NSString* valueFunctionName = getValueFunctionNameForTransformOperation(transformOp))
        [basicAnim setValueFunction:[CAValueFunction functionWithName:valueFunctionName]];
#endif

    return true;
}

bool GraphicsLayerCA::setTransformAnimationKeyframes(const KeyframeValueList& valueList, const Animation* animation, CAKeyframeAnimation* keyframeAnim, int functionIndex, TransformOperation::OperationType transformOpType, bool isMatrixAnimation, const IntSize& boxSize)
{
    RetainPtr<NSMutableArray> keyTimes(AdoptNS, [[NSMutableArray alloc] init]);
    RetainPtr<NSMutableArray> values(AdoptNS, [[NSMutableArray alloc] init]);
    RetainPtr<NSMutableArray> timingFunctions(AdoptNS, [[NSMutableArray alloc] init]);

    for (unsigned i = 0; i < valueList.size(); ++i) {
        const TransformAnimationValue* curValue = static_cast<const TransformAnimationValue*>(valueList.at(i));
        [keyTimes.get() addObject:[NSNumber numberWithFloat:curValue->keyTime()]];

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

            // If any matrix is singular, CA won't animate it correctly. So fall back to software animation
            if (!transform.isInvertible())
                return false;

            CATransform3D caTransform;
            copyTransform(caTransform, transform);
            [values.get() addObject:[NSValue valueWithCATransform3D:caTransform]];
        } else {
            const TransformOperation* transformOp = curValue->value()->at(functionIndex);
            [values.get() addObject:getTransformFunctionValue(transformOp, transformOpType, boxSize)];
        }

        CAMediaTimingFunction* timingFunction = timingFunctionForAnimationValue(curValue, animation);
        [timingFunctions.get() addObject:timingFunction];
    }
    
    // We toss the last tfArray value because it has to one shorter than the others.
    [timingFunctions.get() removeLastObject];

    [keyframeAnim setKeyTimes:keyTimes.get()];
    [keyframeAnim setValues:values.get()];
    [keyframeAnim setTimingFunctions:timingFunctions.get()];

#if HAVE_MODERN_QUARTZCORE
    if (NSString* valueFunctionName = getValueFunctionNameForTransformOperation(transformOpType))
        [keyframeAnim setValueFunction:[CAValueFunction functionWithName:valueFunctionName]];
#endif
    return true;
}

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

    // Suspend the animations on the clones too.
    if (LayerMap* layerCloneMap = primaryLayerClones()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            [currLayer setSpeed:0 ];
            [currLayer setTimeOffset:t];
        }
    }
}

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

    // Resume the animations on the clones too.
    if (LayerMap* layerCloneMap = primaryLayerClones()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            CALayer *currLayer = it->second.get();
            [currLayer setSpeed:1];
            [currLayer setTimeOffset:0];
        }
    }
}

CALayer* GraphicsLayerCA::hostLayerForSublayers() const
{
    return m_structuralLayer.get() ? m_structuralLayer.get() : m_layer.get(); 
}

CALayer* GraphicsLayerCA::layerForSuperlayer() const
{
    return m_structuralLayer ? m_structuralLayer.get() : m_layer.get();
}

CALayer* GraphicsLayerCA::animatedLayer(AnimatedPropertyID property) const
{
    return (property == AnimatedPropertyBackgroundColor) ? m_contentsLayer.get() : primaryLayer();
}

GraphicsLayerCA::LayerMap* GraphicsLayerCA::animatedLayerClones(AnimatedPropertyID property) const
{
    return (property == AnimatedPropertyBackgroundColor) ? m_contentsLayerClones.get() : primaryLayerClones();
}

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

void GraphicsLayerCA::setContentsScale(float scale)
{
    float newScale = clampedContentsScaleForScale(scale);
    if (newScale == m_contentsScale)
        return;

    m_contentsScale = newScale;

    BEGIN_BLOCK_OBJC_EXCEPTIONS

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

    if ([m_layer.get() isKindOfClass:[WebLayer self]]) {
        [m_layer.get() setContentsScale:m_contentsScale];
        [m_layer.get() setNeedsDisplay];
    }
    
    END_BLOCK_OBJC_EXCEPTIONS    
}

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
}

FloatSize GraphicsLayerCA::constrainedSize() const
{
    float tileColumns = ceilf(m_size.width() / cTiledLayerTileSize);
    float tileRows = ceilf(m_size.height() / cTiledLayerTileSize);
    double numTiles = tileColumns * tileRows;
    
    FloatSize constrainedSize = m_size;
    const unsigned cMaxTileCount = 512;
    while (numTiles > cMaxTileCount) {
        // Constrain the wider dimension.
        if (constrainedSize.width() >= constrainedSize.height()) {
            tileColumns = max(floorf(cMaxTileCount / tileRows), 1.0f);
            constrainedSize.setWidth(tileColumns * cTiledLayerTileSize);
        } else {
            tileRows = max(floorf(cMaxTileCount / tileColumns), 1.0f);
            constrainedSize.setHeight(tileRows * cTiledLayerTileSize);
        }
        numTiles = tileColumns * tileRows;
    }
    
    return constrainedSize;
}

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 useTiledLayer)
{
    if (useTiledLayer == m_usingTiledLayer)
        return;

    CGSize tileSize = CGSizeMake(cTiledLayerTileSize, cTiledLayerTileSize);

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

    m_usingTiledLayer = useTiledLayer;

    if (useTiledLayer) {
        WebTiledLayer* tiledLayer = (WebTiledLayer*)m_layer.get();
        [tiledLayer setTileSize:tileSize];
        [tiledLayer setLevelsOfDetail:6];
        [tiledLayer setLevelsOfDetailBias:2];       // up to 400%

        if (GraphicsLayer::compositingCoordinatesOrientation() == GraphicsLayer::CompositingCoordinatesBottomUp)
            [tiledLayer setContentsGravity:@"bottomLeft"];
        else
            [tiledLayer setContentsGravity:@"topLeft"];

#if !HAVE_MODERN_QUARTZCORE
        // Tiled layer has issues with flipped coordinates.
        setContentsOrientation(CompositingCoordinatesTopDown);
#endif
    } else {
#if !HAVE_MODERN_QUARTZCORE
        setContentsOrientation(defaultContentsOrientation());
#endif
    }
    
    [m_layer.get() setLayerOwner:this];
    safeSetSublayers(m_layer.get(), [oldLayer.get() sublayers]);
    
    [[oldLayer.get() superlayer] replaceSublayer:oldLayer.get() with:m_layer.get()];

    updateContentsTransform();

    updateLayerPosition();
    updateLayerSize();
    updateAnchorPoint();
    updateTransform();
    updateChildrenTransform();
    updateMasksToBounds();
    updateContentsOpaque();
    updateBackfaceVisibility();
    updateLayerBackgroundColor();
    
    updateOpacityOnLayer();
    
#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
    moveOrCopyAnimationsForProperty(Move, AnimatedPropertyWebkitTransform, oldLayer.get(), m_layer.get());
    moveOrCopyAnimationsForProperty(Move, AnimatedPropertyOpacity, oldLayer.get(), m_layer.get());
    moveOrCopyAnimationsForProperty(Move, AnimatedPropertyBackgroundColor, oldLayer.get(), m_layer.get());
    
    // need to tell new layer to draw itself
    setNeedsDisplay();
    
    updateDebugIndicators();
}

GraphicsLayer::CompositingCoordinatesOrientation GraphicsLayerCA::defaultContentsOrientation() const
{
#if !HAVE_MODERN_QUARTZCORE
    // Older QuartzCore does not support -geometryFlipped, so we manually flip the root
    // layer geometry, and then flip the contents of each layer back so that the CTM for CG
    // is unflipped, allowing it to do the correct font auto-hinting.
    return CompositingCoordinatesBottomUp;
#else
    return CompositingCoordinatesTopDown;
#endif
}

void GraphicsLayerCA::updateContentsTransform()
{
#if !HAVE_MODERN_QUARTZCORE
    if (contentsOrientation() == CompositingCoordinatesBottomUp) {
        CGAffineTransform contentsTransform = CGAffineTransformMakeScale(1, -1);
        contentsTransform = CGAffineTransformTranslate(contentsTransform, 0, -[m_layer.get() bounds].size.height);
        [m_layer.get() setContentsTransform:contentsTransform];
    }
#else
    ASSERT(contentsOrientation() == CompositingCoordinatesTopDown);
#endif
}

void GraphicsLayerCA::setupContentsLayer(CALayer* contentsLayer)
{
    // Turn off implicit animations on the inner layer.
    [contentsLayer setStyle:[NSDictionary dictionaryWithObject:nullActionsDictionary() forKey:@"actions"]];

    if (defaultContentsOrientation() == CompositingCoordinatesBottomUp) {
        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};
        [contentsLayer setTransform:flipper];
        [contentsLayer setAnchorPoint:CGPointMake(0.0f, 1.0f)];
    } else
        [contentsLayer setAnchorPoint:CGPointZero];

    if (showDebugBorders()) {
        setLayerBorderColor(contentsLayer, Color(0, 0, 128, 180));
        [contentsLayer setBorderWidth:1.0f];
    }
}


#if ENABLE(PLUGIN_PROXY_FOR_VIDEO)
bool GraphicsLayerCA::mediaLayerMustBeUpdatedOnMainThread() const
{
    return m_contentsLayerPurpose == ContentsLayerForMedia;
}
#endif

CALayer *GraphicsLayerCA::findOrMakeClone(CloneID cloneID, CALayer *sourceLayer, LayerMap* clones, CloneLevel cloneLevel)
{
    if (!sourceLayer)
        return 0;

    CALayer *resultLayer;

    // Add with a dummy value to get an iterator for the insertion position, and a boolean that tells
    // us whether there's an item there. This technique avoids two hash lookups.
    RetainPtr<CALayer> dummy;
    pair<LayerMap::iterator, bool> addResult = clones->add(cloneID, dummy);
    if (!addResult.second) {
        // Value was not added, so it exists already.
        resultLayer = addResult.first->second.get();
    } else {
        resultLayer = cloneLayer(sourceLayer, cloneLevel);
#ifndef NDEBUG
        [resultLayer setName:[NSString stringWithFormat:@"Clone %d of layer %@", cloneID[0U], sourceLayer]];
#endif
        addResult.first->second = resultLayer;
    }

    return resultLayer;
}   

void GraphicsLayerCA::ensureCloneLayers(CloneID cloneID, CALayer *& primaryLayer, CALayer *& structuralLayer, CALayer *& contentsLayer, CloneLevel cloneLevel)
{
    structuralLayer = nil;
    contentsLayer = nil;

    if (!m_layerClones)
        m_layerClones = new LayerMap;

    if (!m_structuralLayerClones && m_structuralLayer)
        m_structuralLayerClones = new LayerMap;

    if (!m_contentsLayerClones && m_contentsLayer)
        m_contentsLayerClones = new LayerMap;

    primaryLayer = findOrMakeClone(cloneID, m_layer.get(), m_layerClones.get(), cloneLevel);
    structuralLayer = findOrMakeClone(cloneID, m_structuralLayer.get(), m_structuralLayerClones.get(), cloneLevel);
    contentsLayer = findOrMakeClone(cloneID, m_contentsLayer.get(), m_contentsLayerClones.get(), cloneLevel);
}

void GraphicsLayerCA::removeCloneLayers()
{
    m_layerClones = 0;
    m_structuralLayerClones = 0;
    m_contentsLayerClones = 0;
}

FloatPoint GraphicsLayerCA::positionForCloneRootLayer() const
{
    // This can get called during a sync when we've just removed the m_replicaLayer.
    if (!m_replicaLayer)
        return FloatPoint();

    FloatPoint replicaPosition = m_replicaLayer->replicatedLayerPosition();
    return FloatPoint(replicaPosition.x() + m_anchorPoint.x() * m_size.width(),
                      replicaPosition.y() + m_anchorPoint.y() * m_size.height());
}

void GraphicsLayerCA::propagateLayerChangeToReplicas()
{
    for (GraphicsLayer* currLayer = this; currLayer; currLayer = currLayer->parent()) {
        GraphicsLayerCA* currLayerCA = static_cast<GraphicsLayerCA*>(currLayer);
        if (!currLayerCA->hasCloneLayers())
            break;

        if (currLayerCA->replicaLayer())
            static_cast<GraphicsLayerCA*>(currLayerCA->replicaLayer())->noteLayerPropertyChanged(ReplicatedLayerChanged);
    }
}

CALayer *GraphicsLayerCA::fetchCloneLayers(GraphicsLayer* replicaRoot, ReplicaState& replicaState, CloneLevel cloneLevel)
{
    CALayer *primaryLayer;
    CALayer *structuralLayer;
    CALayer *contentsLayer;
    ensureCloneLayers(replicaState.cloneID(), primaryLayer, structuralLayer, contentsLayer, cloneLevel);

    if (m_maskLayer) {
        CALayer *maskClone = static_cast<GraphicsLayerCA*>(m_maskLayer)->fetchCloneLayers(replicaRoot, replicaState, IntermediateCloneLevel);
        [primaryLayer setMask:maskClone];
    }

    if (m_replicatedLayer) {
        // We are a replica being asked for clones of our layers.
        CALayer *replicaRoot = replicatedLayerRoot(replicaState);
        if (!replicaRoot)
            return nil;

        if (structuralLayer) {
            [structuralLayer insertSublayer:replicaRoot atIndex:0];
            return structuralLayer;
        }
        
        [primaryLayer insertSublayer:replicaRoot atIndex:0];
        return primaryLayer;
    }

    const Vector<GraphicsLayer*>& childLayers = children();
    NSMutableArray* clonalSublayers = nil;

    CALayer *replicaLayer = nil;
    if (m_replicaLayer && m_replicaLayer != replicaRoot) {
        // We have nested replicas. Ask the replica layer for a clone of its contents.
        replicaState.setBranchType(ReplicaState::ReplicaBranch);
        replicaLayer = static_cast<GraphicsLayerCA*>(m_replicaLayer)->fetchCloneLayers(replicaRoot, replicaState, RootCloneLevel);
        replicaState.setBranchType(ReplicaState::ChildBranch);
    }
    
    if (replicaLayer || structuralLayer || contentsLayer || childLayers.size() > 0) {
        clonalSublayers = [[NSMutableArray alloc] init];

        if (structuralLayer) {
            // Replicas render behind the actual layer content.
            if (replicaLayer)
                [clonalSublayers addObject:replicaLayer];
                
            // Add the primary layer next. Even if we have negative z-order children, the primary layer always comes behind.
            [clonalSublayers addObject:primaryLayer];
        } else if (contentsLayer) {
            // FIXME: add the contents layer in the correct order with negative z-order children.
            // This does not cause visible rendering issues because currently contents layers are only used
            // for replaced elements that don't have children.
            [clonalSublayers addObject:contentsLayer];
        }
        
        replicaState.push(ReplicaState::ChildBranch);

        size_t numChildren = childLayers.size();
        for (size_t i = 0; i < numChildren; ++i) {
            GraphicsLayerCA* curChild = static_cast<GraphicsLayerCA*>(childLayers[i]);

            CALayer *childLayer = curChild->fetchCloneLayers(replicaRoot, replicaState, IntermediateCloneLevel);
            if (childLayer)
                [clonalSublayers addObject:childLayer];
        }

        replicaState.pop();

        [clonalSublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
    }
    
    CALayer *result;
    if (structuralLayer) {
        [structuralLayer setSublayers:clonalSublayers];

        if (contentsLayer) {
            // If we have a transform layer, then the contents layer is parented in the 
            // primary layer (which is itself a child of the transform layer).
            [primaryLayer setSublayers:nil];
            [primaryLayer addSublayer:contentsLayer];
        }

        result = structuralLayer;
    } else {
        [primaryLayer setSublayers:clonalSublayers];
        result = primaryLayer;
    }

    [clonalSublayers release];
    return result;
}

CALayer *GraphicsLayerCA::cloneLayer(CALayer *layer, CloneLevel cloneLevel)
{
    static Class transformLayerClass = NSClassFromString(@"CATransformLayer");
    CALayer *newLayer = nil;
    if ([layer isKindOfClass:transformLayerClass])
        newLayer = [transformLayerClass layer];
    else
        newLayer = [CALayer layer];

    [newLayer setStyle:[NSDictionary dictionaryWithObject:nullActionsDictionary() forKey:@"actions"]];
    
    [newLayer setPosition:[layer position]];
    [newLayer setBounds:[layer bounds]];
    [newLayer setAnchorPoint:[layer anchorPoint]];
#if HAVE_MODERN_QUARTZCORE
    [newLayer setAnchorPointZ:[layer anchorPointZ]];
#endif
    [newLayer setTransform:[layer transform]];
    [newLayer setSublayerTransform:[layer sublayerTransform]];
    [newLayer setContents:[layer contents]];
    [newLayer setMasksToBounds:[layer masksToBounds]];
    [newLayer setDoubleSided:[layer isDoubleSided]];
    [newLayer setOpaque:[layer isOpaque]];
    [newLayer setBackgroundColor:[layer backgroundColor]];

    if (cloneLevel == IntermediateCloneLevel) {
        [newLayer setOpacity:[layer opacity]];
        moveOrCopyAnimationsForProperty(Copy, AnimatedPropertyWebkitTransform, layer, newLayer);
        moveOrCopyAnimationsForProperty(Copy, AnimatedPropertyOpacity, layer, newLayer);
    }
    
    if (showDebugBorders()) {
        setLayerBorderColor(newLayer, Color(255, 122, 251));
        [newLayer setBorderWidth:2];
    }
    
    return newLayer;
}

void GraphicsLayerCA::setOpacityInternal(float accumulatedOpacity)
{
    LayerMap* layerCloneMap = 0;
    
    if (preserves3D()) {
        [m_layer.get() setOpacity:accumulatedOpacity];
        layerCloneMap = m_layerClones.get();
    } else {
        [primaryLayer() setOpacity:accumulatedOpacity];
        layerCloneMap = primaryLayerClones();
    }

    if (layerCloneMap) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            if (m_replicaLayer && isReplicatedRootClone(it->first))
                continue;
            CALayer *currLayer = it->second.get();
            [currLayer setOpacity:m_opacity];
        }
    }
}

void GraphicsLayerCA::updateOpacityOnLayer()
{
#if !HAVE_MODERN_QUARTZCORE
    // Distribute opacity either to our own layer or to our children. We pass in the 
    // contribution from our parent(s).
    distributeOpacity(parent() ? parent()->accumulatedOpacity() : 1);
#else
    [primaryLayer() setOpacity:m_opacity];

    if (LayerMap* layerCloneMap = primaryLayerClones()) {
        LayerMap::const_iterator end = layerCloneMap->end();
        for (LayerMap::const_iterator it = layerCloneMap->begin(); it != end; ++it) {
            if (m_replicaLayer && isReplicatedRootClone(it->first))
                continue;

            CALayer *currLayer = it->second.get();
            [currLayer setOpacity:m_opacity];
        }
        
    }
#endif
}

void GraphicsLayerCA::noteSublayersChanged()
{
    noteLayerPropertyChanged(ChildrenChanged);
    propagateLayerChangeToReplicas();
}

void GraphicsLayerCA::noteLayerPropertyChanged(LayerChangeFlags flags)
{
    if (!m_uncommittedChanges && m_client)
        m_client->notifySyncRequired(this);

    m_uncommittedChanges |= flags;
}

float GraphicsLayerCA::clampedContentsScaleForScale(float scale) const
{
    const float kMaxScale = 5.0f;
    const float kMinScale = 0.01f;
    const float kMaxScaledDimension = 1024.0f;

    // clamp
    float result = max(kMinScale, std::min(scale, kMaxScale));

    // We need to clamp the scaled size to (1024x1024), otherwise it is likely to get jettisoned.
    // FIXME: when <rdar://problem/7398907> is fixed, we should consider converting it to tiled layer
    // if scaled size is too big.
    if (!m_size.isEmpty() && result > 1.0f) {
        FloatSize scaledSize(m_size.width() * result, m_size.height() * result);
        if (scaledSize.width() * scaledSize.height() >= kMaxScaledDimension * kMaxScaledDimension) {
            result = min(kMaxScaledDimension / m_size.width(), kMaxScaledDimension / m_size.height());
            scaledSize = FloatSize(m_size.width() * result, m_size.height() * result);
        }

        // If layer is bigger than (512x512) and free memory level is low, we need to clamp contentsScale again.
        if (scaledSize.width() * scaledSize.height() >= kMaxScaledDimension * kMaxScaledDimension / 4) {
            int freeMemoryLevel = systemMemoryLevel();
            if (freeMemoryLevel <= 30) {
                result = min(1.0f, result);
                NSLog(@"Free memory level is 30, clamp contentScale for layer %@ to %f from %f", m_layer.get(), result, scale);
            }
            else if (freeMemoryLevel <= 45) {
                result = min(2.0f, result);
                NSLog(@"Free memory level is 45, clamp contentScale for layer %@ to %f from %f", m_layer.get(), result, scale);
            }
        }
    }

    // if it hasn't changed much, don't do any work
    if ((fabs(result - m_contentsScale) / m_contentsScale) < 0.25)
        return m_contentsScale;

    return result;
}

} // namespace WebCore

#endif // USE(ACCELERATED_COMPOSITING)