mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-06-28 12:10:28 +00:00
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.
155 lines
5.5 KiB
C++
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;
|
|
}
|
|
|
|
}
|