blob: 38748aa22d0ebd67ee136e3ef90cbcc7d65a88c6 [file] [log] [blame]
/*
* Copyright (C) 2021 Tyler Wilcock <twilco.o@protonmail.com>.
*
* 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. ``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
* 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 "CSSCounterStyleRule.h"
#include "CSSPropertyParser.h"
#include "CSSStyleSheet.h"
#include "CSSTokenizer.h"
#include "Pair.h"
#include <wtf/text/StringBuilder.h>
namespace WebCore {
StyleRuleCounterStyle::StyleRuleCounterStyle(const AtomString& name, Ref<StyleProperties>&& properties)
: StyleRuleBase(StyleRuleType::CounterStyle)
, m_name(name)
, m_properties(WTFMove(properties))
{
}
Ref<StyleRuleCounterStyle> StyleRuleCounterStyle::create(const AtomString& name, Ref<StyleProperties>&& properties)
{
return adoptRef(*new StyleRuleCounterStyle(name, WTFMove(properties)));
}
static CounterStyleSystem toCounterStyleSystemEnum(RefPtr<CSSValue> system)
{
if (!system || !system->isPrimitiveValue())
return CounterStyleSystem::Symbolic;
auto& primitiveSystemValue = downcast<CSSPrimitiveValue>(*system);
ASSERT(primitiveSystemValue.isValueID() || primitiveSystemValue.isPair());
CSSValueID systemKeyword = CSSValueInvalid;
if (primitiveSystemValue.isValueID())
systemKeyword = primitiveSystemValue.valueID();
else if (auto* pair = primitiveSystemValue.pairValue()) {
// This value must be `fixed` or `extends`, both of which can or must have an additional component.
auto firstValue = pair->first();
ASSERT(firstValue && firstValue->isValueID());
if (firstValue)
systemKeyword = firstValue->valueID();
}
switch (systemKeyword) {
case CSSValueCyclic:
return CounterStyleSystem::Cyclic;
case CSSValueFixed:
return CounterStyleSystem::Fixed;
case CSSValueSymbolic:
return CounterStyleSystem::Symbolic;
case CSSValueAlphabetic:
return CounterStyleSystem::Alphabetic;
case CSSValueNumeric:
return CounterStyleSystem::Numeric;
case CSSValueAdditive:
return CounterStyleSystem::Additive;
case CSSValueExtends:
return CounterStyleSystem::Extends;
default:
ASSERT_NOT_REACHED();
return CounterStyleSystem::Symbolic;
}
}
static bool symbolsValidForSystem(CounterStyleSystem system, RefPtr<CSSValue> symbols, RefPtr<CSSValue> additiveSymbols)
{
switch (system) {
case CounterStyleSystem::Cyclic:
case CounterStyleSystem::Fixed:
case CounterStyleSystem::Symbolic:
return symbols && symbols->isValueList() && downcast<CSSValueList>(*symbols).length();
case CounterStyleSystem::Alphabetic:
case CounterStyleSystem::Numeric:
return symbols && symbols->isValueList() && downcast<CSSValueList>(*symbols).length() >= 2u;
case CounterStyleSystem::Additive:
return additiveSymbols && additiveSymbols->isValueList() && downcast<CSSValueList>(*additiveSymbols).length();
case CounterStyleSystem::Extends:
return !symbols && !additiveSymbols;
default:
ASSERT_NOT_REACHED();
return false;
}
}
bool StyleRuleCounterStyle::newValueInvalidOrEqual(CSSPropertyID propertyID, const RefPtr<CSSValue> newValue) const
{
auto currentValue = m_properties->getPropertyCSSValue(propertyID);
if (compareCSSValuePtr(currentValue, newValue))
return true;
RefPtr<CSSValue> symbols;
RefPtr<CSSValue> additiveSymbols;
switch (propertyID) {
case CSSPropertySystem:
// If the attribute being set is `system`, and the new value would change the algorithm used, do nothing
// and abort these steps.
// (It's okay to change an aspect of the algorithm, like the first symbol value of a `fixed` system.)
return toCounterStyleSystemEnum(currentValue) != toCounterStyleSystemEnum(newValue);
case CSSPropertySymbols:
symbols = newValue;
additiveSymbols = m_properties->getPropertyCSSValue(CSSPropertyAdditiveSymbols);
break;
case CSSPropertyAdditiveSymbols:
symbols = m_properties->getPropertyCSSValue(CSSPropertySymbols);
additiveSymbols = newValue;
break;
default:
return false;
}
auto system = m_properties->getPropertyCSSValue(CSSPropertySystem);
return symbolsValidForSystem(toCounterStyleSystemEnum(system), symbols, additiveSymbols);
}
StyleRuleCounterStyle::~StyleRuleCounterStyle() = default;
MutableStyleProperties& StyleRuleCounterStyle::mutableProperties()
{
if (!is<MutableStyleProperties>(m_properties))
m_properties = m_properties->mutableCopy();
return downcast<MutableStyleProperties>(m_properties.get());
}
Ref<CSSCounterStyleRule> CSSCounterStyleRule::create(StyleRuleCounterStyle& rule, CSSStyleSheet* sheet)
{
return adoptRef(*new CSSCounterStyleRule(rule, sheet));
}
CSSCounterStyleRule::CSSCounterStyleRule(StyleRuleCounterStyle& counterStyleRule, CSSStyleSheet* parent)
: CSSRule(parent)
, m_counterStyleRule(counterStyleRule)
{
}
CSSCounterStyleRule::~CSSCounterStyleRule() = default;
String CSSCounterStyleRule::cssText() const
{
String systemText = system();
const char* systemPrefix = systemText.isEmpty() ? "" : " system: ";
const char* systemSuffix = systemText.isEmpty() ? "" : ";";
String symbolsText = symbols();
const char* symbolsPrefix = symbolsText.isEmpty() ? "" : " symbols: ";
const char* symbolsSuffix = symbolsText.isEmpty() ? "" : ";";
String additiveSymbolsText = additiveSymbols();
const char* additiveSymbolsPrefix = additiveSymbolsText.isEmpty() ? "" : " additive-symbols: ";
const char* additiveSymbolsSuffix = additiveSymbolsText.isEmpty() ? "" : ";";
String negativeText = negative();
const char* negativePrefix = negativeText.isEmpty() ? "" : " negative: ";
const char* negativeSuffix = negativeText.isEmpty() ? "" : ";";
String prefixText = prefix();
const char* prefixTextPrefix = prefixText.isEmpty() ? "" : " prefix: ";
const char* prefixTextSuffix = prefixText.isEmpty() ? "" : ";";
String suffixText = suffix();
const char* suffixTextPrefix = suffixText.isEmpty() ? "" : " suffix: ";
const char* suffixTextSuffix = suffixText.isEmpty() ? "" : ";";
String padText = pad();
const char* padPrefix = padText.isEmpty() ? "" : " pad: ";
const char* padSuffix = padText.isEmpty() ? "" : ";";
String rangeText = range();
const char* rangePrefix = rangeText.isEmpty() ? "" : " range: ";
const char* rangeSuffix = rangeText.isEmpty() ? "" : ";";
String fallbackText = fallback();
const char* fallbackPrefix = fallbackText.isEmpty() ? "" : " fallback: ";
const char* fallbackSuffix = fallbackText.isEmpty() ? "" : ";";
String speakAsText = speakAs();
const char* speakAsPrefix = speakAsText.isEmpty() ? "" : " speak-as: ";
const char* speakAsSuffix = speakAsText.isEmpty() ? "" : ";";
return makeString("@counter-style ", name(), " {",
systemPrefix, systemText, systemSuffix,
symbolsPrefix, symbolsText, symbolsSuffix,
additiveSymbolsPrefix, additiveSymbolsText, additiveSymbolsSuffix,
negativePrefix, negativeText, negativeSuffix,
prefixTextPrefix, prefixText, prefixTextSuffix,
suffixTextPrefix, suffixText, suffixTextSuffix,
padPrefix, padText, padSuffix,
rangePrefix, rangeText, rangeSuffix,
fallbackPrefix, fallbackText, fallbackSuffix,
speakAsPrefix, speakAsText, speakAsSuffix,
" }");
}
void CSSCounterStyleRule::reattach(StyleRuleBase& rule)
{
m_counterStyleRule = static_cast<StyleRuleCounterStyle&>(rule);
}
// https://drafts.csswg.org/css-counter-styles-3/#dom-csscounterstylerule-name
void CSSCounterStyleRule::setName(const String& text)
{
auto tokenizer = CSSTokenizer(text);
auto tokenRange = tokenizer.tokenRange();
auto name = CSSPropertyParserHelpers::consumeCounterStyleNameInPrelude(tokenRange);
if (name.isNull() || name == m_counterStyleRule->name())
return;
CSSStyleSheet::RuleMutationScope mutationScope(this);
m_counterStyleRule->setName(name);
}
void CSSCounterStyleRule::setterInternal(CSSPropertyID propertyID, const String& valueText)
{
auto tokenizer = CSSTokenizer(valueText);
auto tokenRange = tokenizer.tokenRange();
auto newValue = CSSPropertyParser::parseCounterStyleDescriptor(propertyID, tokenRange, parserContext());
if (m_counterStyleRule->newValueInvalidOrEqual(propertyID, newValue))
return;
CSSStyleSheet::RuleMutationScope mutationScope(this);
m_counterStyleRule->mutableProperties().setProperty(propertyID, WTFMove(newValue));
}
void CSSCounterStyleRule::setSystem(const String& text)
{
setterInternal(CSSPropertySystem, text);
}
void CSSCounterStyleRule::setNegative(const String& text)
{
setterInternal(CSSPropertyNegative, text);
}
void CSSCounterStyleRule::setPrefix(const String& text)
{
setterInternal(CSSPropertyPrefix, text);
}
void CSSCounterStyleRule::setSuffix(const String& text)
{
setterInternal(CSSPropertySuffix, text);
}
void CSSCounterStyleRule::setRange(const String& text)
{
setterInternal(CSSPropertyRange, text);
}
void CSSCounterStyleRule::setPad(const String& text)
{
setterInternal(CSSPropertyPad, text);
}
void CSSCounterStyleRule::setFallback(const String& text)
{
setterInternal(CSSPropertyFallback, text);
}
void CSSCounterStyleRule::setSymbols(const String& text)
{
setterInternal(CSSPropertySymbols, text);
}
void CSSCounterStyleRule::setAdditiveSymbols(const String& text)
{
setterInternal(CSSPropertyAdditiveSymbols, text);
}
void CSSCounterStyleRule::setSpeakAs(const String& text)
{
setterInternal(CSSPropertySpeakAs, text);
}
} // namespace WebCore