/* * Copyright (c) 2018-2025, Andreas Kling * Copyright (c) 2021, the SerenityOS developers. * Copyright (c) 2021-2025, Sam Atkins * Copyright (c) 2024, Matthew Olsson * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Web::CSS { GC_DEFINE_ALLOCATOR(StyleComputer); static bool property_affects_font_metrics(PropertyID property_id) { return property_id == PropertyID::FontSize || property_id == PropertyID::LineHeight; } CSSStyleProperties const& MatchingRule::declaration() const { if (rule->type() == CSSRule::Type::Style) return static_cast(*rule).declaration(); if (rule->type() == CSSRule::Type::NestedDeclarations) return static_cast(*rule).declaration(); VERIFY_NOT_REACHED(); } SelectorList const& MatchingRule::absolutized_selectors() const { if (rule->type() == CSSRule::Type::Style) return static_cast(*rule).absolutized_selectors(); if (rule->type() == CSSRule::Type::NestedDeclarations) return static_cast(*rule).absolutized_selectors(); VERIFY_NOT_REACHED(); } FlyString const& MatchingRule::qualified_layer_name() const { if (rule->type() == CSSRule::Type::Style) return static_cast(*rule).qualified_layer_name(); if (rule->type() == CSSRule::Type::NestedDeclarations) return static_cast(*rule).qualified_layer_name(); VERIFY_NOT_REACHED(); } StyleComputer::StyleComputer(DOM::Document& document) : m_document(document) , m_default_font_metrics(16, Platform::FontPlugin::the().default_font(16)->pixel_metrics(), InitialValues::line_height()) , m_root_element_font_metrics(m_default_font_metrics) { m_ancestor_filter = make>(); } StyleComputer::~StyleComputer() = default; void StyleComputer::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_document); if (m_has_result_cache) visitor.visit(*m_has_result_cache); if (m_cached_font_computation_context.has_value()) m_cached_font_computation_context->visit_edges(visitor); if (m_cached_line_height_computation_context.has_value()) m_cached_line_height_computation_context->visit_edges(visitor); if (m_cached_generic_computation_context.has_value()) m_cached_generic_computation_context->visit_edges(visitor); } Optional StyleComputer::user_agent_style_sheet_source(StringView name) { extern String default_stylesheet_source; extern String quirks_mode_stylesheet_source; extern String mathml_stylesheet_source; extern String svg_stylesheet_source; if (name == "CSS/Default.css"sv) return default_stylesheet_source; if (name == "CSS/QuirksMode.css"sv) return quirks_mode_stylesheet_source; if (name == "MathML/Default.css"sv) return mathml_stylesheet_source; if (name == "SVG/Default.css"sv) return svg_stylesheet_source; return {}; } RuleCache const* StyleComputer::rule_cache_for_cascade_origin(CascadeOrigin cascade_origin, Optional qualified_layer_name, GC::Ptr shadow_root) const { auto& style_scope = shadow_root ? shadow_root->style_scope() : document().style_scope(); style_scope.build_rule_cache_if_needed(); auto const* rule_caches_by_layer = [&]() -> RuleCaches const* { switch (cascade_origin) { case CascadeOrigin::Author: return &style_scope.m_rule_cache->author_rule_cache; case CascadeOrigin::User: return &style_scope.m_rule_cache->user_rule_cache; case CascadeOrigin::UserAgent: return &style_scope.m_rule_cache->user_agent_rule_cache; default: VERIFY_NOT_REACHED(); } }(); if (!rule_caches_by_layer) return nullptr; if (!qualified_layer_name.has_value()) return &rule_caches_by_layer->main; return rule_caches_by_layer->by_layer.get(*qualified_layer_name).value_or(nullptr); } [[nodiscard]] static bool filter_namespace_rule(Optional const& element_namespace_uri, MatchingRule const& rule) { // FIXME: Filter out non-default namespace using prefixes if (rule.default_namespace.has_value() && element_namespace_uri != rule.default_namespace) return false; return true; } NonnullRefPtr StyleComputer::invalidation_plan_for_properties(Vector const& properties, StyleScope const& style_scope) const { auto result = InvalidationPlan::create(); if (!style_scope.m_rule_cache) return result; auto const& invalidation_plans = style_scope.m_rule_cache->style_invalidation_data.invalidation_plans; for (auto const& property : properties) { if (auto it = invalidation_plans.find(property); it != invalidation_plans.end()) { result->include_all_from(*it->value); if (result->invalidate_whole_subtree) break; } } return result; } Vector const* StyleComputer::has_invalidation_metadata_for_property(InvalidationSet::Property const& property, StyleScope const& style_scope) const { if (!style_scope.m_rule_cache) return nullptr; auto return_bucket_if_present = [](auto const& map, auto const& key) -> Vector const* { auto bucket = map.get(key); if (!bucket.has_value()) return nullptr; return &bucket.value(); }; switch (property.type) { case InvalidationSet::Property::Type::Id: return return_bucket_if_present(style_scope.m_rule_cache->style_invalidation_data.ids_used_in_has_selectors, property.name()); case InvalidationSet::Property::Type::Class: return return_bucket_if_present(style_scope.m_rule_cache->style_invalidation_data.class_names_used_in_has_selectors, property.name()); case InvalidationSet::Property::Type::Attribute: return return_bucket_if_present(style_scope.m_rule_cache->style_invalidation_data.attribute_names_used_in_has_selectors, property.name()); case InvalidationSet::Property::Type::TagName: return return_bucket_if_present(style_scope.m_rule_cache->style_invalidation_data.tag_names_used_in_has_selectors, property.name()); case InvalidationSet::Property::Type::PseudoClass: return return_bucket_if_present(style_scope.m_rule_cache->style_invalidation_data.pseudo_classes_used_in_has_selectors, property.value.get()); default: break; } return nullptr; } static bool scope_selector_matches(Selector const& selector, DOM::Element const& element, MatchingRule const& rule, GC::Ptr shadow_host, GC::Ptr rule_root, GC::Ptr scope) { SelectorEngine::MatchContext context { .style_sheet_for_rule = *rule.sheet, .subject = element, .rule_shadow_root = rule_root, }; return SelectorEngine::matches(selector, DOM::AbstractElement(element), shadow_host, context, scope); } struct ResolvedScope { GC::Ptr root; size_t proximity { NumericLimits::max() }; }; static Optional resolve_single_scope(DOM::AbstractElement abstract_element, MatchingRule const& rule, GC::Ptr shadow_host, GC::Ptr rule_root, CSSScopeRule const& scope_rule, GC::Ptr outer_root) { GC::Ptr root; size_t proximity = 0; // https://drafts.csswg.org/css-cascade-6/#scope-limits // Finding the scoping root(s) // For each element matched by , create a scope using that element as the scoping root. if (scope_rule.start_selectors_for_matching().has_value()) { for (auto const* candidate = &abstract_element.element(); candidate; candidate = candidate->parent_element().ptr(), ++proximity) { if (outer_root && !outer_root->is_inclusive_ancestor_of(*candidate)) break; for (auto const& selector : *scope_rule.start_selectors_for_matching()) { if (scope_selector_matches(selector, *candidate, rule, shadow_host, rule_root, outer_root)) { root = candidate; break; } } if (root) break; } } else { // If no is specified, the scoping root is the parent element of the owner node of the // stylesheet where the @scope rule is defined. // FIXME: This means the scoping root is the `