ladybird/Libraries/LibWeb/CSS/CSSScopeRule.cpp

205 lines
6.9 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2026, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "CSSScopeRule.h"
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/CSSScopeRule.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/CSS/CSSRuleList.h>
#include <LibWeb/Dump.h>
namespace Web::CSS {
GC_DEFINE_ALLOCATOR(CSSScopeRule);
GC::Ref<CSSScopeRule> CSSScopeRule::create(JS::Realm& realm, Optional<SelectorList>&& start_selectors, Optional<SelectorList>&& end_selectors, CSSRuleList& rules)
{
return realm.create<CSSScopeRule>(realm, move(start_selectors), move(end_selectors), rules);
}
CSSScopeRule::CSSScopeRule(JS::Realm& realm, Optional<SelectorList>&& start_selectors, Optional<SelectorList>&& end_selectors, CSSRuleList& rules)
: CSSGroupingRule(realm, rules, Type::Scope)
, m_start_selectors(move(start_selectors))
, m_end_selectors(move(end_selectors))
{
}
CSSScopeRule::~CSSScopeRule() = default;
void CSSScopeRule::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(CSSScopeRule);
Base::initialize(realm);
}
void CSSScopeRule::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_cached_nearest_ancestor_scope_rule);
}
Optional<String> CSSScopeRule::start() const
{
if (m_start_selectors.has_value())
return serialize_a_group_of_selectors(*m_start_selectors);
return {};
}
Optional<String> CSSScopeRule::end() const
{
if (m_end_selectors.has_value())
return serialize_a_group_of_selectors(*m_end_selectors);
return {};
}
Optional<SelectorList> const& CSSScopeRule::start_selectors_for_matching() const
{
if (!m_start_selectors.has_value())
return m_start_selectors;
if (!m_cached_start_selectors_for_matching.has_value())
m_cached_start_selectors_for_matching = absolutize_selectors_relative_to(*m_start_selectors, m_parent_rule);
return m_cached_start_selectors_for_matching;
}
static SelectorList adapt_end_selectors(SelectorList const& selectors)
{
// NB: This is a modified version of adapt_nested_relative_selector_list().
// End selectors are implicitly prepended with `:scope` if that doesn't already appear.
// We also prepend it if the selector is relative.
SelectorList new_list;
new_list.ensure_capacity(selectors.size());
for (auto const& selector : selectors) {
auto first_combinator = selector->compound_selectors().first().combinator;
if (!first_is_one_of(first_combinator, Selector::Combinator::None, Selector::Combinator::Descendant)
|| (!selector->contains_the_nesting_selector() && !selector->contains_pseudo_class(PseudoClass::Scope))) {
new_list.append(selector->relative_to(Selector::SimpleSelector {
.type = Selector::SimpleSelector::Type::PseudoClass,
.value = Selector::SimpleSelector::PseudoClassSelector {
.type = PseudoClass::Scope,
} }));
} else if (first_combinator == Selector::Combinator::Descendant) {
// Replace leading descendant combinator (whitespace) with none, because we're not actually relative.
auto copied_compound_selectors = selector->compound_selectors();
copied_compound_selectors.first().combinator = Selector::Combinator::None;
new_list.append(Selector::create(move(copied_compound_selectors)));
} else {
new_list.append(selector);
}
}
return new_list;
}
Optional<SelectorList> const& CSSScopeRule::end_selectors_for_matching() const
{
if (!m_end_selectors.has_value())
return m_end_selectors;
if (!m_cached_end_selectors_for_matching.has_value())
m_cached_end_selectors_for_matching = adapt_end_selectors(*m_end_selectors);
return m_cached_end_selectors_for_matching;
}
GC::Ptr<CSSScopeRule const> CSSScopeRule::nearest_ancestor_scope_rule() const
{
if (m_cached_nearest_ancestor_scope_rule.has_value())
return m_cached_nearest_ancestor_scope_rule.value();
for (auto const* parent = parent_rule(); parent; parent = parent->parent_rule()) {
if (auto const* scope_rule = as_if<CSSScopeRule const>(parent)) {
m_cached_nearest_ancestor_scope_rule = scope_rule;
return m_cached_nearest_ancestor_scope_rule.value();
}
}
m_cached_nearest_ancestor_scope_rule = nullptr;
return m_cached_nearest_ancestor_scope_rule.value();
}
void CSSScopeRule::clear_caches()
{
Base::clear_caches();
m_cached_start_selectors_for_matching.clear();
m_cached_end_selectors_for_matching.clear();
m_cached_nearest_ancestor_scope_rule.clear();
}
// https://drafts.csswg.org/cssom-1/#serialize-a-css-rule
String CSSScopeRule::serialized() const
{
// AD-HOC: There is no spec for this yet.
StringBuilder builder;
builder.append("@scope"sv);
if (auto start = this->start(); start.has_value())
builder.appendff(" ({})", *start);
if (auto end = this->end(); end.has_value())
builder.appendff(" to ({})", *end);
builder.append(" {\n"sv);
for (size_t i = 0; i < css_rules().length(); i++) {
auto rule = css_rules().item(i);
auto result = rule->css_text();
if (result.is_empty())
continue;
builder.append(" "sv);
builder.append(result);
builder.append('\n');
}
builder.append('}');
return builder.to_string_without_validation();
}
void CSSScopeRule::dump(StringBuilder& builder, int indent_levels) const
{
Base::dump(builder, indent_levels);
dump_indent(builder, indent_levels + 1);
if (m_start_selectors.has_value()) {
builder.appendff("Start selectors ({}):\n", m_start_selectors->size());
for (auto& selector : *m_start_selectors)
dump_selector(builder, selector, indent_levels + 2);
dump_indent(builder, indent_levels + 1);
builder.appendff("Absolutized start selectors:\n");
for (auto& selector : start_selectors_for_matching().value()) {
dump_selector(builder, selector, indent_levels + 2);
}
} else {
builder.append("Start selectors: <none>\n"sv);
}
dump_indent(builder, indent_levels + 1);
if (m_end_selectors.has_value()) {
builder.appendff("End selectors ({}):\n", m_end_selectors->size());
for (auto& selector : *m_end_selectors)
dump_selector(builder, selector, indent_levels + 2);
dump_indent(builder, indent_levels + 1);
builder.appendff("Absolutized end selectors:\n");
for (auto& selector : end_selectors_for_matching().value()) {
dump_selector(builder, selector, indent_levels + 2);
}
} else {
builder.append("End selectors: <none>\n"sv);
}
dump_indent(builder, indent_levels + 1);
builder.appendff("Rules ({}):\n", css_rules().length());
for (auto& rule : css_rules())
dump_rule(builder, rule, indent_levels + 2);
}
}