ladybird/Libraries/LibWeb/CSS/CSSRule.cpp
Sam Atkins 214d2b5e1f LibWeb/CSS: Implement CSSContainerRule
No parsing yet, just CSSContainerRule and the supporting ContainerQuery
class.

CSSContainerRule is unusual in how it matches, because instead of it
either matching or not matching globally, it instead is matched against
a specific element. But also, some at-rules inside it always apply, as
if they were written outside it. This doesn't fit well with how
CSSConditionRule is implemented, and will likely require some rework
later. For now, `condition_matches()` always returns false, and
`for_each_effective_rule()` is overridden to always process those
global at-rules and nothing else.
2026-03-30 14:49:24 +01:00

143 lines
4.4 KiB
C++

/*
* Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/Bindings/CSSRulePrototype.h>
#include <LibWeb/CSS/CSSImportRule.h>
#include <LibWeb/CSS/CSSLayerBlockRule.h>
#include <LibWeb/CSS/CSSRule.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/Dump.h>
namespace Web::CSS {
CSSRule::CSSRule(JS::Realm& realm, Type type)
: PlatformObject(realm)
, m_type(type)
{
}
void CSSRule::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_parent_style_sheet);
visitor.visit(m_parent_rule);
}
// https://www.w3.org/TR/cssom/#dom-cssrule-type
WebIDL::UnsignedShort CSSRule::type_for_bindings() const
{
// NOTE: Types that aren't defined in the spec must return 0.
// To do this, we arbitrarily make non-spec ones start at 100.
auto type = to_underlying(m_type);
if (type >= 100)
return 0;
return type;
}
// https://www.w3.org/TR/cssom/#dom-cssrule-csstext
String CSSRule::css_text() const
{
// The cssText attribute must return a serialization of the CSS rule.
return serialized();
}
// https://www.w3.org/TR/cssom/#dom-cssrule-csstext
void CSSRule::set_css_text(StringView)
{
// On setting the cssText attribute must do nothing.
}
void CSSRule::set_parent_rule(CSSRule* parent_rule)
{
clear_caches();
m_parent_rule = parent_rule;
if (parent_rule == nullptr)
set_parent_style_sheet(nullptr);
else
set_parent_style_sheet(parent_rule->parent_style_sheet());
clear_caches();
}
void CSSRule::set_parent_style_sheet(CSSStyleSheet* parent_style_sheet)
{
clear_caches();
m_parent_style_sheet = parent_style_sheet;
clear_caches();
}
void CSSRule::dump(StringBuilder& builder, int indent_levels) const
{
dump_indent(builder, indent_levels);
builder.appendff("{}:\n", class_name());
}
void CSSRule::clear_caches()
{
m_cached_layer_name.clear();
}
FlyString CSSRule::parent_layer_internal_qualified_name_slow_case() const
{
Vector<FlyString> layer_names;
for (auto* rule = parent_rule(); rule; rule = rule->parent_rule()) {
switch (rule->type()) {
case Type::Import:
// @import is only a parent to style sheets, not to rules directly. It's handled below this loop.
VERIFY_NOT_REACHED();
break;
case Type::LayerBlock: {
auto& layer_block = as<CSSLayerBlockRule>(*rule);
layer_names.append(layer_block.internal_name());
break;
}
// Ignore everything else
// Note that LayerStatement cannot have child rules so we still ignore it here.
case Type::Container:
case Type::CounterStyle:
case Type::LayerStatement:
case Type::Style:
case Type::Media:
case Type::FontFace:
case Type::FontFeatureValues:
case Type::Function:
case Type::FunctionDeclarations:
case Type::Keyframes:
case Type::Keyframe:
case Type::Namespace:
case Type::Supports:
case Type::NestedDeclarations:
case Type::Property:
case Type::Page:
case Type::Margin:
break;
}
}
// If this style sheet is owned by a rule, include its qualified layer name.
if (m_parent_style_sheet && m_parent_style_sheet->owner_rule()) {
if (auto* import = as_if<CSSImportRule>(*m_parent_style_sheet->owner_rule())) {
// https://drafts.csswg.org/css-cascade-5/#at-import
// The layer is added to the layer order even if the import fails to load the stylesheet, but is subject to
// any import conditions (just as if declared by an @layer rule wrapped in the appropriate conditional
// group rules).
if (auto layer_name = import->internal_layer_name(); layer_name.has_value() && import->matches()) {
layer_names.append(layer_name.release_value());
auto parent_qualified_layer_name = m_parent_style_sheet->owner_rule()->parent_layer_internal_qualified_name();
if (!parent_qualified_layer_name.is_empty())
layer_names.append(move(parent_qualified_layer_name));
}
}
}
return MUST(String::join('.', layer_names.in_reverse()));
}
}