blob: e47035cca1e7a8892d1d90eb8b21523ce14da41e [file] [log] [blame]
/*
* Copyright (C) 2013 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. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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"
#import "RemoteLayerTreePropertyApplier.h"
#import "PlatformCAAnimationRemote.h"
#import "PlatformCALayerRemote.h"
#import "RemoteLayerTreeHost.h"
#import "RemoteLayerTreeViews.h"
#import <QuartzCore/QuartzCore.h>
#import <WebCore/PlatformCAFilters.h>
#import <WebCore/ScrollbarThemeMac.h>
#import <pal/spi/cocoa/QuartzCoreSPI.h>
#import <wtf/BlockObjCExceptions.h>
#if PLATFORM(IOS_FAMILY)
#import <UIKit/UIView.h>
#import <UIKitSPI.h>
#endif
#if PLATFORM(IOS_FAMILY)
@interface UIView (WKUIViewUtilities)
- (void)_web_setSubviews:(NSArray *)subviews;
@end
@implementation UIView (WKUIViewUtilities)
- (void)_web_setSubviews:(NSArray *)newSubviews
{
NSUInteger numOldSubviews = self.subviews.count;
NSUInteger numNewSubviews = newSubviews.count;
NSUInteger currIndex = 0;
for (currIndex = 0; currIndex < numNewSubviews; ++currIndex) {
UIView *currNewSubview = [newSubviews objectAtIndex:currIndex];
if (currIndex < numOldSubviews) {
UIView *existingSubview = [self.subviews objectAtIndex:currIndex];
if (existingSubview == currNewSubview)
continue;
}
// New or moved subview.
[self insertSubview:currNewSubview atIndex:currIndex];
}
// Remove views at the end.
NSMutableArray *viewsToRemove = nil;
auto appendViewToRemove = [&viewsToRemove](UIView *view) {
if (!viewsToRemove)
viewsToRemove = [[NSMutableArray alloc] init];
[viewsToRemove addObject:view];
};
NSUInteger remainingSubviews = self.subviews.count;
for (NSUInteger i = currIndex; i < remainingSubviews; ++i) {
UIView *subview = [self.subviews objectAtIndex:i];
if ([subview conformsToProtocol:@protocol(WKContentControlled)])
appendViewToRemove(subview);
}
if (viewsToRemove) {
[viewsToRemove makeObjectsPerformSelector:@selector(removeFromSuperview)];
[viewsToRemove release];
}
}
@end
#endif
namespace WebKit {
using namespace WebCore;
static CGColorRef cgColorFromColor(const Color& color)
{
if (!color.isValid())
return nil;
return cachedCGColor(color);
}
static NSString *toCAFilterType(PlatformCALayer::FilterType type)
{
switch (type) {
case PlatformCALayer::Linear:
return kCAFilterLinear;
case PlatformCALayer::Nearest:
return kCAFilterNearest;
case PlatformCALayer::Trilinear:
return kCAFilterTrilinear;
};
ASSERT_NOT_REACHED();
return 0;
}
static void updateCustomAppearance(CALayer *layer, GraphicsLayer::CustomAppearance customAppearance)
{
#if ENABLE(RUBBER_BANDING)
switch (customAppearance) {
case GraphicsLayer::CustomAppearance::None:
case GraphicsLayer::CustomAppearance::DarkBackdrop:
case GraphicsLayer::CustomAppearance::LightBackdrop:
ScrollbarThemeMac::removeOverhangAreaBackground(layer);
ScrollbarThemeMac::removeOverhangAreaShadow(layer);
break;
case GraphicsLayer::CustomAppearance::ScrollingOverhang:
ScrollbarThemeMac::setUpOverhangAreaBackground(layer);
break;
case GraphicsLayer::CustomAppearance::ScrollingShadow:
ScrollbarThemeMac::setUpOverhangAreaShadow(layer);
break;
}
#else
UNUSED_PARAM(customAppearance);
#endif
}
void RemoteLayerTreePropertyApplier::applyPropertiesToLayer(CALayer *layer, RemoteLayerTreeHost* layerTreeHost, const RemoteLayerTreeTransaction::LayerProperties& properties, RemoteLayerBackingStore::LayerContentsType layerContentsType)
{
if (properties.changedProperties & RemoteLayerTreeTransaction::NameChanged)
layer.name = properties.name;
if (properties.changedProperties & RemoteLayerTreeTransaction::PositionChanged) {
layer.position = CGPointMake(properties.position.x(), properties.position.y());
layer.zPosition = properties.position.z();
}
if (properties.changedProperties & RemoteLayerTreeTransaction::AnchorPointChanged) {
layer.anchorPoint = CGPointMake(properties.anchorPoint.x(), properties.anchorPoint.y());
layer.anchorPointZ = properties.anchorPoint.z();
}
if (properties.changedProperties & RemoteLayerTreeTransaction::BoundsChanged)
layer.bounds = properties.bounds;
if (properties.changedProperties & RemoteLayerTreeTransaction::BackgroundColorChanged)
layer.backgroundColor = cgColorFromColor(properties.backgroundColor);
if (properties.changedProperties & RemoteLayerTreeTransaction::BorderColorChanged)
layer.borderColor = cgColorFromColor(properties.borderColor);
if (properties.changedProperties & RemoteLayerTreeTransaction::BorderWidthChanged)
layer.borderWidth = properties.borderWidth;
if (properties.changedProperties & RemoteLayerTreeTransaction::OpacityChanged)
layer.opacity = properties.opacity;
if (properties.changedProperties & RemoteLayerTreeTransaction::TransformChanged)
layer.transform = properties.transform ? (CATransform3D)*properties.transform.get() : CATransform3DIdentity;
if (properties.changedProperties & RemoteLayerTreeTransaction::SublayerTransformChanged)
layer.sublayerTransform = properties.sublayerTransform ? (CATransform3D)*properties.sublayerTransform.get() : CATransform3DIdentity;
if (properties.changedProperties & RemoteLayerTreeTransaction::HiddenChanged)
layer.hidden = properties.hidden;
if (properties.changedProperties & RemoteLayerTreeTransaction::GeometryFlippedChanged)
layer.geometryFlipped = properties.geometryFlipped;
if (properties.changedProperties & RemoteLayerTreeTransaction::DoubleSidedChanged)
layer.doubleSided = properties.doubleSided;
if (properties.changedProperties & RemoteLayerTreeTransaction::MasksToBoundsChanged)
layer.masksToBounds = properties.masksToBounds;
if (properties.changedProperties & RemoteLayerTreeTransaction::OpaqueChanged)
layer.opaque = properties.opaque;
if (properties.changedProperties & RemoteLayerTreeTransaction::ContentsRectChanged)
layer.contentsRect = properties.contentsRect;
if (properties.changedProperties & RemoteLayerTreeTransaction::ContentsScaleChanged) {
layer.contentsScale = properties.contentsScale;
layer.rasterizationScale = properties.contentsScale;
}
if (properties.changedProperties & RemoteLayerTreeTransaction::CornerRadiusChanged)
layer.cornerRadius = properties.cornerRadius;
if (properties.changedProperties & RemoteLayerTreeTransaction::ShapeRoundedRectChanged) {
Path path;
if (properties.shapeRoundedRect)
path.addRoundedRect(*properties.shapeRoundedRect);
ASSERT([layer isKindOfClass:[CAShapeLayer class]]);
[(CAShapeLayer *)layer setPath:path.platformPath()];
}
if (properties.changedProperties & RemoteLayerTreeTransaction::ShapePathChanged) {
ASSERT([layer isKindOfClass:[CAShapeLayer class]]);
[(CAShapeLayer *)layer setPath:properties.shapePath.platformPath()];
}
if (properties.changedProperties & RemoteLayerTreeTransaction::MinificationFilterChanged)
layer.minificationFilter = toCAFilterType(properties.minificationFilter);
if (properties.changedProperties & RemoteLayerTreeTransaction::MagnificationFilterChanged)
layer.magnificationFilter = toCAFilterType(properties.magnificationFilter);
if (properties.changedProperties & RemoteLayerTreeTransaction::BlendModeChanged)
PlatformCAFilters::setBlendingFiltersOnLayer(layer, properties.blendMode);
if (properties.changedProperties & RemoteLayerTreeTransaction::WindRuleChanged) {
ASSERT([layer isKindOfClass:[CAShapeLayer class]]);
CAShapeLayer *shapeLayer = (CAShapeLayer *)layer;
switch (properties.windRule) {
case WindRule::NonZero:
shapeLayer.fillRule = @"non-zero";
break;
case WindRule::EvenOdd:
shapeLayer.fillRule = @"even-odd";
break;
}
}
if (properties.changedProperties & RemoteLayerTreeTransaction::SpeedChanged)
layer.speed = properties.speed;
if (properties.changedProperties & RemoteLayerTreeTransaction::TimeOffsetChanged)
layer.timeOffset = properties.timeOffset;
if (properties.changedProperties & RemoteLayerTreeTransaction::BackingStoreChanged
|| properties.changedProperties & RemoteLayerTreeTransaction::BackingStoreAttachmentChanged)
{
RemoteLayerBackingStore* backingStore = properties.backingStore.get();
if (backingStore && properties.backingStoreAttached)
backingStore->applyBackingStoreToLayer(layer, layerContentsType);
else {
layer.contents = nil;
layer.contentsOpaque = NO;
}
}
if (properties.changedProperties & RemoteLayerTreeTransaction::FiltersChanged)
PlatformCAFilters::setFiltersOnLayer(layer, properties.filters ? *properties.filters : FilterOperations());
if (properties.changedProperties & RemoteLayerTreeTransaction::AnimationsChanged)
PlatformCAAnimationRemote::updateLayerAnimations(layer, layerTreeHost, properties.addedAnimations, properties.keyPathsOfAnimationsToRemove);
if (properties.changedProperties & RemoteLayerTreeTransaction::EdgeAntialiasingMaskChanged)
layer.edgeAntialiasingMask = properties.edgeAntialiasingMask;
if (properties.changedProperties & RemoteLayerTreeTransaction::CustomAppearanceChanged)
updateCustomAppearance(layer, properties.customAppearance);
}
void RemoteLayerTreePropertyApplier::applyProperties(RemoteLayerTreeNode& node, RemoteLayerTreeHost* layerTreeHost, const RemoteLayerTreeTransaction::LayerProperties& properties, const RelatedLayerMap& relatedLayers, RemoteLayerBackingStore::LayerContentsType layerContentsType)
{
BEGIN_BLOCK_OBJC_EXCEPTIONS;
applyPropertiesToLayer(node.layer(), layerTreeHost, properties, layerContentsType);
updateChildren(node, properties, relatedLayers);
updateMask(node, properties, relatedLayers);
if (properties.changedProperties & RemoteLayerTreeTransaction::EventRegionChanged)
node.setEventRegion(properties.eventRegion);
#if PLATFORM(IOS_FAMILY)
applyPropertiesToUIView(node.uiView(), properties, relatedLayers);
#endif
END_BLOCK_OBJC_EXCEPTIONS;
}
void RemoteLayerTreePropertyApplier::updateChildren(RemoteLayerTreeNode& node, const RemoteLayerTreeTransaction::LayerProperties& properties, const RelatedLayerMap& relatedLayers)
{
if (!properties.changedProperties.contains(RemoteLayerTreeTransaction::ChildrenChanged))
return;
#if PLATFORM(IOS_FAMILY)
auto hasViewChildren = [&] {
if (node.uiView() && [[node.uiView() subviews] count])
return true;
if (properties.children.isEmpty())
return false;
auto* childNode = relatedLayers.get(properties.children.first());
ASSERT(childNode);
return childNode && childNode->uiView();
};
auto contentView = [&] {
if (properties.customAppearance == GraphicsLayer::CustomAppearance::LightBackdrop || properties.customAppearance == GraphicsLayer::CustomAppearance::DarkBackdrop) {
// This is a UIBackdropView, which should have children attached to
// its content view, not directly on its layers.
return [(_UIBackdropView *)node.uiView() contentView];
}
return node.uiView();
};
if (hasViewChildren()) {
ASSERT(node.uiView());
RetainPtr<NSMutableArray> subviews = adoptNS([[NSMutableArray alloc] initWithCapacity:properties.children.size()]);
for (auto& child : properties.children) {
auto* childNode = relatedLayers.get(child);
ASSERT(childNode);
if (!childNode)
continue;
ASSERT(childNode->uiView());
[subviews addObject:childNode->uiView()];
}
[contentView() _web_setSubviews:subviews.get()];
return;
}
#endif
RetainPtr<NSMutableArray> sublayers = adoptNS([[NSMutableArray alloc] initWithCapacity:properties.children.size()]);
for (auto& child : properties.children) {
auto* childNode = relatedLayers.get(child);
ASSERT(childNode);
if (!childNode)
continue;
#if PLATFORM(IOS_FAMILY)
ASSERT(!childNode->uiView());
#endif
[sublayers addObject:childNode->layer()];
}
node.layer().sublayers = sublayers.get();
}
void RemoteLayerTreePropertyApplier::updateMask(RemoteLayerTreeNode& node, const RemoteLayerTreeTransaction::LayerProperties& properties, const RelatedLayerMap& relatedLayers)
{
if (!properties.changedProperties.contains(RemoteLayerTreeTransaction::MaskLayerChanged))
return;
auto maskOwnerLayer = [&] {
CALayer *layer = node.layer();
#if PLATFORM(IOS_FAMILY)
if (properties.customAppearance == GraphicsLayer::CustomAppearance::LightBackdrop || properties.customAppearance == GraphicsLayer::CustomAppearance::DarkBackdrop) {
// This is a UIBackdropView, which means any mask must be applied to the CABackdropLayer rather
// that the view's layer. The backdrop is the first layer child.
if (layer.sublayers.count && [layer.sublayers[0] isKindOfClass:[CABackdropLayer class]])
layer = layer.sublayers[0];
}
#endif
return layer;
};
if (!properties.maskLayerID) {
maskOwnerLayer().mask = nullptr;
return;
}
auto* maskNode = relatedLayers.get(properties.maskLayerID);
ASSERT(maskNode);
if (!maskNode)
return;
CALayer *maskLayer = maskNode->layer();
ASSERT(!maskLayer.superlayer);
if (maskLayer.superlayer)
return;
maskOwnerLayer().mask = maskLayer;
}
#if PLATFORM(IOS_FAMILY)
void RemoteLayerTreePropertyApplier::applyPropertiesToUIView(UIView *view, const RemoteLayerTreeTransaction::LayerProperties& properties, const RelatedLayerMap& relatedLayers)
{
if (properties.changedProperties.containsAny({ RemoteLayerTreeTransaction::ContentsHiddenChanged, RemoteLayerTreeTransaction::UserInteractionEnabledChanged }))
view.userInteractionEnabled = !properties.contentsHidden && properties.userInteractionEnabled;
}
#endif
} // namespace WebKit