blob: c6d029ccce7bdede4f2301a9a5353bb151a8145f [file] [log] [blame]
/*
* Copyright (C) 2024-2025 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.
*/
#include "config.h"
#include "TimelineRange.h"
#include "CSSNumericFactory.h"
#include "CSSPropertyParserConsumer+Timeline.h"
#include "CSSValuePair.h"
#include "CSSValuePool.h"
#include "ContainerNodeInlines.h"
#include "StyleBuilderConverter.h"
#include "StyleBuilderState.h"
namespace WebCore {
static bool percentIsDefault(float percent, SingleTimelineRange::Type type)
{
return percent == SingleTimelineRange::defaultValue(type).value();
}
bool SingleTimelineRange::isDefault(const Length& offset, Type type)
{
return offset.isAuto() || offset.isNormal() || (offset.isPercent() && percentIsDefault(offset.percent(), type));
}
bool SingleTimelineRange::isDefault(const CSSPrimitiveValue& value, Type type)
{
return value.valueID() == CSSValueNormal || (value.isPercentage() && !value.isCalculated() && percentIsDefault(value.resolveAsPercentageNoConversionDataRequired(), type));
}
Length SingleTimelineRange::defaultValue(Type type)
{
return type == Type::Start ? Length(0, LengthType::Percent) : Length(100, LengthType::Percent);
}
bool SingleTimelineRange::isOffsetValue(const CSSPrimitiveValue& value)
{
return value.isLength() || value.isPercentage() || value.isCalculatedPercentageWithLength();
}
TimelineRangeValue SingleTimelineRange::serialize() const
{
if (name == Name::Normal)
return CSSPrimitiveValue::create(valueID(name))->stringValue();
return TimelineRangeOffset { CSSPrimitiveValue::create(valueID(name))->stringValue(), offset.isPercentOrCalculated() ? CSSNumericFactory::percent(offset.percent()) : CSSNumericFactory::px(offset.value()) };
}
static const std::optional<CSSToLengthConversionData> cssToLengthConversionData(RefPtr<Element> element)
{
CheckedPtr elementRenderer = element->renderer();
if (!elementRenderer)
return std::nullopt;
CheckedPtr elementParentRenderer = elementRenderer->parent();
Ref document = element->document();
CheckedPtr documentElement = document->documentElement();
if (!documentElement)
return std::nullopt;
// FIXME: Investigate container query units
return CSSToLengthConversionData {
elementRenderer->style(),
documentElement->renderer() ? &documentElement->renderer()->style() : nullptr,
elementParentRenderer ? &elementParentRenderer->style() : nullptr,
document->renderView()
};
}
Length SingleTimelineRange::lengthForCSSValue(RefPtr<const CSSPrimitiveValue> value, RefPtr<Element> element)
{
if (!value || !element)
return { };
if (value->valueID() == CSSValueAuto)
return { };
auto conversionData = cssToLengthConversionData(element);
if (!conversionData) {
if (value->isPercentage())
return Length(value->resolveAsPercentageNoConversionDataRequired(), LengthType::Percent);
if (value->isPx())
return Length(value->resolveAsLengthNoConversionDataRequired(), LengthType::Fixed);
return { };
}
if (value->isLength())
return value->resolveAsLength<WebCore::Length>(*conversionData);
if (value->isPercentage())
return Length(value->resolveAsPercentage(*conversionData), LengthType::Percent);
if (value->isCalculatedPercentageWithLength()) {
if (RefPtr cssCalcValue = value->cssCalcValue())
return Length(cssCalcValue->createCalculationValue(*conversionData, CSSCalcSymbolTable { }));
}
ASSERT_NOT_REACHED();
return { };
}
SingleTimelineRange SingleTimelineRange::range(const CSSValue& value, Type type, Style::BuilderState* state, RefPtr<Element> element)
{
if (RefPtr primitiveValue = dynamicDowncast<CSSPrimitiveValue>(value)) {
// <length-percentage>
if (SingleTimelineRange::isOffsetValue(*primitiveValue))
return { SingleTimelineRange::Name::Omitted, state ? Style::BuilderConverter::convertLength(*state, *primitiveValue) : lengthForCSSValue(primitiveValue, element) };
// <timeline-range-name> or Normal
return { SingleTimelineRange::timelineName(primitiveValue->valueID()), defaultValue(type) };
}
RefPtr pair = dynamicDowncast<CSSValuePair>(value);
if (!pair)
return { };
// <timeline-range-name> <length-percentage>
Ref primitiveValue = downcast<CSSPrimitiveValue>(pair->second());
ASSERT(SingleTimelineRange::isOffsetValue(primitiveValue));
return { SingleTimelineRange::timelineName(pair->first().valueID()), state ? Style::BuilderConverter::convertLength(*state, primitiveValue) : lengthForCSSValue(RefPtr { primitiveValue.ptr() }, element) };
}
RefPtr<CSSValue> SingleTimelineRange::parse(TimelineRangeValue&& value, RefPtr<Element> element, Type type)
{
if (!element)
return { };
Ref document = element->document();
const auto& parserContext = document->cssParserContext();
return WTF::switchOn(value,
[&](String& rangeString) -> RefPtr<CSSValue> {
return CSSPropertyParserHelpers::parseSingleAnimationRange(rangeString, parserContext, type);
},
[&](TimelineRangeOffset& rangeOffset) -> RefPtr<CSSValue> {
if (auto consumedRangeName = CSSPropertyParserHelpers::parseSingleAnimationRange(rangeOffset.rangeName, parserContext, type)) {
if (rangeOffset.offset)
return CSSValuePair::createNoncoalescing(*consumedRangeName, *rangeOffset.offset->toCSSValue());
return consumedRangeName;
}
if (RefPtr offset = rangeOffset.offset)
return offset->toCSSValue();
return nullptr;
},
[&](RefPtr<CSSKeywordValue> rangeKeyword) {
return rangeKeyword->toCSSValue();
},
[&](RefPtr<CSSNumericValue> rangeValue) {
return rangeValue->toCSSValue();
});
}
SingleTimelineRange::Name SingleTimelineRange::timelineName(CSSValueID valueID)
{
switch (valueID) {
case CSSValueNormal:
return SingleTimelineRange::Name::Normal;
case CSSValueCover:
return SingleTimelineRange::Name::Cover;
case CSSValueContain:
return SingleTimelineRange::Name::Contain;
case CSSValueEntry:
return SingleTimelineRange::Name::Entry;
case CSSValueExit:
return SingleTimelineRange::Name::Exit;
case CSSValueEntryCrossing:
return SingleTimelineRange::Name::EntryCrossing;
case CSSValueExitCrossing:
return SingleTimelineRange::Name::ExitCrossing;
default:
ASSERT_NOT_REACHED();
return SingleTimelineRange::Name::Normal;
}
}
CSSValueID SingleTimelineRange::valueID(SingleTimelineRange::Name range)
{
switch (range) {
case SingleTimelineRange::Name::Normal:
return CSSValueNormal;
case SingleTimelineRange::Name::Cover:
return CSSValueCover;
case SingleTimelineRange::Name::Contain:
return CSSValueContain;
case SingleTimelineRange::Name::Entry:
return CSSValueEntry;
case SingleTimelineRange::Name::Exit:
return CSSValueExit;
case SingleTimelineRange::Name::EntryCrossing:
return CSSValueEntryCrossing;
case SingleTimelineRange::Name::ExitCrossing:
return CSSValueExitCrossing;
case SingleTimelineRange::Name::Omitted:
return CSSValueInvalid;
}
ASSERT_NOT_REACHED();
return CSSValueNormal;
}
WTF::TextStream& operator<<(WTF::TextStream& ts, const SingleTimelineRange& range)
{
switch (range.name) {
case SingleTimelineRange::Name::Normal: ts << "Normal "_s; break;
case SingleTimelineRange::Name::Omitted: ts << "Omitted "_s; break;
case SingleTimelineRange::Name::Cover: ts << "Cover "_s; break;
case SingleTimelineRange::Name::Contain: ts << "Contain "_s; break;
case SingleTimelineRange::Name::Entry: ts << "Entry "_s; break;
case SingleTimelineRange::Name::Exit: ts << "Exit "_s; break;
case SingleTimelineRange::Name::EntryCrossing: ts << "EntryCrossing "_s; break;
case SingleTimelineRange::Name::ExitCrossing: ts << "ExitCrossing "_s; break;
}
ts << range.offset;
return ts;
}
TimelineRange TimelineRange::defaultForScrollTimeline()
{
return {
{ SingleTimelineRange::Name::Omitted, SingleTimelineRange::defaultValue(SingleTimelineRange::Type::Start) },
{ SingleTimelineRange::Name::Omitted, SingleTimelineRange::defaultValue(SingleTimelineRange::Type::End) }
};
}
TimelineRange TimelineRange::defaultForViewTimeline()
{
return {
{ SingleTimelineRange::Name::Cover, SingleTimelineRange::defaultValue(SingleTimelineRange::Type::Start) },
{ SingleTimelineRange::Name::Cover, SingleTimelineRange::defaultValue(SingleTimelineRange::Type::End) }
};
}
} // namespace WebCore