mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-06 08:10:02 +00:00
LibWeb: Avoid duplicate work when computing style
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Some checks are pending
CI / macOS, arm64, Sanitizer, Clang (push) Waiting to run
CI / Linux, x86_64, Fuzzers, Clang (push) Waiting to run
CI / Linux, x86_64, Sanitizer, GNU (push) Waiting to run
CI / Linux, x86_64, Sanitizer, Clang (push) Waiting to run
Package the js repl as a binary artifact / Linux, arm64 (push) Waiting to run
Package the js repl as a binary artifact / macOS, arm64 (push) Waiting to run
Package the js repl as a binary artifact / Linux, x86_64 (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run
Before this change we were running the CSS cascade machinery twice per element: - First, to compute the "logical alias mapping context" based on writing-mode and pals. - Then, to compute all properties. This patch factors out the heaviest work from the cascade machinery to a separate step that can be run only once. This step will: - Collect all the matching rules for the element - Resolve custom properties for the element We still perform the per-element cascade twice, but now this is hogging less than 1% of CPU time when typing on Discord (compared to 9% before.)
This commit is contained in:
parent
98b45b4137
commit
128b66e30d
Notes:
github-actions[bot]
2025-07-20 21:23:25 +00:00
Author: https://github.com/awesomekling
Commit: 128b66e30d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5537
2 changed files with 47 additions and 40 deletions
|
@ -1537,18 +1537,15 @@ void StyleComputer::start_needed_transitions(ComputedProperties const& previous_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.w3.org/TR/css-cascade/#cascading
|
StyleComputer::MatchingRuleSet StyleComputer::build_matching_rule_set(DOM::Element const& element, Optional<PseudoElement> pseudo_element, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode mode) const
|
||||||
// https://drafts.csswg.org/css-cascade-5/#layering
|
|
||||||
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode mode, Optional<LogicalAliasMappingContext> logical_alias_mapping_context) const
|
|
||||||
{
|
{
|
||||||
auto cascaded_properties = m_document->heap().allocate<CascadedProperties>();
|
|
||||||
|
|
||||||
// First, we collect all the CSS rules whose selectors match `element`:
|
// First, we collect all the CSS rules whose selectors match `element`:
|
||||||
MatchingRuleSet matching_rule_set;
|
MatchingRuleSet matching_rule_set;
|
||||||
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, attempted_pseudo_class_matches);
|
matching_rule_set.user_agent_rules = collect_matching_rules(element, CascadeOrigin::UserAgent, pseudo_element, attempted_pseudo_class_matches);
|
||||||
sort_matching_rules(matching_rule_set.user_agent_rules);
|
sort_matching_rules(matching_rule_set.user_agent_rules);
|
||||||
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, attempted_pseudo_class_matches);
|
matching_rule_set.user_rules = collect_matching_rules(element, CascadeOrigin::User, pseudo_element, attempted_pseudo_class_matches);
|
||||||
sort_matching_rules(matching_rule_set.user_rules);
|
sort_matching_rules(matching_rule_set.user_rules);
|
||||||
|
|
||||||
// @layer-ed author rules
|
// @layer-ed author rules
|
||||||
for (auto const& layer_name : m_qualified_layer_names_in_order) {
|
for (auto const& layer_name : m_qualified_layer_names_in_order) {
|
||||||
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, attempted_pseudo_class_matches, layer_name);
|
auto layer_rules = collect_matching_rules(element, CascadeOrigin::Author, pseudo_element, attempted_pseudo_class_matches, layer_name);
|
||||||
|
@ -1562,26 +1559,23 @@ GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element&
|
||||||
|
|
||||||
if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
|
if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
|
||||||
VERIFY(pseudo_element.has_value());
|
VERIFY(pseudo_element.has_value());
|
||||||
if (matching_rule_set.author_rules.is_empty() && matching_rule_set.user_rules.is_empty() && matching_rule_set.user_agent_rules.is_empty()) {
|
did_match_any_pseudo_element_rules = !matching_rule_set.author_rules.is_empty()
|
||||||
did_match_any_pseudo_element_rules = false;
|
|| !matching_rule_set.user_rules.is_empty()
|
||||||
|
|| !matching_rule_set.user_agent_rules.is_empty();
|
||||||
|
}
|
||||||
|
return matching_rule_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/css-cascade/#cascading
|
||||||
|
// https://drafts.csswg.org/css-cascade-5/#layering
|
||||||
|
GC::Ref<CascadedProperties> StyleComputer::compute_cascaded_values(DOM::Element& element, Optional<CSS::PseudoElement> pseudo_element, bool did_match_any_pseudo_element_rules, ComputeStyleMode mode, MatchingRuleSet const& matching_rule_set, Optional<LogicalAliasMappingContext> logical_alias_mapping_context) const
|
||||||
|
{
|
||||||
|
auto cascaded_properties = m_document->heap().allocate<CascadedProperties>();
|
||||||
|
if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
|
||||||
|
if (!did_match_any_pseudo_element_rules)
|
||||||
return cascaded_properties;
|
return cascaded_properties;
|
||||||
}
|
|
||||||
did_match_any_pseudo_element_rules = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then we resolve all the CSS custom properties ("variables") for this element:
|
|
||||||
// FIXME: Also resolve !important custom properties, in a second cascade.
|
|
||||||
|
|
||||||
if (!pseudo_element.has_value() || pseudo_element_supports_property(*pseudo_element, PropertyID::Custom)) {
|
|
||||||
HashMap<FlyString, CSS::StyleProperty> custom_properties;
|
|
||||||
for (auto& layer : matching_rule_set.author_rules) {
|
|
||||||
cascade_custom_properties(element, pseudo_element, layer.rules, custom_properties);
|
|
||||||
}
|
|
||||||
element.set_custom_properties(pseudo_element, move(custom_properties));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then we apply the declarations from the matched rules in cascade order:
|
|
||||||
|
|
||||||
// Normal user agent declarations
|
// Normal user agent declarations
|
||||||
cascade_declarations(*cascaded_properties, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::No, {}, logical_alias_mapping_context);
|
cascade_declarations(*cascaded_properties, element, pseudo_element, matching_rule_set.user_agent_rules, CascadeOrigin::UserAgent, Important::No, {}, logical_alias_mapping_context);
|
||||||
|
|
||||||
|
@ -2141,7 +2135,7 @@ void StyleComputer::compute_font(ComputedProperties& style, DOM::Element const*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context(DOM::Element& element, Optional<PseudoElement> pseudo_element, ComputeStyleMode mode) const
|
LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context(DOM::Element& element, Optional<PseudoElement> pseudo_element, ComputeStyleMode mode, MatchingRuleSet const& matching_rule_set) const
|
||||||
{
|
{
|
||||||
auto normalize_value = [&](auto property_id, auto value) {
|
auto normalize_value = [&](auto property_id, auto value) {
|
||||||
if (!value || value->is_inherit() || value->is_unset()) {
|
if (!value || value->is_inherit() || value->is_unset()) {
|
||||||
|
@ -2159,10 +2153,9 @@ LogicalAliasMappingContext StyleComputer::compute_logical_alias_mapping_context(
|
||||||
};
|
};
|
||||||
|
|
||||||
bool did_match_any_pseudo_element_rules = false;
|
bool did_match_any_pseudo_element_rules = false;
|
||||||
PseudoClassBitmap attempted_pseudo_class_matches;
|
|
||||||
|
|
||||||
// FIXME: Ideally we wouldn't run the whole cascade just for these few properties.
|
// FIXME: Ideally we wouldn't run the whole cascade just for these few properties.
|
||||||
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, attempted_pseudo_class_matches, mode, {});
|
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, mode, matching_rule_set, {});
|
||||||
|
|
||||||
auto writing_mode = normalize_value(PropertyID::WritingMode, cascaded_properties->property(PropertyID::WritingMode));
|
auto writing_mode = normalize_value(PropertyID::WritingMode, cascaded_properties->property(PropertyID::WritingMode));
|
||||||
auto direction = normalize_value(PropertyID::Direction, cascaded_properties->property(PropertyID::Direction));
|
auto direction = normalize_value(PropertyID::Direction, cascaded_properties->property(PropertyID::Direction));
|
||||||
|
@ -2459,8 +2452,20 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::Element& elem
|
||||||
// 1. Perform the cascade. This produces the "specified style"
|
// 1. Perform the cascade. This produces the "specified style"
|
||||||
bool did_match_any_pseudo_element_rules = false;
|
bool did_match_any_pseudo_element_rules = false;
|
||||||
PseudoClassBitmap attempted_pseudo_class_matches;
|
PseudoClassBitmap attempted_pseudo_class_matches;
|
||||||
auto logical_alias_mapping_context = compute_logical_alias_mapping_context(element, pseudo_element, mode);
|
auto matching_rule_set = build_matching_rule_set(element, pseudo_element, attempted_pseudo_class_matches, did_match_any_pseudo_element_rules, mode);
|
||||||
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, attempted_pseudo_class_matches, mode, logical_alias_mapping_context);
|
|
||||||
|
// Resolve all the CSS custom properties ("variables") for this element:
|
||||||
|
// FIXME: Also resolve !important custom properties, in a second cascade.
|
||||||
|
if (!pseudo_element.has_value() || pseudo_element_supports_property(*pseudo_element, PropertyID::Custom)) {
|
||||||
|
HashMap<FlyString, CSS::StyleProperty> custom_properties;
|
||||||
|
for (auto& layer : matching_rule_set.author_rules) {
|
||||||
|
cascade_custom_properties(element, pseudo_element, layer.rules, custom_properties);
|
||||||
|
}
|
||||||
|
element.set_custom_properties(pseudo_element, move(custom_properties));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto logical_alias_mapping_context = compute_logical_alias_mapping_context(element, pseudo_element, mode, matching_rule_set);
|
||||||
|
auto cascaded_properties = compute_cascaded_values(element, pseudo_element, did_match_any_pseudo_element_rules, mode, matching_rule_set, logical_alias_mapping_context);
|
||||||
element.set_cascaded_properties(pseudo_element, cascaded_properties);
|
element.set_cascaded_properties(pseudo_element, cascaded_properties);
|
||||||
|
|
||||||
if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
|
if (mode == ComputeStyleMode::CreatePseudoElementStyleIfNeeded) {
|
||||||
|
|
|
@ -203,9 +203,22 @@ private:
|
||||||
|
|
||||||
struct MatchingFontCandidate;
|
struct MatchingFontCandidate;
|
||||||
|
|
||||||
LogicalAliasMappingContext compute_logical_alias_mapping_context(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const;
|
struct LayerMatchingRules {
|
||||||
|
FlyString qualified_layer_name;
|
||||||
|
Vector<MatchingRule const*> rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MatchingRuleSet {
|
||||||
|
Vector<MatchingRule const*> user_agent_rules;
|
||||||
|
Vector<MatchingRule const*> user_rules;
|
||||||
|
Vector<LayerMatchingRules> author_rules;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] MatchingRuleSet build_matching_rule_set(DOM::Element const&, Optional<PseudoElement>, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode) const;
|
||||||
|
|
||||||
|
LogicalAliasMappingContext compute_logical_alias_mapping_context(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode, MatchingRuleSet const&) const;
|
||||||
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const;
|
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::Element&, Optional<CSS::PseudoElement>, ComputeStyleMode) const;
|
||||||
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool& did_match_any_pseudo_element_rules, PseudoClassBitmap& attempted_pseudo_class_matches, ComputeStyleMode, Optional<LogicalAliasMappingContext>) const;
|
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::Element&, Optional<CSS::PseudoElement>, bool did_match_any_pseudo_element_rules, ComputeStyleMode, MatchingRuleSet const&, Optional<LogicalAliasMappingContext>) const;
|
||||||
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
|
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
|
||||||
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
|
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, bool inclusive);
|
||||||
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
|
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
|
||||||
|
@ -228,17 +241,6 @@ private:
|
||||||
Vector<FlyString> m_qualified_layer_names_in_order;
|
Vector<FlyString> m_qualified_layer_names_in_order;
|
||||||
void build_qualified_layer_names_cache();
|
void build_qualified_layer_names_cache();
|
||||||
|
|
||||||
struct LayerMatchingRules {
|
|
||||||
FlyString qualified_layer_name;
|
|
||||||
Vector<MatchingRule const*> rules;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MatchingRuleSet {
|
|
||||||
Vector<MatchingRule const*> user_agent_rules;
|
|
||||||
Vector<MatchingRule const*> user_rules;
|
|
||||||
Vector<LayerMatchingRules> author_rules;
|
|
||||||
};
|
|
||||||
|
|
||||||
void cascade_declarations(
|
void cascade_declarations(
|
||||||
CascadedProperties&,
|
CascadedProperties&,
|
||||||
DOM::Element&,
|
DOM::Element&,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue