blob: aff78bf0d9ea670c6fbf667807afac11566979e7 [file] [log] [blame]
/*
* Copyright (C) 2023 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. ``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 "CSSCounterStyleRegistry.h"
#include "CSSCounterStyle.h"
#include "CSSPrimitiveValue.h"
#include "CSSValuePair.h"
#include "StyleListStyleType.h"
#include <wtf/NeverDestroyed.h>
namespace WebCore {
DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(CSSCounterStyleRegistry);
void CSSCounterStyleRegistry::resolveUserAgentReferences()
{
for (auto& [name, counter] : userAgentCounterStyles()) {
// decimal counter has no fallback or extended references because it is the last resource for both cases.
if (counter->name() == "decimal"_s)
continue;
if (counter->isFallbackUnresolved())
resolveFallbackReference(counter);
if (counter->isExtendsSystem() && counter->isExtendsUnresolved())
resolveExtendsReference(counter);
}
}
void CSSCounterStyleRegistry::resolveReferencesIfNeeded()
{
if (!m_hasUnresolvedReferences)
return;
for (auto& [name, counter] : m_authorCounterStyles) {
if (counter->isFallbackUnresolved())
resolveFallbackReference(counter, &m_authorCounterStyles);
if (counter->isExtendsSystem() && counter->isExtendsUnresolved())
resolveExtendsReference(counter, &m_authorCounterStyles);
}
m_hasUnresolvedReferences = false;
}
void CSSCounterStyleRegistry::resolveExtendsReference(CSSCounterStyle& counterStyle, CounterStyleMap* map)
{
HashSet<CSSCounterStyle*> countersInChain;
resolveExtendsReference(counterStyle, countersInChain, map);
}
void CSSCounterStyleRegistry::resolveExtendsReference(CSSCounterStyle& counter, HashSet<CSSCounterStyle*>& countersInChain, CounterStyleMap* map)
{
ASSERT(counter.isExtendsSystem() && counter.isExtendsUnresolved());
if (!(counter.isExtendsSystem() && counter.isExtendsUnresolved()))
return;
if (countersInChain.contains(&counter)) {
// Chain of references forms a circle. Treat all as extending decimal (https://www.w3.org/TR/css-counter-styles-3/#extends-system).
auto decimal = decimalCounter();
for (const RefPtr counterInChain : countersInChain) {
ASSERT(counterInChain);
if (!counterInChain)
continue;
counterInChain->extendAndResolve(decimal);
}
// Recursion return for circular chain.
return;
}
countersInChain.add(&counter);
auto extendedCounter = counterStyle(counter.extendsName(), map);
if (extendedCounter->isExtendsSystem() && extendedCounter->isExtendsUnresolved())
resolveExtendsReference(extendedCounter, countersInChain, map);
// Recursion return for non-circular chain. Calling resolveExtendsReference() for the extendedCounter might have already resolved this counter style if a circle was formed. If it is still unresolved, it should get resolved here.
if (counter.isExtendsUnresolved())
counter.extendAndResolve(extendedCounter);
}
void CSSCounterStyleRegistry::resolveFallbackReference(CSSCounterStyle& counter, CounterStyleMap* map)
{
counter.setFallbackReference(counterStyle(counter.fallbackName(), map));
}
void CSSCounterStyleRegistry::addCounterStyle(const CSSCounterStyleDescriptors& descriptors)
{
m_hasUnresolvedReferences = true;
m_authorCounterStyles.set(descriptors.m_name, CSSCounterStyle::create(descriptors, false));
}
void CSSCounterStyleRegistry::addUserAgentCounterStyle(const CSSCounterStyleDescriptors& descriptors)
{
userAgentCounterStyles().set(descriptors.m_name, CSSCounterStyle::create(descriptors, true));
}
Ref<CSSCounterStyle> CSSCounterStyleRegistry::decimalCounter()
{
auto& userAgentCounters = userAgentCounterStyles();
auto iterator = userAgentCounters.find("decimal"_s);
// user agent counter style should always be populated with a counter named decimal if counter-style-at-rule is enabled
ASSERT(iterator != userAgentCounters.end());
return iterator->value.get();
}
// A valid map means that the search begins at the author counter style map, otherwise we skip the search to the UA counter styles.
Ref<CSSCounterStyle> CSSCounterStyleRegistry::counterStyle(const AtomString& name, CounterStyleMap* map)
{
if (name.isEmpty())
return decimalCounter();
// If there is a map, the search starts from the given map.
if (map) {
if (RefPtr counter = map->get(name))
return counter.releaseNonNull();
}
// If there was no map (called for user-agent references resolution), or the counter was not found in the given map, we search at the user-agent map.
if (RefPtr userAgentCounter = userAgentCounterStyles().get(name))
return userAgentCounter.releaseNonNull();
return decimalCounter();
}
Ref<CSSCounterStyle> CSSCounterStyleRegistry::resolvedCounterStyle(const Style::CounterStyle& style)
{
resolveReferencesIfNeeded();
return counterStyle(style.identifier.value, &m_authorCounterStyles);
}
CounterStyleMap& CSSCounterStyleRegistry::userAgentCounterStyles()
{
static NeverDestroyed<CounterStyleMap> counters;
return counters;
}
bool CSSCounterStyleRegistry::operator==(const CSSCounterStyleRegistry& other) const
{
// Intentionally doesn't check m_hasUnresolvedReferences.
return m_authorCounterStyles == other.m_authorCounterStyles;
}
void CSSCounterStyleRegistry::clearAuthorCounterStyles()
{
if (m_authorCounterStyles.isEmpty())
return;
m_authorCounterStyles.clear();
invalidate();
}
void CSSCounterStyleRegistry::invalidate()
{
m_hasUnresolvedReferences = true;
}
} // namespace WebCore