| /* |
| * 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 |