diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index d7477637b3..af6440153f 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -1,11 +1,12 @@ /* - * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2018-2022, Andreas Kling * Copyright (c) 2021, the SerenityOS developers. * Copyright (c) 2021, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -70,8 +71,24 @@ void StyleComputer::for_each_stylesheet(CascadeOrigin cascade_origin, Callback c Vector StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin) const { - Vector matching_rules; + if (cascade_origin == CascadeOrigin::Author) { + Vector rules_to_run; + for (auto const& class_name : element.class_names()) { + if (auto it = m_rule_cache->rules_by_class.find(class_name); it != m_rule_cache->rules_by_class.end()) + rules_to_run.extend(it->value); + } + rules_to_run.extend(m_rule_cache->other_rules); + Vector matching_rules; + for (auto const& rule_to_run : rules_to_run) { + auto const& selector = rule_to_run.rule->selectors()[rule_to_run.selector_index]; + if (SelectorEngine::matches(selector, element)) + matching_rules.append(rule_to_run); + } + return matching_rules; + } + + Vector matching_rules; size_t style_sheet_index = 0; for_each_stylesheet(cascade_origin, [&](auto& sheet) { size_t rule_index = 0; @@ -902,6 +919,8 @@ NonnullRefPtr StyleComputer::create_document_style() const NonnullRefPtr StyleComputer::compute_style(DOM::Element& element) const { + build_rule_cache_if_needed(); + auto style = StyleProperties::create(); // 1. Perform the cascade. This produces the "specified style" compute_cascaded_values(style, element); @@ -950,4 +969,63 @@ bool PropertyDependencyNode::has_cycles() } return false; } + +void StyleComputer::build_rule_cache_if_needed() const +{ + if (m_rule_cache && m_rule_cache->generation == m_document.style_sheets().generation()) + return; + const_cast(*this).build_rule_cache(); +} + +void StyleComputer::build_rule_cache() +{ + // FIXME: Make a rule cache for UA style as well. + + m_rule_cache = make(); + + size_t num_class_rules = 0; + + Vector matching_rules; + size_t style_sheet_index = 0; + for_each_stylesheet(CascadeOrigin::Author, [&](auto& sheet) { + size_t rule_index = 0; + static_cast(sheet).for_each_effective_style_rule([&](auto const& rule) { + size_t selector_index = 0; + for (CSS::Selector const& selector : rule.selectors()) { + MatchingRule matching_rule { rule, style_sheet_index, rule_index, selector_index, selector.specificity() }; + + bool added_to_bucket = false; + for (auto const& simple_selector : selector.compound_selectors().last().simple_selectors) { + if (simple_selector.type == CSS::Selector::SimpleSelector::Type::Class) { + m_rule_cache->rules_by_class.ensure(simple_selector.value).append(move(matching_rule)); + ++num_class_rules; + added_to_bucket = true; + break; + } + } + if (!added_to_bucket) + m_rule_cache->other_rules.append(move(matching_rule)); + + ++selector_index; + } + ++rule_index; + }); + ++style_sheet_index; + }); + + if constexpr (LIBWEB_CSS_DEBUG) { + dbgln("Built rule cache!"); + dbgln(" Class: {}", num_class_rules); + dbgln(" Other: {}", m_rule_cache->other_rules.size()); + dbgln(" Total: {}", num_class_rules + m_rule_cache->other_rules.size()); + } + + m_rule_cache->generation = m_document.style_sheets().generation(); +} + +void StyleComputer::invalidate_rule_cache() +{ + m_rule_cache = nullptr; +} + } diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h index 1eed4f51f7..b366b17cf4 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h @@ -66,6 +66,8 @@ public: Vector collect_matching_rules(DOM::Element const&, CascadeOrigin = CascadeOrigin::Any) const; + void invalidate_rule_cache(); + private: void compute_cascaded_values(StyleProperties&, DOM::Element&) const; void compute_font(StyleProperties&, DOM::Element const*) const; @@ -88,7 +90,17 @@ private: void cascade_declarations(StyleProperties&, DOM::Element&, Vector const&, CascadeOrigin, bool important, HashMap const&) const; + void build_rule_cache(); + void build_rule_cache_if_needed() const; + DOM::Document& m_document; + + struct RuleCache { + HashMap> rules_by_class; + Vector other_rules; + int generation { 0 }; + }; + OwnPtr m_rule_cache; }; } diff --git a/Userland/Libraries/LibWeb/CSS/StyleSheetList.cpp b/Userland/Libraries/LibWeb/CSS/StyleSheetList.cpp index 3b309bc97f..f4945c1e8a 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleSheetList.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleSheetList.cpp @@ -5,6 +5,7 @@ */ #include +#include namespace Web::CSS { @@ -12,11 +13,17 @@ void StyleSheetList::add_sheet(NonnullRefPtr sheet) { VERIFY(!m_sheets.contains_slow(sheet)); m_sheets.append(move(sheet)); + + ++m_generation; + m_document.invalidate_style(); } void StyleSheetList::remove_sheet(CSSStyleSheet& sheet) { m_sheets.remove_first_matching([&](auto& entry) { return &*entry == &sheet; }); + + ++m_generation; + m_document.invalidate_style(); } StyleSheetList::StyleSheetList(DOM::Document& document) diff --git a/Userland/Libraries/LibWeb/CSS/StyleSheetList.h b/Userland/Libraries/LibWeb/CSS/StyleSheetList.h index 3aa0b63874..83622557be 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleSheetList.h +++ b/Userland/Libraries/LibWeb/CSS/StyleSheetList.h @@ -42,11 +42,16 @@ public: bool is_supported_property_index(u32) const; + int generation() const { return m_generation; } + void bump_generation() { ++m_generation; } + private: explicit StyleSheetList(DOM::Document&); DOM::Document& m_document; NonnullRefPtrVector m_sheets; + + int m_generation { 0 }; }; } diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index e9423c0bac..a0ef87151f 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -1147,6 +1147,10 @@ void Document::evaluate_media_queries_and_report_changes() for (auto& style_sheet : style_sheets().sheets()) { style_sheet.evaluate_media_queries(window()); } + + // FIXME: This invalidates too often! + // We should only invalidate when one or more @media rules changes evaluation status. + style_computer().invalidate_rule_cache(); } NonnullRefPtr Document::implementation() const