blob: 9b04f1b9d4241523216e7935221d097eb3b21c3c [file] [log] [blame]
/**
* (C) 1999-2003 Lars Knoll ([email protected])
* Copyright (C) 2004-2023 Apple Inc. All rights reserved.
* Copyright (C) 2011 Research In Motion Limited. All rights reserved.
* Copyright (C) 2013 Intel Corporation. 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 "StyleProperties.h"
#include "CSSColorValue.h"
#include "CSSCustomPropertyValue.h"
#include "CSSPendingSubstitutionValue.h"
#include "CSSPrimitiveValue.h"
#include "CSSPropertyInitialValues.h"
#include "CSSPropertyNames.h"
#include "CSSPropertyParserConsumer+Color.h"
#include "CSSPropertyParserConsumer+Font.h"
#include "CSSSerializationContext.h"
#include "CSSStyleProperties.h"
#include "CSSValueKeywords.h"
#include "CSSValueList.h"
#include "Color.h"
#include "ShorthandSerializer.h"
#include "StylePropertiesInlines.h"
#include "StylePropertyShorthand.h"
#include <bitset>
#include <wtf/text/MakeString.h>
#include <wtf/text/StringBuilder.h>
#ifndef NDEBUG
#include <stdio.h>
#include <wtf/text/CString.h>
#endif
namespace WebCore {
DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(StyleProperties);
constexpr unsigned maxShorthandsForLonghand = 4; // FIXME: Generate this from CSSProperties.json and use it for StylePropertyShorthandVector too.
Ref<ImmutableStyleProperties> StyleProperties::immutableCopyIfNeeded() const
{
if (m_isMutable)
return uncheckedDowncast<MutableStyleProperties>(*this).immutableDeduplicatedCopy();
return const_cast<ImmutableStyleProperties&>(uncheckedDowncast<ImmutableStyleProperties>(*this));
}
String serializeLonghandValue(const CSS::SerializationContext& context, CSSPropertyID property, const CSSValue& value)
{
// Longhands set by mask and background shorthands can have comma-separated lists with implicit initial values in them.
// We need to serialize those lists with the actual values, not as "initial".
// Doing this for all CSSValueList with comma separators is better than checking the property is one of those longhands.
// Serializing this way is harmless for other properties; those won't have any implicit initial values.
if (auto* list = dynamicDowncast<CSSValueList>(value); list && list->separator() == CSSValueList::CommaSeparator) {
StringBuilder result;
auto separator = ""_s;
for (Ref individualValue : *list)
result.append(std::exchange(separator, ", "_s), serializeLonghandValue(context, property, individualValue));
return result.toString();
}
return value.isImplicitInitialValue() ? initialValueTextForLonghand(property) : value.cssText(context);
}
inline String StyleProperties::serializeLonghandValue(const CSS::SerializationContext& context, CSSPropertyID propertyID) const
{
return WebCore::serializeLonghandValue(context, propertyID, getPropertyCSSValue(propertyID).get());
}
inline String StyleProperties::serializeShorthandValue(const CSS::SerializationContext& context, CSSPropertyID propertyID) const
{
return WebCore::serializeShorthandValue(context, *this, propertyID);
}
String StyleProperties::getPropertyValue(CSSPropertyID propertyID) const
{
return isLonghand(propertyID) ? serializeLonghandValue(CSS::defaultSerializationContext(), propertyID) : serializeShorthandValue(CSS::defaultSerializationContext(), propertyID);
}
std::optional<Color> StyleProperties::propertyAsColor(CSSPropertyID property) const
{
auto value = getPropertyCSSValue(property);
if (!value)
return std::nullopt;
if (value->isColor())
return CSSColorValue::absoluteColor(*value);
auto serializationString = WebCore::serializeLonghandValue(CSS::defaultSerializationContext(), property, *value);
return CSSPropertyParserHelpers::deprecatedParseColorRawWithoutContext(serializationString);
}
std::optional<CSSValueID> StyleProperties::propertyAsValueID(CSSPropertyID property) const
{
return longhandValueID(property, getPropertyCSSValue(property).get());
}
String StyleProperties::getCustomPropertyValue(const String& propertyName) const
{
RefPtr<CSSValue> value = getCustomPropertyCSSValue(propertyName);
if (value)
return value->cssText(CSS::defaultSerializationContext());
return String();
}
RefPtr<CSSValue> StyleProperties::getPropertyCSSValue(CSSPropertyID propertyID) const
{
int foundPropertyIndex = findPropertyIndex(propertyID);
if (foundPropertyIndex == -1)
return nullptr;
auto property = propertyAt(foundPropertyIndex);
RefPtr value = property.value();
// System fonts are represented as CSSPrimitiveValue for various font subproperties, but these must serialize as the empty string.
// It might be better to implement this as a special CSSValue type instead of turning them into null here.
if (property.shorthandID() == CSSPropertyFont && CSSPropertyParserHelpers::isSystemFontShorthand(valueID(value)))
return nullptr;
return value;
}
RefPtr<CSSValue> StyleProperties::getCustomPropertyCSSValue(const String& propertyName) const
{
int foundPropertyIndex = findCustomPropertyIndex(propertyName);
if (foundPropertyIndex == -1)
return nullptr;
return propertyAt(foundPropertyIndex).value();
}
bool StyleProperties::propertyIsImportant(CSSPropertyID propertyID) const
{
if (isLonghand(propertyID)) {
int foundPropertyIndex = findPropertyIndex(propertyID);
if (foundPropertyIndex != -1)
return propertyAt(foundPropertyIndex).isImportant();
return false;
}
ASSERT(shorthandForProperty(propertyID).length());
for (auto longhand : shorthandForProperty(propertyID)) {
if (!propertyIsImportant(longhand))
return false;
}
return true;
}
bool StyleProperties::customPropertyIsImportant(const String& propertyName) const
{
int foundPropertyIndex = findCustomPropertyIndex(propertyName);
if (foundPropertyIndex != -1)
return propertyAt(foundPropertyIndex).isImportant();
return false;
}
String StyleProperties::getPropertyShorthand(CSSPropertyID propertyID) const
{
int foundPropertyIndex = findPropertyIndex(propertyID);
if (foundPropertyIndex == -1)
return String();
return nameString(propertyAt(foundPropertyIndex).shorthandID());
}
bool StyleProperties::isPropertyImplicit(CSSPropertyID propertyID) const
{
int foundPropertyIndex = findPropertyIndex(propertyID);
if (foundPropertyIndex == -1)
return false;
return propertyAt(foundPropertyIndex).isImplicit();
}
String StyleProperties::asText(const CSS::SerializationContext& context) const
{
return asTextInternal(context).toString();
}
AtomString StyleProperties::asTextAtom(const CSS::SerializationContext& context) const
{
return asTextInternal(context).toAtomString();
}
static constexpr bool canUseShorthandForLonghand(CSSPropertyID shorthandID, CSSPropertyID longhandID)
{
ASSERT(isShorthand(shorthandID));
ASSERT(isLonghand(longhandID));
switch (shorthandID) {
// We are not yet using the CSSPropertyFont shorthand here because our editing code is currently incompatible.
case CSSPropertyFont:
return false;
// Avoid legacy shorthands according to https://www.w3.org/TR/css-cascade-5/#legacy-shorthand
case CSSPropertyPageBreakAfter:
case CSSPropertyPageBreakBefore:
case CSSPropertyPageBreakInside:
case CSSPropertyWebkitBackgroundSize:
case CSSPropertyWebkitBorderRadius:
case CSSPropertyWebkitColumnBreakAfter:
case CSSPropertyWebkitColumnBreakBefore:
case CSSPropertyWebkitColumnBreakInside:
case CSSPropertyWebkitMaskPosition:
case CSSPropertyWebkitPerspective:
case CSSPropertyWebkitTextOrientation:
return false;
// No other browser currently supports text-decoration-skip, so it's currently more web
// compatible to avoid collapsing text-decoration-skip-ink, its only longhand.
case CSSPropertyTextDecorationSkip:
return false;
// FIXME: -webkit-mask is a legacy shorthand but it's used to serialize -webkit-mask-clip,
// which should be a legacy shorthand of mask-clip, but it's implemented as a longhand.
case CSSPropertyWebkitMask:
return longhandID == CSSPropertyWebkitMaskClip;
// FIXME: more mask nonsense.
case CSSPropertyMask:
return longhandID != CSSPropertyMaskComposite && longhandID != CSSPropertyMaskMode && longhandID != CSSPropertyMaskSize;
// FIXME: If font-variant-ligatures is none, this depends on the value of the longhand.
case CSSPropertyFontVariant:
// FIXME: These shorthands are avoided for unknown legacy reasons, probably shouldn't be avoided.
case CSSPropertyColumnRule:
case CSSPropertyColumns:
case CSSPropertyContainer:
case CSSPropertyFontSynthesis:
case CSSPropertyGridArea:
case CSSPropertyGridColumn:
case CSSPropertyGridRow:
case CSSPropertyMaskPosition:
case CSSPropertyOffset:
case CSSPropertyTextEmphasis:
case CSSPropertyWebkitTextStroke:
return false;
default:
return true;
}
}
StringBuilder StyleProperties::asTextInternal(const CSS::SerializationContext& context) const
{
StringBuilder result;
constexpr unsigned shorthandPropertyCount = lastShorthandProperty - firstShorthandProperty + 1;
std::bitset<shorthandPropertyCount> shorthandPropertyUsed;
std::bitset<shorthandPropertyCount> shorthandPropertyAppeared;
unsigned numDecls = 0;
for (auto property : *this) {
auto propertyID = property.id();
ASSERT(isLonghand(propertyID) || propertyID == CSSPropertyCustom);
Vector<CSSPropertyID, maxShorthandsForLonghand> shorthands;
if (RefPtr substitutionValue = dynamicDowncast<CSSPendingSubstitutionValue>(property.value()))
shorthands.append(substitutionValue->shorthandPropertyId());
else {
for (auto& shorthand : matchingShorthandsForLonghand(propertyID)) {
if (canUseShorthandForLonghand(shorthand.id(), propertyID))
shorthands.append(shorthand.id());
}
}
String value;
bool alreadyUsedShorthand = false;
for (auto& shorthandPropertyID : shorthands) {
ASSERT(isShorthand(shorthandPropertyID));
unsigned shorthandPropertyIndex = shorthandPropertyID - firstShorthandProperty;
ASSERT(shorthandPropertyIndex < shorthandPropertyUsed.size());
if (shorthandPropertyUsed[shorthandPropertyIndex]) {
alreadyUsedShorthand = true;
break;
}
if (shorthandPropertyAppeared[shorthandPropertyIndex])
continue;
shorthandPropertyAppeared.set(shorthandPropertyIndex);
value = serializeShorthandValue(context, shorthandPropertyID);
if (!value.isNull()) {
propertyID = shorthandPropertyID;
shorthandPropertyUsed.set(shorthandPropertyIndex);
break;
}
}
if (alreadyUsedShorthand)
continue;
if (value.isNull())
value = WebCore::serializeLonghandValue(context, propertyID, *property.value());
if (numDecls++)
result.append(' ');
if (propertyID == CSSPropertyCustom)
result.append(downcast<CSSCustomPropertyValue>(*property.value()).name());
else
result.append(nameLiteral(propertyID));
result.append(": "_s, value, property.isImportant() ? " !important"_s : ""_s, ';');
}
ASSERT(!numDecls ^ !result.isEmpty());
return result;
}
bool StyleProperties::hasCSSOMWrapper() const
{
auto* mutableProperties = dynamicDowncast<MutableStyleProperties>(*this);
return mutableProperties && mutableProperties->m_cssomWrapper;
}
bool StyleProperties::traverseSubresources(NOESCAPE const Function<bool(const CachedResource&)>& handler) const
{
for (auto property : *this) {
if (property.value()->traverseSubresources(handler))
return true;
}
return false;
}
bool StyleProperties::mayDependOnBaseURL() const
{
bool result = false;
Function<IterationStatus(CSSValue&)> func = [&](CSSValue& value) -> IterationStatus {
if (value.mayDependOnBaseURL()) {
result = true;
return IterationStatus::Done;
}
return value.visitChildren(func);
};
for (auto property : *this) {
if (func(*property.value()) == IterationStatus::Done)
return result;
}
return false;
}
bool StyleProperties::propertyMatches(CSSPropertyID propertyID, const CSSValue* propertyValue) const
{
int foundPropertyIndex = findPropertyIndex(propertyID);
if (foundPropertyIndex == -1)
return false;
return propertyAt(foundPropertyIndex).value()->equals(*propertyValue);
}
Ref<MutableStyleProperties> StyleProperties::mutableCopy() const
{
return adoptRef(*new MutableStyleProperties(*this));
}
Ref<MutableStyleProperties> StyleProperties::copyProperties(std::span<const CSSPropertyID> properties) const
{
auto vector = WTF::compactMap(properties, [&](auto& property) -> std::optional<CSSProperty> {
if (auto value = getPropertyCSSValue(property))
return CSSProperty(property, value.releaseNonNull());
return std::nullopt;
});
return MutableStyleProperties::create(WTF::move(vector));
}
CSSStyleProperties* MutableStyleProperties::cssStyleProperties()
{
return m_cssomWrapper.get();
}
CSSStyleProperties& MutableStyleProperties::ensureCSSStyleProperties()
{
if (m_cssomWrapper) {
ASSERT(!static_cast<CSSStyleProperties*>(m_cssomWrapper.get())->parentRule());
ASSERT(!m_cssomWrapper->parentElement());
return *m_cssomWrapper;
}
lazyInitialize(m_cssomWrapper, makeUniqueWithoutRefCountedCheck<PropertySetCSSStyleProperties>(*this));
return *m_cssomWrapper;
}
unsigned StyleProperties::averageSizeInBytes()
{
// Please update this if the storage scheme changes so that this longer reflects the actual size.
return ImmutableStyleProperties::objectSize(4);
}
// See the function above if you need to update this.
struct SameSizeAsStyleProperties : public RefCounted<SameSizeAsStyleProperties> {
unsigned bitfield;
};
static_assert(sizeof(StyleProperties) == sizeof(SameSizeAsStyleProperties), "style property set should stay small");
#ifndef NDEBUG
void StyleProperties::showStyle()
{
SAFE_FPRINTF(stderr, "%s\n", asText(CSS::defaultSerializationContext()).ascii());
}
#endif
String StyleProperties::PropertyReference::cssName() const
{
if (id() == CSSPropertyCustom)
return downcast<CSSCustomPropertyValue>(*value()).name();
return nameString(id());
}
String StyleProperties::PropertyReference::cssText(const CSS::SerializationContext& context) const
{
return makeString(cssName(), ": "_s, WebCore::serializeLonghandValue(context, id(), *m_value), isImportant() ? " !important;"_s : ";"_s);
}
} // namespace WebCore