mirror of
https://github.com/RGBCube/serenity
synced 2025-05-30 14:18:11 +00:00
LibWeb: Cache CSS rules in buckets to reduce number of rules checked
This patch introduces the StyleComputer::RuleCache, which divides all of our (author) CSS rules into buckets. Currently, there are two buckets: - Rules where a specific class must be present. - All other rules. This allows us to check a significantly smaller set of rules for each element, since we can skip over any rule that requires a class attribute not present on the element. This takes the typical numer of rules tested per element on Discord from ~16000 to ~550. :^) We can definitely improve the cache invalidation. It currently happens too often due to media queries. And we also need to make sure we invalidate when mutating style through CSSOM APIs.
This commit is contained in:
parent
8d104b7de2
commit
646b37d1a9
5 changed files with 108 additions and 2 deletions
|
@ -1,11 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
|
||||||
* Copyright (c) 2021, the SerenityOS developers.
|
* Copyright (c) 2021, the SerenityOS developers.
|
||||||
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
|
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/Debug.h>
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
#include <AK/TemporaryChange.h>
|
#include <AK/TemporaryChange.h>
|
||||||
#include <LibGfx/Font.h>
|
#include <LibGfx/Font.h>
|
||||||
|
@ -70,8 +71,24 @@ void StyleComputer::for_each_stylesheet(CascadeOrigin cascade_origin, Callback c
|
||||||
|
|
||||||
Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin) const
|
Vector<MatchingRule> StyleComputer::collect_matching_rules(DOM::Element const& element, CascadeOrigin cascade_origin) const
|
||||||
{
|
{
|
||||||
Vector<MatchingRule> matching_rules;
|
if (cascade_origin == CascadeOrigin::Author) {
|
||||||
|
Vector<MatchingRule> 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<MatchingRule> 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<MatchingRule> matching_rules;
|
||||||
size_t style_sheet_index = 0;
|
size_t style_sheet_index = 0;
|
||||||
for_each_stylesheet(cascade_origin, [&](auto& sheet) {
|
for_each_stylesheet(cascade_origin, [&](auto& sheet) {
|
||||||
size_t rule_index = 0;
|
size_t rule_index = 0;
|
||||||
|
@ -902,6 +919,8 @@ NonnullRefPtr<StyleProperties> StyleComputer::create_document_style() const
|
||||||
|
|
||||||
NonnullRefPtr<StyleProperties> StyleComputer::compute_style(DOM::Element& element) const
|
NonnullRefPtr<StyleProperties> StyleComputer::compute_style(DOM::Element& element) const
|
||||||
{
|
{
|
||||||
|
build_rule_cache_if_needed();
|
||||||
|
|
||||||
auto style = StyleProperties::create();
|
auto style = StyleProperties::create();
|
||||||
// 1. Perform the cascade. This produces the "specified style"
|
// 1. Perform the cascade. This produces the "specified style"
|
||||||
compute_cascaded_values(style, element);
|
compute_cascaded_values(style, element);
|
||||||
|
@ -950,4 +969,63 @@ bool PropertyDependencyNode::has_cycles()
|
||||||
}
|
}
|
||||||
return false;
|
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<StyleComputer&>(*this).build_rule_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StyleComputer::build_rule_cache()
|
||||||
|
{
|
||||||
|
// FIXME: Make a rule cache for UA style as well.
|
||||||
|
|
||||||
|
m_rule_cache = make<RuleCache>();
|
||||||
|
|
||||||
|
size_t num_class_rules = 0;
|
||||||
|
|
||||||
|
Vector<MatchingRule> matching_rules;
|
||||||
|
size_t style_sheet_index = 0;
|
||||||
|
for_each_stylesheet(CascadeOrigin::Author, [&](auto& sheet) {
|
||||||
|
size_t rule_index = 0;
|
||||||
|
static_cast<CSSStyleSheet const&>(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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ public:
|
||||||
|
|
||||||
Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin = CascadeOrigin::Any) const;
|
Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin = CascadeOrigin::Any) const;
|
||||||
|
|
||||||
|
void invalidate_rule_cache();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void compute_cascaded_values(StyleProperties&, DOM::Element&) const;
|
void compute_cascaded_values(StyleProperties&, DOM::Element&) const;
|
||||||
void compute_font(StyleProperties&, DOM::Element const*) const;
|
void compute_font(StyleProperties&, DOM::Element const*) const;
|
||||||
|
@ -88,7 +90,17 @@ private:
|
||||||
|
|
||||||
void cascade_declarations(StyleProperties&, DOM::Element&, Vector<MatchingRule> const&, CascadeOrigin, bool important, HashMap<String, StyleProperty const*> const&) const;
|
void cascade_declarations(StyleProperties&, DOM::Element&, Vector<MatchingRule> const&, CascadeOrigin, bool important, HashMap<String, StyleProperty const*> const&) const;
|
||||||
|
|
||||||
|
void build_rule_cache();
|
||||||
|
void build_rule_cache_if_needed() const;
|
||||||
|
|
||||||
DOM::Document& m_document;
|
DOM::Document& m_document;
|
||||||
|
|
||||||
|
struct RuleCache {
|
||||||
|
HashMap<FlyString, Vector<MatchingRule>> rules_by_class;
|
||||||
|
Vector<MatchingRule> other_rules;
|
||||||
|
int generation { 0 };
|
||||||
|
};
|
||||||
|
OwnPtr<RuleCache> m_rule_cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibWeb/CSS/StyleSheetList.h>
|
#include <LibWeb/CSS/StyleSheetList.h>
|
||||||
|
#include <LibWeb/DOM/Document.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
@ -12,11 +13,17 @@ void StyleSheetList::add_sheet(NonnullRefPtr<CSSStyleSheet> sheet)
|
||||||
{
|
{
|
||||||
VERIFY(!m_sheets.contains_slow(sheet));
|
VERIFY(!m_sheets.contains_slow(sheet));
|
||||||
m_sheets.append(move(sheet));
|
m_sheets.append(move(sheet));
|
||||||
|
|
||||||
|
++m_generation;
|
||||||
|
m_document.invalidate_style();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StyleSheetList::remove_sheet(CSSStyleSheet& sheet)
|
void StyleSheetList::remove_sheet(CSSStyleSheet& sheet)
|
||||||
{
|
{
|
||||||
m_sheets.remove_first_matching([&](auto& entry) { return &*entry == &sheet; });
|
m_sheets.remove_first_matching([&](auto& entry) { return &*entry == &sheet; });
|
||||||
|
|
||||||
|
++m_generation;
|
||||||
|
m_document.invalidate_style();
|
||||||
}
|
}
|
||||||
|
|
||||||
StyleSheetList::StyleSheetList(DOM::Document& document)
|
StyleSheetList::StyleSheetList(DOM::Document& document)
|
||||||
|
|
|
@ -42,11 +42,16 @@ public:
|
||||||
|
|
||||||
bool is_supported_property_index(u32) const;
|
bool is_supported_property_index(u32) const;
|
||||||
|
|
||||||
|
int generation() const { return m_generation; }
|
||||||
|
void bump_generation() { ++m_generation; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit StyleSheetList(DOM::Document&);
|
explicit StyleSheetList(DOM::Document&);
|
||||||
|
|
||||||
DOM::Document& m_document;
|
DOM::Document& m_document;
|
||||||
NonnullRefPtrVector<CSSStyleSheet> m_sheets;
|
NonnullRefPtrVector<CSSStyleSheet> m_sheets;
|
||||||
|
|
||||||
|
int m_generation { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1147,6 +1147,10 @@ void Document::evaluate_media_queries_and_report_changes()
|
||||||
for (auto& style_sheet : style_sheets().sheets()) {
|
for (auto& style_sheet : style_sheets().sheets()) {
|
||||||
style_sheet.evaluate_media_queries(window());
|
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<DOMImplementation> Document::implementation() const
|
NonnullRefPtr<DOMImplementation> Document::implementation() const
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue