ladybird/Libraries/LibWeb/CSS/CascadedProperties.cpp
Andreas Kling 47cb66ef27 LibWeb: Account for shadow contexts in the cascade
Apply author declarations in encapsulation-context order before layer,
specificity, scope proximity, and source-order tie breaking. Normal
author rules now cascade from inner shadow contexts outward. This lets
outer contexts override component defaults. Important author rules
cascade the other way so inner contexts can enforce requirements.

Keep the ordering context-bucketed. The common document-only path still
collects and sorts one author context. Custom properties follow the same
context order. var() substitution now observes the same winners as
longhand properties.

Make revert-layer remove declarations only from the matching cascade
origin, context, and layer. This keeps presentational hints and values
from other shadow contexts available when one context rolls back its own
layer.

Add coverage for document and shadow-host competition, slotted rules,
nested slot contexts, inline styles, important declarations, custom
properties, and revert-layer across shadow contexts.
2026-05-31 12:51:58 +02:00

155 lines
5.5 KiB
C++

/*
* Copyright (c) 2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/CascadedProperties.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/ShadowRoot.h>
namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CascadedProperties);
CascadedProperties::CascadedProperties() = default;
CascadedProperties::~CascadedProperties() = default;
void CascadedProperties::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto const& [property_id, entries] : m_properties) {
for (auto const& entry : entries) {
visitor.visit(entry.source);
visitor.visit(entry.source_shadow_root);
}
}
}
void CascadedProperties::revert_property(PropertyID property_id, Important important, CascadeOrigin cascade_origin)
{
auto it = m_properties.find(property_id);
if (it == m_properties.end())
return;
auto& entries = it->value;
entries.remove_all_matching([&](auto& entry) {
// https://drafts.csswg.org/css-cascade-5/#author-presentational-hint-origin
// For the purpose of cascading this author presentational hint origin is treated as an independent origin, but
// for the purpose of the revert keyword it is considered part of the author origin.
auto origin_matches = entry.origin == cascade_origin
|| (cascade_origin == CascadeOrigin::Author && entry.origin == CascadeOrigin::AuthorPresentationalHint);
return entry.property.property_id == property_id
&& entry.property.important == important
&& origin_matches;
});
if (entries.is_empty()) {
m_contained_properties_cache.set(to_underlying(property_id), false);
m_properties.remove(it);
}
}
void CascadedProperties::revert_layer_property(PropertyID property_id, Important important, CascadeOrigin cascade_origin, Optional<FlyString> layer_name, GC::Ptr<DOM::ShadowRoot const> source_shadow_root)
{
auto it = m_properties.find(property_id);
if (it == m_properties.end())
return;
auto& entries = it->value;
entries.remove_all_matching([&](auto& entry) {
return entry.property.property_id == property_id
&& entry.property.important == important
&& entry.origin == cascade_origin
&& entry.source_shadow_root == source_shadow_root
&& layer_name == entry.layer_name;
});
if (entries.is_empty()) {
m_contained_properties_cache.set(to_underlying(property_id), false);
m_properties.remove(it);
}
}
void CascadedProperties::set_property(PropertyID property_id, NonnullRefPtr<StyleValue const> value, Important important, CascadeOrigin origin, Optional<FlyString> layer_name, GC::Ptr<CSS::CSSStyleDeclaration const> source, GC::Ptr<DOM::ShadowRoot const> source_shadow_root)
{
m_contained_properties_cache.set(to_underlying(property_id), true);
auto& entries = m_properties.ensure(property_id);
for (auto& entry : entries.in_reverse()) {
if (entry.origin == origin && entry.layer_name == layer_name && entry.source_shadow_root == source_shadow_root) {
if (entry.property.important == Important::Yes && important == Important::No)
return;
entry.property = StyleProperty {
.important = important,
.property_id = property_id,
.value = value,
};
entry.cascade_index = m_next_cascade_index++;
entry.source = source;
entry.source_shadow_root = source_shadow_root;
return;
}
}
entries.append(Entry {
.property = StyleProperty {
.important = important,
.property_id = property_id,
.value = value,
},
.cascade_index = m_next_cascade_index++,
.origin = origin,
.layer_name = move(layer_name),
.source = source,
.source_shadow_root = source_shadow_root,
});
}
RefPtr<StyleValue const> CascadedProperties::property(PropertyID property_id) const
{
if (!m_contained_properties_cache.get(to_underlying(property_id)))
return nullptr;
return m_properties.get(property_id)->last().property.value;
}
PropertyID CascadedProperties::property_with_higher_priority(PropertyID first_property_id, PropertyID second_property_id) const
{
if (!m_contained_properties_cache.get(to_underlying(first_property_id)))
return second_property_id;
if (!m_contained_properties_cache.get(to_underlying(second_property_id)))
return first_property_id;
if (m_properties.get(first_property_id)->last().cascade_index >= m_properties.get(second_property_id)->last().cascade_index)
return first_property_id;
return second_property_id;
}
GC::Ptr<CSSStyleDeclaration const> CascadedProperties::property_source(PropertyID property_id) const
{
if (!m_contained_properties_cache.get(to_underlying(property_id)))
return nullptr;
return m_properties.get(property_id)->last().source;
}
GC::Ptr<DOM::ShadowRoot const> CascadedProperties::property_source_shadow_root(PropertyID property_id) const
{
if (!m_contained_properties_cache.get(to_underlying(property_id)))
return nullptr;
return m_properties.get(property_id)->last().source_shadow_root;
}
Optional<StyleProperty> CascadedProperties::style_property(PropertyID property_id) const
{
if (!m_contained_properties_cache.get(to_underlying(property_id)))
return {};
return m_properties.get(property_id)->last().property;
}
}