blob: 8928db3ef4ac63490d60e13d1e6b9ad96342394f [file]
/*
* (C) 1999-2003 Lars Knoll (knoll@kde.org)
* Copyright (C) 2004-2023 Apple Inc. All rights reserved.
* Copyright (C) 2026 Samuel Weinig <sam@webkit.org>
*
* 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 "CSSPrimitiveValue.h"
#include "CSSCalcSymbolTable.h"
#include "CSSCalcValue.h"
#include "CSSMarkup.h"
#include "CSSParserIdioms.h"
#include "CSSPrimitiveNumericCategory.h"
#include "CSSPrimitiveNumericTypes+ComputedStyleDependencies.h"
#include "CSSPrimitiveNumericTypes+Serialization.h"
#include "CSSSerializationContext.h"
#include "CSSToLengthConversionData.h"
#include "CSSUnevaluatedCalc.h"
#include "CSSValueKeywords.h"
#include "CSSValuePool.h"
#include "ComputedStyleDependencies.h"
#include "ContainerQueryEvaluator.h"
#include "DeprecatedCSSOMPrimitiveValue.h"
#include "FontCascade.h"
#include "NodeRenderStyle.h"
#include "RenderBoxInlines.h"
#include "RenderView.h"
#include "StyleComputedStyle.h"
#include "StyleLengthResolution.h"
#include "StylePrimitiveNumericTypes+Rounding.h"
#include <type_traits>
#include <wtf/Hasher.h>
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/StdLibExtras.h>
#include <wtf/text/MakeString.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
static HashMap<const CSSPrimitiveValue*, String>& NODELETE serializedPrimitiveValues()
{
static NeverDestroyed<HashMap<const CSSPrimitiveValue*, String>> map;
return map;
}
CSSUnitType CSSPrimitiveValue::primitiveType() const
{
if (RefPtr calcValue = const_cast<CSSCalc::Value*>(cssCalcValue()))
return CSS::UnevaluatedCalcBase { calcValue.releaseNonNull() }.primitiveType();
return primitiveUnitType();
}
CSSPrimitiveValue::CSSPrimitiveValue(double number, CSSUnitType type)
: CSSValue(ClassType::Primitive)
{
setPrimitiveUnitType(type);
m_value.number = number;
}
CSSPrimitiveValue::CSSPrimitiveValue(StaticCSSValueTag, double number, CSSUnitType type)
: CSSPrimitiveValue(number, type)
{
makeStatic();
}
CSSPrimitiveValue::CSSPrimitiveValue(CSS::UnevaluatedCalcBase&& value)
: CSSValue(ClassType::Primitive)
{
setPrimitiveUnitType(CSSUnitType::Calc);
m_value.calc = &value.leakRef();
}
CSSPrimitiveValue::~CSSPrimitiveValue()
{
auto type = primitiveUnitType();
switch (type) {
case CSSUnitType::Calc:
m_value.calc->deref();
break;
case CSSUnitType::CalcPercentageWithAngle:
case CSSUnitType::CalcPercentageWithLength:
ASSERT_NOT_REACHED();
break;
case CSSUnitType::Number:
case CSSUnitType::Integer:
case CSSUnitType::Percentage:
case CSSUnitType::Em:
case CSSUnitType::QuirkyEm:
case CSSUnitType::Ex:
case CSSUnitType::Cap:
case CSSUnitType::Ch:
case CSSUnitType::Ic:
case CSSUnitType::Rcap:
case CSSUnitType::Rch:
case CSSUnitType::Rem:
case CSSUnitType::Rex:
case CSSUnitType::Ric:
case CSSUnitType::Px:
case CSSUnitType::Cm:
case CSSUnitType::Mm:
case CSSUnitType::In:
case CSSUnitType::Pt:
case CSSUnitType::Pc:
case CSSUnitType::Deg:
case CSSUnitType::Rad:
case CSSUnitType::Grad:
case CSSUnitType::Ms:
case CSSUnitType::S:
case CSSUnitType::Hz:
case CSSUnitType::Khz:
case CSSUnitType::Turn:
case CSSUnitType::Vw:
case CSSUnitType::Vh:
case CSSUnitType::Vmin:
case CSSUnitType::Vmax:
case CSSUnitType::Vb:
case CSSUnitType::Vi:
case CSSUnitType::Svw:
case CSSUnitType::Svh:
case CSSUnitType::Svmin:
case CSSUnitType::Svmax:
case CSSUnitType::Svb:
case CSSUnitType::Svi:
case CSSUnitType::Lvw:
case CSSUnitType::Lvh:
case CSSUnitType::Lvmin:
case CSSUnitType::Lvmax:
case CSSUnitType::Lvb:
case CSSUnitType::Lvi:
case CSSUnitType::Dvw:
case CSSUnitType::Dvh:
case CSSUnitType::Dvmin:
case CSSUnitType::Dvmax:
case CSSUnitType::Dvb:
case CSSUnitType::Dvi:
case CSSUnitType::Dppx:
case CSSUnitType::X:
case CSSUnitType::Dpi:
case CSSUnitType::Dpcm:
case CSSUnitType::Fr:
case CSSUnitType::Q:
case CSSUnitType::Lh:
case CSSUnitType::Rlh:
case CSSUnitType::Unknown:
case CSSUnitType::Cqw:
case CSSUnitType::Cqh:
case CSSUnitType::Cqi:
case CSSUnitType::Cqb:
case CSSUnitType::Cqmin:
case CSSUnitType::Cqmax:
break;
}
if (m_hasCachedCSSText) {
ASSERT(isMainThread());
ASSERT(serializedPrimitiveValues().contains(this));
serializedPrimitiveValues().remove(this);
}
}
static CSSPrimitiveValue* valueFromPool(std::span<AlignedStorage<CSSPrimitiveValue>> pool, double value)
{
// Casting to a signed integer first since casting a negative floating point value to an unsigned
// integer is undefined behavior.
unsigned poolIndex = static_cast<unsigned>(static_cast<int>(value));
double roundTripValue = poolIndex;
if (equalSpans(asByteSpan(value), asByteSpan(roundTripValue)) && poolIndex < pool.size())
return pool[poolIndex].get();
return nullptr;
}
Ref<CSSPrimitiveValue> CSSPrimitiveValue::create(double value)
{
if (RefPtr result = valueFromPool(staticCSSValuePool->m_numberValues, value))
return result.releaseNonNull();
return adoptRef(*new CSSPrimitiveValue(value, CSSUnitType::Number));
}
Ref<CSSPrimitiveValue> CSSPrimitiveValue::create(double value, CSSUnitType type)
{
switch (type) {
case CSSUnitType::Number:
if (RefPtr result = valueFromPool(staticCSSValuePool->m_numberValues, value))
return result.releaseNonNull();
break;
case CSSUnitType::Percentage:
if (RefPtr result = valueFromPool(staticCSSValuePool->m_percentageValues, value))
return result.releaseNonNull();
break;
case CSSUnitType::Px:
if (RefPtr result = valueFromPool(staticCSSValuePool->m_pixelValues, value))
return result.releaseNonNull();
break;
default:
break;
}
return adoptRef(*new CSSPrimitiveValue(value, type));
}
Ref<CSSPrimitiveValue> CSSPrimitiveValue::create(CSS::UnevaluatedCalcBase value)
{
return adoptRef(*new CSSPrimitiveValue(WTF::move(value)));
}
Ref<CSSPrimitiveValue> CSSPrimitiveValue::createInteger(double value)
{
return adoptRef(*new CSSPrimitiveValue(value, CSSUnitType::Integer));
}
std::optional<bool> CSSPrimitiveValue::isZero() const
{
if (isCalculated())
return std::nullopt;
return !m_value.number;
}
std::optional<bool> CSSPrimitiveValue::isOne() const
{
if (isCalculated())
return std::nullopt;
return m_value.number == 1;
}
std::optional<bool> CSSPrimitiveValue::isPositive() const
{
if (isCalculated())
return std::nullopt;
return m_value.number > 0;
}
std::optional<bool> CSSPrimitiveValue::isNegative() const
{
if (isCalculated())
return std::nullopt;
return m_value.number < 0;
}
NEVER_INLINE String CSSPrimitiveValue::formatNumberValue(ASCIILiteral suffix) const
{
return CSS::formatCSSNumberValue(CSS::SerializableNumber { m_value.number, suffix });
}
NEVER_INLINE String CSSPrimitiveValue::formatIntegerValue(ASCIILiteral suffix) const
{
if (!std::isfinite(m_value.number))
return CSS::formatNonfiniteCSSNumberValue(CSS::SerializableNumber { m_value.number, suffix });
return makeString(m_value.number, suffix);
}
ALWAYS_INLINE String CSSPrimitiveValue::serializeInternal(const CSS::SerializationContext& context) const
{
auto type = primitiveUnitType();
switch (type) {
case CSSUnitType::Cap:
case CSSUnitType::Ch:
case CSSUnitType::Cm:
case CSSUnitType::Cqb:
case CSSUnitType::Cqh:
case CSSUnitType::Cqi:
case CSSUnitType::Cqmax:
case CSSUnitType::Cqmin:
case CSSUnitType::Cqw:
case CSSUnitType::Deg:
case CSSUnitType::Dpcm:
case CSSUnitType::Dpi:
case CSSUnitType::Dppx:
case CSSUnitType::Dvb:
case CSSUnitType::Dvh:
case CSSUnitType::Dvi:
case CSSUnitType::Dvmax:
case CSSUnitType::Dvmin:
case CSSUnitType::Dvw:
case CSSUnitType::Em:
case CSSUnitType::Ex:
case CSSUnitType::Fr:
case CSSUnitType::Grad:
case CSSUnitType::Hz:
case CSSUnitType::Ic:
case CSSUnitType::In:
case CSSUnitType::Khz:
case CSSUnitType::Lh:
case CSSUnitType::Lvb:
case CSSUnitType::Lvh:
case CSSUnitType::Lvi:
case CSSUnitType::Lvmax:
case CSSUnitType::Lvmin:
case CSSUnitType::Lvw:
case CSSUnitType::Mm:
case CSSUnitType::Ms:
case CSSUnitType::Number:
case CSSUnitType::Pc:
case CSSUnitType::Percentage:
case CSSUnitType::Pt:
case CSSUnitType::Px:
case CSSUnitType::Q:
case CSSUnitType::Rad:
case CSSUnitType::Rcap:
case CSSUnitType::Rch:
case CSSUnitType::Rem:
case CSSUnitType::Rex:
case CSSUnitType::Ric:
case CSSUnitType::Rlh:
case CSSUnitType::S:
case CSSUnitType::Svb:
case CSSUnitType::Svh:
case CSSUnitType::Svi:
case CSSUnitType::Svmax:
case CSSUnitType::Svmin:
case CSSUnitType::Svw:
case CSSUnitType::Turn:
case CSSUnitType::Vb:
case CSSUnitType::Vh:
case CSSUnitType::Vi:
case CSSUnitType::Vmax:
case CSSUnitType::Vmin:
case CSSUnitType::Vw:
case CSSUnitType::X:
return formatNumberValue(unitTypeString(type));
case CSSUnitType::Calc:
return CSS::UnevaluatedCalcBase { protect(const_cast<CSSCalc::Value&>(*cssCalcValue())) }.serializationForCSS(context);
case CSSUnitType::Integer:
return formatIntegerValue(""_s);
case CSSUnitType::QuirkyEm:
return formatNumberValue("em"_s);
case CSSUnitType::CalcPercentageWithAngle:
case CSSUnitType::CalcPercentageWithLength:
case CSSUnitType::Unknown:
break;
}
ASSERT_NOT_REACHED();
return String();
}
String CSSPrimitiveValue::customCSSText(const CSS::SerializationContext& context) const
{
switch (primitiveUnitType()) {
case CSSUnitType::Unknown:
return String();
default:
// The memoization map and m_hasCachedCSSText are not thread-safe, and worker threads can
// serialize FontFace descriptors concurrently with the main thread. Only memoize on the
// main thread; other threads serialize without touching the shared state.
if (!isMainThread())
return serializeInternal(context);
auto& map = serializedPrimitiveValues();
ASSERT(map.contains(this) == m_hasCachedCSSText);
if (m_hasCachedCSSText)
return map.get(this);
String serializedValue = serializeInternal(context);
// m_hasCachedCSSText is written here without synchronization, so it must live in its own
// memory location (not a bit-field packed with concurrently-read members).
static_assert(std::is_member_object_pointer_v<decltype(&CSSPrimitiveValue::m_hasCachedCSSText)>,
"m_hasCachedCSSText must not be a bit-field");
m_hasCachedCSSText = true;
map.add(this, serializedValue);
return serializedValue;
}
}
bool CSSPrimitiveValue::equals(const CSSPrimitiveValue& other) const
{
if (primitiveUnitType() != other.primitiveUnitType())
return false;
switch (primitiveUnitType()) {
case CSSUnitType::Unknown:
return false;
case CSSUnitType::Number:
case CSSUnitType::Integer:
case CSSUnitType::Percentage:
case CSSUnitType::Em:
case CSSUnitType::QuirkyEm:
case CSSUnitType::Ex:
case CSSUnitType::Cap:
case CSSUnitType::Ch:
case CSSUnitType::Ic:
case CSSUnitType::Rcap:
case CSSUnitType::Rch:
case CSSUnitType::Rem:
case CSSUnitType::Rex:
case CSSUnitType::Ric:
case CSSUnitType::Px:
case CSSUnitType::Cm:
case CSSUnitType::Dppx:
case CSSUnitType::X:
case CSSUnitType::Dpi:
case CSSUnitType::Dpcm:
case CSSUnitType::Mm:
case CSSUnitType::In:
case CSSUnitType::Pt:
case CSSUnitType::Pc:
case CSSUnitType::Deg:
case CSSUnitType::Rad:
case CSSUnitType::Grad:
case CSSUnitType::Ms:
case CSSUnitType::S:
case CSSUnitType::Hz:
case CSSUnitType::Khz:
case CSSUnitType::Turn:
case CSSUnitType::Vw:
case CSSUnitType::Vh:
case CSSUnitType::Vmin:
case CSSUnitType::Vmax:
case CSSUnitType::Vb:
case CSSUnitType::Vi:
case CSSUnitType::Svw:
case CSSUnitType::Svh:
case CSSUnitType::Svmin:
case CSSUnitType::Svmax:
case CSSUnitType::Svb:
case CSSUnitType::Svi:
case CSSUnitType::Lvw:
case CSSUnitType::Lvh:
case CSSUnitType::Lvmin:
case CSSUnitType::Lvmax:
case CSSUnitType::Lvb:
case CSSUnitType::Lvi:
case CSSUnitType::Dvw:
case CSSUnitType::Dvh:
case CSSUnitType::Dvmin:
case CSSUnitType::Dvmax:
case CSSUnitType::Dvb:
case CSSUnitType::Dvi:
case CSSUnitType::Fr:
case CSSUnitType::Q:
case CSSUnitType::Lh:
case CSSUnitType::Rlh:
case CSSUnitType::Cqw:
case CSSUnitType::Cqh:
case CSSUnitType::Cqi:
case CSSUnitType::Cqb:
case CSSUnitType::Cqmin:
case CSSUnitType::Cqmax:
return m_value.number == other.m_value.number;
case CSSUnitType::Calc:
return CSS::UnevaluatedCalcBase { protect(const_cast<CSSCalc::Value&>(*cssCalcValue())) } == CSS::UnevaluatedCalcBase { protect(const_cast<CSSCalc::Value&>(*other.cssCalcValue())) };
case CSSUnitType::CalcPercentageWithAngle:
case CSSUnitType::CalcPercentageWithLength:
// FIXME: seems like these should be handled.
ASSERT_NOT_REACHED();
break;
}
return false;
}
bool CSSPrimitiveValue::addDerivedHash(Hasher& hasher) const
{
add(hasher, primitiveUnitType());
switch (primitiveUnitType()) {
case CSSUnitType::Unknown:
break;
case CSSUnitType::Number:
case CSSUnitType::Integer:
case CSSUnitType::Percentage:
case CSSUnitType::Em:
case CSSUnitType::QuirkyEm:
case CSSUnitType::Ex:
case CSSUnitType::Cap:
case CSSUnitType::Ch:
case CSSUnitType::Ic:
case CSSUnitType::Rcap:
case CSSUnitType::Rch:
case CSSUnitType::Rem:
case CSSUnitType::Rex:
case CSSUnitType::Ric:
case CSSUnitType::Px:
case CSSUnitType::Cm:
case CSSUnitType::Dppx:
case CSSUnitType::X:
case CSSUnitType::Dpi:
case CSSUnitType::Dpcm:
case CSSUnitType::Mm:
case CSSUnitType::In:
case CSSUnitType::Pt:
case CSSUnitType::Pc:
case CSSUnitType::Deg:
case CSSUnitType::Rad:
case CSSUnitType::Grad:
case CSSUnitType::Ms:
case CSSUnitType::S:
case CSSUnitType::Hz:
case CSSUnitType::Khz:
case CSSUnitType::Turn:
case CSSUnitType::Vw:
case CSSUnitType::Vh:
case CSSUnitType::Vmin:
case CSSUnitType::Vmax:
case CSSUnitType::Vb:
case CSSUnitType::Vi:
case CSSUnitType::Svw:
case CSSUnitType::Svh:
case CSSUnitType::Svmin:
case CSSUnitType::Svmax:
case CSSUnitType::Svb:
case CSSUnitType::Svi:
case CSSUnitType::Lvw:
case CSSUnitType::Lvh:
case CSSUnitType::Lvmin:
case CSSUnitType::Lvmax:
case CSSUnitType::Lvb:
case CSSUnitType::Lvi:
case CSSUnitType::Dvw:
case CSSUnitType::Dvh:
case CSSUnitType::Dvmin:
case CSSUnitType::Dvmax:
case CSSUnitType::Dvb:
case CSSUnitType::Dvi:
case CSSUnitType::Fr:
case CSSUnitType::Q:
case CSSUnitType::Lh:
case CSSUnitType::Rlh:
case CSSUnitType::Cqw:
case CSSUnitType::Cqh:
case CSSUnitType::Cqi:
case CSSUnitType::Cqb:
case CSSUnitType::Cqmin:
case CSSUnitType::Cqmax:
add(hasher, m_value.number);
break;
case CSSUnitType::Calc:
add(hasher, m_value.calc);
break;
case CSSUnitType::CalcPercentageWithAngle:
case CSSUnitType::CalcPercentageWithLength:
ASSERT_NOT_REACHED();
return false;
}
return true;
}
// https://drafts.css-houdini.org/css-properties-values-api/#dependency-cycles
void CSSPrimitiveValue::collectComputedStyleDependencies(ComputedStyleDependencies& dependencies) const
{
if (RefPtr calcValue = const_cast<CSSCalc::Value*>(cssCalcValue())) {
CSS::UnevaluatedCalcBase { calcValue.releaseNonNull() }.collectComputedStyleDependencies(dependencies);
return;
}
if (auto lengthUnit = CSS::toLengthUnit(primitiveUnitType()))
CSS::collectComputedStyleDependencies(dependencies, *lengthUnit);
}
Ref<DeprecatedCSSOMValue> CSSPrimitiveValue::customCreateDeprecatedCSSOMWrapper(CSSStyleDeclaration& owner) const
{
return switchOn([&](const auto& value) { return DeprecatedCSSOMPrimitiveValue::create(value, owner); });
}
} // namespace WebCore