blob: 7662541194dfaa9c28e8812553898ab3bc669193 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll ([email protected])
* (C) 2004-2005 Allan Sandfeld Jensen ([email protected])
* Copyright (C) 2006, 2007 Nicholas Shanks ([email protected])
* Copyright (C) 2005-2023 Apple Inc. All rights reserved.
* Copyright (C) 2014 Google Inc. All rights reserved.
* Copyright (C) 2007 Alexey Proskuryakov <[email protected]>
* Copyright (C) 2007, 2008 Eric Seidel <[email protected]>
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
* Copyright (C) Research In Motion Limited 2011. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "CSSToStyleMap.h"
#include "Animation.h"
#include "CSSBackgroundRepeatValue.h"
#include "CSSBorderImageSliceValue.h"
#include "CSSBorderImageWidthValue.h"
#include "CSSImageSetValue.h"
#include "CSSImageValue.h"
#include "CSSPrimitiveValue.h"
#include "CSSPrimitiveValueMappings.h"
#include "CSSPropertyParser.h"
#include "CSSQuadValue.h"
#include "CSSScrollValue.h"
#include "CSSValueKeywords.h"
#include "CSSViewValue.h"
#include "CompositeOperation.h"
#include "FillLayer.h"
#include "ScrollTimeline.h"
#include "StyleBuilderConverter.h"
#include "StyleResolver.h"
#include "ViewTimeline.h"
namespace WebCore {
static bool treatAsInitialValue(const CSSValue& value, CSSPropertyID propertyID)
{
switch (valueID(value)) {
case CSSValueInitial:
return true;
case CSSValueUnset:
return !CSSProperty::isInheritedProperty(propertyID);
default:
return false;
}
}
CSSToStyleMap::CSSToStyleMap(Style::BuilderState& builderState)
: m_builderState(builderState)
{
}
RefPtr<StyleImage> CSSToStyleMap::styleImage(const CSSValue& value)
{
return m_builderState.createStyleImage(value);
}
void CSSToStyleMap::mapFillAttachment(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setAttachment(FillLayer::initialFillAttachment(layer.type()));
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
switch (value.valueID()) {
case CSSValueFixed:
layer.setAttachment(FillAttachment::FixedBackground);
break;
case CSSValueScroll:
layer.setAttachment(FillAttachment::ScrollBackground);
break;
case CSSValueLocal:
layer.setAttachment(FillAttachment::LocalBackground);
break;
default:
return;
}
}
void CSSToStyleMap::mapFillClip(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setClip(FillLayer::initialFillClip(layer.type()));
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
layer.setClip(fromCSSValue<FillBox>(value));
}
void CSSToStyleMap::mapFillComposite(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setComposite(FillLayer::initialFillComposite(layer.type()));
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
layer.setComposite(fromCSSValue<CompositeOperator>(value));
}
void CSSToStyleMap::mapFillBlendMode(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setBlendMode(FillLayer::initialFillBlendMode(layer.type()));
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
layer.setBlendMode(fromCSSValue<BlendMode>(value));
}
void CSSToStyleMap::mapFillOrigin(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setOrigin(FillLayer::initialFillOrigin(layer.type()));
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
layer.setOrigin(fromCSSValue<FillBox>(value));
}
void CSSToStyleMap::mapFillImage(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setImage(FillLayer::initialFillImage(layer.type()));
return;
}
layer.setImage(styleImage(value));
}
void CSSToStyleMap::mapFillRepeat(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setRepeat(FillLayer::initialFillRepeat(layer.type()));
return;
}
auto* backgroundRepeatValue = dynamicDowncast<CSSBackgroundRepeatValue>(value);
if (!backgroundRepeatValue)
return;
auto repeatX = fromCSSValueID<FillRepeat>(backgroundRepeatValue->xValue());
auto repeatY = fromCSSValueID<FillRepeat>(backgroundRepeatValue->yValue());
layer.setRepeat(FillRepeatXY { repeatX, repeatY });
}
static inline bool convertToLengthSize(const CSSValue& value, Style::BuilderState& builderState, LengthSize& size)
{
if (value.isPair()) {
auto pair = Style::requiredPairDowncast<CSSPrimitiveValue>(builderState, value);
if (!pair)
return false;
size.width = Style::BuilderConverter::convertLengthOrAuto(builderState, pair->first);
size.height = Style::BuilderConverter::convertLengthOrAuto(builderState, pair->second);
} else {
auto primitiveValue = Style::requiredDowncast<CSSPrimitiveValue>(builderState, value);
if (!primitiveValue)
return false;
size.width = Style::BuilderConverter::convertLengthOrAuto(builderState, *primitiveValue);
}
return true;
}
void CSSToStyleMap::mapFillSize(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setSize(FillLayer::initialFillSize(layer.type()));
return;
}
FillSize fillSize;
switch (value.valueID()) {
case CSSValueContain:
fillSize.type = FillSizeType::Contain;
break;
case CSSValueCover:
fillSize.type = FillSizeType::Cover;
break;
default:
ASSERT(fillSize.type == FillSizeType::Size);
if (!convertToLengthSize(value, m_builderState, fillSize.size))
return;
break;
}
layer.setSize(fillSize);
}
void CSSToStyleMap::mapFillXPosition(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setXPosition(FillLayer::initialFillXPosition(layer.type()));
return;
}
layer.setXPosition(Style::toStyleFromCSSValue<Style::PositionX>(m_builderState, value));
}
void CSSToStyleMap::mapFillYPosition(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, propertyID)) {
layer.setYPosition(FillLayer::initialFillYPosition(layer.type()));
return;
}
layer.setYPosition(Style::toStyleFromCSSValue<Style::PositionY>(m_builderState, value));
}
void CSSToStyleMap::mapFillMaskMode(CSSPropertyID propertyID, FillLayer& layer, const CSSValue& value)
{
MaskMode maskMode = FillLayer::initialFillMaskMode(layer.type());
if (treatAsInitialValue(value, propertyID)) {
layer.setMaskMode(maskMode);
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
switch (value.valueID()) {
case CSSValueAlpha:
maskMode = MaskMode::Alpha;
break;
case CSSValueLuminance:
maskMode = MaskMode::Luminance;
break;
case CSSValueMatchSource:
ASSERT(propertyID == CSSPropertyMaskMode);
maskMode = MaskMode::MatchSource;
break;
case CSSValueAuto: // -webkit-mask-source-type
break;
default:
ASSERT_NOT_REACHED();
}
layer.setMaskMode(maskMode);
}
void CSSToStyleMap::mapAnimationDelay(Animation& animation, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationDelay)) {
animation.setDelay(Animation::initialDelay());
return;
}
auto* primitiveValue = dynamicDowncast<CSSPrimitiveValue>(value);
if (!primitiveValue)
return;
animation.setDelay(primitiveValue->resolveAsTime(m_builderState.cssToLengthConversionData()));
}
void CSSToStyleMap::mapAnimationDirection(Animation& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationDirection)) {
layer.setDirection(Animation::initialDirection());
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
switch (value.valueID()) {
case CSSValueNormal:
layer.setDirection(Animation::Direction::Normal);
break;
case CSSValueAlternate:
layer.setDirection(Animation::Direction::Alternate);
break;
case CSSValueReverse:
layer.setDirection(Animation::Direction::Reverse);
break;
case CSSValueAlternateReverse:
layer.setDirection(Animation::Direction::AlternateReverse);
break;
default:
break;
}
}
void CSSToStyleMap::mapAnimationDuration(Animation& animation, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationDuration)) {
animation.setDuration(Animation::initialDuration());
return;
}
auto* primitiveValue = dynamicDowncast<CSSPrimitiveValue>(value);
if (!primitiveValue)
return;
if (value.valueID() == CSSValueAuto) {
animation.setDuration(std::nullopt);
return;
}
auto duration = std::max<double>(primitiveValue->resolveAsTime(m_builderState.cssToLengthConversionData()), 0);
animation.setDuration(duration);
}
void CSSToStyleMap::mapAnimationFillMode(Animation& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationFillMode)) {
layer.setFillMode(Animation::initialFillMode());
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
switch (value.valueID()) {
case CSSValueNone:
layer.setFillMode(AnimationFillMode::None);
break;
case CSSValueForwards:
layer.setFillMode(AnimationFillMode::Forwards);
break;
case CSSValueBackwards:
layer.setFillMode(AnimationFillMode::Backwards);
break;
case CSSValueBoth:
layer.setFillMode(AnimationFillMode::Both);
break;
default:
break;
}
}
void CSSToStyleMap::mapAnimationIterationCount(Animation& animation, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationIterationCount)) {
animation.setIterationCount(Animation::initialIterationCount());
return;
}
auto* primitiveValue = dynamicDowncast<CSSPrimitiveValue>(value);
if (!primitiveValue)
return;
if (primitiveValue->valueID() == CSSValueInfinite)
animation.setIterationCount(Animation::IterationCountInfinite);
else
animation.setIterationCount(primitiveValue->resolveAsNumber<float>(m_builderState.cssToLengthConversionData()));
}
void CSSToStyleMap::mapAnimationName(Animation& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationName)) {
layer.setName(Animation::initialName());
return;
}
auto* primitiveValue = dynamicDowncast<CSSPrimitiveValue>(value);
if (!primitiveValue)
return;
if (primitiveValue->valueID() == CSSValueNone)
layer.setName(Animation::initialName());
else
layer.setName({ AtomString { primitiveValue->stringValue() }, m_builderState.styleScopeOrdinal(), primitiveValue->isCustomIdent() });
}
void CSSToStyleMap::mapAnimationPlayState(Animation& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationPlayState)) {
layer.setPlayState(Animation::initialPlayState());
return;
}
if (!is<CSSPrimitiveValue>(value))
return;
AnimationPlayState playState = (value.valueID() == CSSValuePaused) ? AnimationPlayState::Paused : AnimationPlayState::Playing;
layer.setPlayState(playState);
}
void CSSToStyleMap::mapAnimationProperty(Animation& animation, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimation)) {
animation.setProperty(Animation::initialProperty());
return;
}
auto* primitiveValue = dynamicDowncast<CSSPrimitiveValue>(value);
if (!primitiveValue)
return;
if (primitiveValue->valueID() == CSSValueAll) {
animation.setProperty({ Animation::TransitionMode::All, CSSPropertyInvalid });
return;
}
if (primitiveValue->valueID() == CSSValueNone) {
animation.setProperty({ Animation::TransitionMode::None, CSSPropertyInvalid });
return;
}
if (primitiveValue->propertyID() == CSSPropertyInvalid) {
auto stringValue = primitiveValue->stringValue();
auto transitionMode = isCustomPropertyName(stringValue) ? Animation::TransitionMode::SingleProperty : Animation::TransitionMode::UnknownProperty;
animation.setProperty({ transitionMode, AtomString { stringValue } });
return;
}
animation.setProperty({ Animation::TransitionMode::SingleProperty, primitiveValue->propertyID() });
}
void CSSToStyleMap::mapAnimationTimeline(Animation& animation, const CSSValue& value)
{
auto mapScrollValue = [](const CSSScrollValue& cssScrollValue) -> Animation::AnonymousScrollTimeline {
auto scroller = [&] {
auto& scrollerValue = cssScrollValue.scroller();
if (!scrollerValue)
return Scroller::Nearest;
switch (scrollerValue->valueID()) {
case CSSValueNearest:
return Scroller::Nearest;
case CSSValueRoot:
return Scroller::Root;
case CSSValueSelf:
return Scroller::Self;
default:
ASSERT_NOT_REACHED();
return Scroller::Nearest;
}
}();
auto& axisValue = cssScrollValue.axis();
auto axis = axisValue ? fromCSSValueID<ScrollAxis>(axisValue->valueID()) : ScrollAxis::Block;
return { scroller, axis };
};
auto mapViewValue = [&](const CSSViewValue& cssViewValue) -> Animation::AnonymousViewTimeline {
auto& axisValue = cssViewValue.axis();
auto axis = axisValue ? fromCSSValueID<ScrollAxis>(axisValue->valueID()) : ScrollAxis::Block;
auto convertInsetValue = [&](CSSValue* value) -> std::optional<Length> {
if (!value)
return std::nullopt;
return Style::BuilderConverter::convertLengthOrAuto(m_builderState, *value);
};
auto startInset = convertInsetValue(cssViewValue.startInset().get());
auto endInset = [&] {
if (auto& endInsetValue = cssViewValue.endInset())
return convertInsetValue(endInsetValue.get());
return convertInsetValue(cssViewValue.startInset().get());
}();
return { axis, { startInset, endInset } };
};
if (treatAsInitialValue(value, CSSPropertyAnimationTimeline))
animation.setTimeline(Animation::initialTimeline());
else if (auto* viewValue = dynamicDowncast<CSSViewValue>(value))
animation.setTimeline(mapViewValue(*viewValue));
else if (auto* scrollValue = dynamicDowncast<CSSScrollValue>(value))
animation.setTimeline(mapScrollValue(*scrollValue));
else if (value.isCustomIdent())
animation.setTimeline(AtomString(value.customIdent()));
else {
switch (value.valueID()) {
case CSSValueNone:
animation.setTimeline(Animation::TimelineKeyword::None);
break;
case CSSValueAuto:
animation.setTimeline(Animation::TimelineKeyword::Auto);
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
}
void CSSToStyleMap::mapAnimationTimingFunction(Animation& animation, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationTimingFunction))
animation.setTimingFunction(Animation::initialTimingFunction());
else if (auto timingFunction = Style::BuilderConverter::convertTimingFunction(m_builderState, value))
animation.setTimingFunction(WTFMove(timingFunction));
}
void CSSToStyleMap::mapAnimationCompositeOperation(Animation& animation, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationComposition))
animation.setCompositeOperation(Animation::initialCompositeOperation());
else if (auto compositeOperation = toCompositeOperation(value))
animation.setCompositeOperation(*compositeOperation);
}
void CSSToStyleMap::mapAnimationAllowsDiscreteTransitions(Animation& layer, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyTransitionBehavior))
layer.setAllowsDiscreteTransitions(Animation::initialAllowsDiscreteTransitions());
else if (is<CSSPrimitiveValue>(value))
layer.setAllowsDiscreteTransitions(value.valueID() == CSSValueAllowDiscrete);
}
void CSSToStyleMap::mapAnimationRangeStart(Animation& animation, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationRangeStart))
animation.setRangeStart(Animation::initialRangeStart());
animation.setRangeStart(SingleTimelineRange::range(value, SingleTimelineRange::Type::Start, &m_builderState));
}
void CSSToStyleMap::mapAnimationRangeEnd(Animation& animation, const CSSValue& value)
{
if (treatAsInitialValue(value, CSSPropertyAnimationRangeEnd))
animation.setRangeEnd(Animation::initialRangeEnd());
animation.setRangeEnd(SingleTimelineRange::range(value, SingleTimelineRange::Type::End, &m_builderState));
}
void CSSToStyleMap::mapNinePieceImage(const CSSValue* value, NinePieceImage& image)
{
// If we're not a value list, then we are "none" and don't need to alter the empty image at all.
auto* borderImage = dynamicDowncast<CSSValueList>(value);
if (!borderImage)
return;
// Retrieve the border image value.
for (auto& current : *borderImage) {
if (current.isImage())
image.setImage(styleImage(current));
else if (auto* imageSlice = dynamicDowncast<CSSBorderImageSliceValue>(current))
mapNinePieceImageSlice(*imageSlice, image);
else if (auto* slashList = dynamicDowncast<CSSValueList>(current)) {
// Map in the image slices.
if (auto* imageSlice = dynamicDowncast<CSSBorderImageSliceValue>(slashList->item(0)))
mapNinePieceImageSlice(*imageSlice, image);
// Map in the border slices.
if (auto* borderImageWidth = dynamicDowncast<CSSBorderImageWidthValue>(slashList->item(1)))
mapNinePieceImageWidth(*borderImageWidth, image);
// Map in the outset.
if (slashList->item(2))
image.setOutset(mapNinePieceImageQuad(*slashList->item(2)));
} else if (current.isPair()) {
// Set the appropriate rules for stretch/round/repeat of the slices.
mapNinePieceImageRepeat(current, image);
}
}
}
void CSSToStyleMap::mapNinePieceImageSlice(const CSSValue& value, NinePieceImage& image)
{
if (auto* sliceValue = dynamicDowncast<CSSBorderImageSliceValue>(value))
mapNinePieceImageSlice(*sliceValue, image);
}
void CSSToStyleMap::mapNinePieceImageSlice(const CSSBorderImageSliceValue& value, NinePieceImage& image)
{
// Set up a length box to represent our image slices.
auto& conversionData = m_builderState.cssToLengthConversionData();
auto side = [&](const CSSValue& value) -> Length {
RefPtr primitive = dynamicDowncast<CSSPrimitiveValue>(value);
if (!primitive) {
m_builderState.setCurrentPropertyInvalidAtComputedValueTime();
return { };
}
if (primitive->isPercentage())
return { primitive->resolveAsPercentage(conversionData), LengthType::Percent };
return { primitive->resolveAsNumber<int>(conversionData), LengthType::Fixed };
};
auto& slices = value.slices();
image.setImageSlices({
side(slices.top()),
side(slices.right()),
side(slices.bottom()),
side(slices.left())
});
// Set our fill mode.
image.setFill(value.fill());
}
void CSSToStyleMap::mapNinePieceImageWidth(const CSSValue& value, NinePieceImage& image)
{
if (auto* widthValue = dynamicDowncast<CSSBorderImageWidthValue>(value))
mapNinePieceImageWidth(*widthValue, image);
}
void CSSToStyleMap::mapNinePieceImageWidth(const CSSBorderImageWidthValue& value, NinePieceImage& image)
{
if (!is<CSSBorderImageWidthValue>(value))
return;
image.setBorderSlices(mapNinePieceImageQuad(value.widths()));
image.setOverridesBorderWidths(value.overridesBorderWidths());
}
LengthBox CSSToStyleMap::mapNinePieceImageQuad(const CSSValue& value)
{
if (value.isQuad()) [[likely]]
return mapNinePieceImageQuad(value.quad());
// Values coming from CSS Typed OM may not have been converted to a Quad yet.
auto* primitive = dynamicDowncast<CSSPrimitiveValue>(value);
if (!primitive)
return LengthBox();
if (!primitive->isNumber() && !primitive->isLength())
return LengthBox();
auto side = mapNinePieceImageSide(value);
return { Length { side }, Length { side }, Length { side }, Length { side } };
}
Length CSSToStyleMap::mapNinePieceImageSide(const CSSValue& value)
{
auto primitiveValue = Style::requiredDowncast<CSSPrimitiveValue>(m_builderState, value);
if (!primitiveValue)
return { };
if (primitiveValue->valueID() == CSSValueAuto)
return { };
auto& conversionData = m_builderState.cssToLengthConversionData();
if (primitiveValue->isNumber())
return { primitiveValue->resolveAsNumber<float>(conversionData), LengthType::Relative };
if (primitiveValue->isPercentage())
return { primitiveValue->resolveAsPercentage<float>(conversionData), LengthType::Percent };
if (primitiveValue->isCalculatedPercentageWithLength())
return Length { primitiveValue->cssCalcValue()->createCalculationValue(conversionData, CSSCalcSymbolTable { }) };
return { primitiveValue->resolveAsLength<Length>(conversionData) };
}
LengthBox CSSToStyleMap::mapNinePieceImageQuad(const Quad& quad)
{
auto side = [&](const CSSValue& value) {
return mapNinePieceImageSide(value);
};
return { side(quad.top()), side(quad.right()), side(quad.bottom()), side(quad.left()) };
}
template<> constexpr NinePieceImageRule fromCSSValueID(CSSValueID valueID)
{
switch (valueID) {
case CSSValueStretch:
return NinePieceImageRule::Stretch;
case CSSValueRound:
return NinePieceImageRule::Round;
case CSSValueSpace:
return NinePieceImageRule::Space;
case CSSValueRepeat:
return NinePieceImageRule::Repeat;
default:
break;
}
ASSERT_NOT_REACHED_UNDER_CONSTEXPR_CONTEXT();
return NinePieceImageRule::Repeat;
}
void CSSToStyleMap::mapNinePieceImageRepeat(const CSSValue& value, NinePieceImage& image)
{
if (!value.isPair())
return;
image.setHorizontalRule(fromCSSValue<NinePieceImageRule>(value.first()));
image.setVerticalRule(fromCSSValue<NinePieceImageRule>(value.second()));
}
}