2020-01-18 08:38:21 +00:00
|
|
|
/*
|
LibWeb: Use an ancestor filter to quickly reject many CSS selectors
Given a selector like `.foo .bar #baz`, we know that elements with
the class names `foo` and `bar` must be present in the ancestor chain of
the candidate element, or the selector cannot match.
By keeping track of the current ancestor chain during style computation,
and which strings are used in tag names and attribute names, we can do
a quick check before evaluating the selector itself, to see if all the
required ancestors are present.
The way this works:
1. CSS::Selector now has a cache of up to 8 strings that must be present
in the ancestor chain of a matching element. Note that we actually
store string *hashes*, not the strings themselves.
2. When Document performs a recursive style update, we now push and pop
elements to the ancestor chain stack as they are entered and exited.
3. When entering/exiting an ancestor, StyleComputer collects all the
relevant string hashes from that ancestor element and updates a
counting bloom filter.
4. Before evaluating a selector, we first check if any of the hashes
required by the selector are definitely missing from the ancestor
filter. If so, it cannot be a match, and we reject it immediately.
5. Otherwise, we carry on and evaluate the selector as usual.
I originally tried doing this with a HashMap, but we ended up losing
a huge chunk of the time saved to HashMap instead. As it turns out,
a simple counting bloom filter is way better at handling this.
The cost is a flat 8KB per StyleComputer, and since it's a bloom filter,
false positives are a thing.
This is extremely efficient, and allows us to quickly reject the
majority of selectors on many huge websites.
Some example rejection rates:
- https://amazon.com: 77%
- https://github.com/SerenityOS/serenity: 61%
- https://nytimes.com: 57%
- https://store.steampowered.com: 55%
- https://en.wikipedia.org: 45%
- https://youtube.com: 32%
- https://shopify.com: 25%
This also yields a chunky 37% speedup on StyleBench. :^)
2024-03-22 12:50:33 +00:00
|
|
|
* Copyright (c) 2018-2024, Andreas Kling <kling@serenityos.org>
|
2023-02-17 14:06:55 +00:00
|
|
|
* Copyright (c) 2021-2023, Sam Atkins <atkinssj@serenityos.org>
|
2020-01-18 08:38:21 +00:00
|
|
|
*
|
2021-04-22 08:24:48 +00:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 08:38:21 +00:00
|
|
|
*/
|
|
|
|
|
2019-06-27 15:47:59 +00:00
|
|
|
#pragma once
|
|
|
|
|
2021-12-03 20:00:31 +00:00
|
|
|
#include <AK/HashMap.h>
|
2022-02-24 15:54:12 +00:00
|
|
|
#include <AK/Optional.h>
|
2019-09-21 12:32:17 +00:00
|
|
|
#include <AK/OwnPtr.h>
|
2024-06-28 18:27:00 +00:00
|
|
|
#include <LibGfx/Font/Typeface.h>
|
2024-02-22 13:56:15 +00:00
|
|
|
#include <LibWeb/Animations/KeyframeEffect.h>
|
2022-03-29 00:14:20 +00:00
|
|
|
#include <LibWeb/CSS/CSSFontFaceRule.h>
|
2023-05-26 20:00:54 +00:00
|
|
|
#include <LibWeb/CSS/CSSKeyframesRule.h>
|
2021-05-24 21:02:58 +00:00
|
|
|
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
2022-02-24 15:54:12 +00:00
|
|
|
#include <LibWeb/CSS/Selector.h>
|
2020-03-07 09:32:51 +00:00
|
|
|
#include <LibWeb/CSS/StyleProperties.h>
|
2020-07-26 17:37:56 +00:00
|
|
|
#include <LibWeb/Forward.h>
|
2024-05-15 20:58:24 +00:00
|
|
|
#include <LibWeb/Loader/ResourceLoader.h>
|
2019-06-27 15:47:59 +00:00
|
|
|
|
2020-07-26 18:01:35 +00:00
|
|
|
namespace Web::CSS {
|
2020-03-07 09:27:02 +00:00
|
|
|
|
LibWeb: Use an ancestor filter to quickly reject many CSS selectors
Given a selector like `.foo .bar #baz`, we know that elements with
the class names `foo` and `bar` must be present in the ancestor chain of
the candidate element, or the selector cannot match.
By keeping track of the current ancestor chain during style computation,
and which strings are used in tag names and attribute names, we can do
a quick check before evaluating the selector itself, to see if all the
required ancestors are present.
The way this works:
1. CSS::Selector now has a cache of up to 8 strings that must be present
in the ancestor chain of a matching element. Note that we actually
store string *hashes*, not the strings themselves.
2. When Document performs a recursive style update, we now push and pop
elements to the ancestor chain stack as they are entered and exited.
3. When entering/exiting an ancestor, StyleComputer collects all the
relevant string hashes from that ancestor element and updates a
counting bloom filter.
4. Before evaluating a selector, we first check if any of the hashes
required by the selector are definitely missing from the ancestor
filter. If so, it cannot be a match, and we reject it immediately.
5. Otherwise, we carry on and evaluate the selector as usual.
I originally tried doing this with a HashMap, but we ended up losing
a huge chunk of the time saved to HashMap instead. As it turns out,
a simple counting bloom filter is way better at handling this.
The cost is a flat 8KB per StyleComputer, and since it's a bloom filter,
false positives are a thing.
This is extremely efficient, and allows us to quickly reject the
majority of selectors on many huge websites.
Some example rejection rates:
- https://amazon.com: 77%
- https://github.com/SerenityOS/serenity: 61%
- https://nytimes.com: 57%
- https://store.steampowered.com: 55%
- https://en.wikipedia.org: 45%
- https://youtube.com: 32%
- https://shopify.com: 25%
This also yields a chunky 37% speedup on StyleBench. :^)
2024-03-22 12:50:33 +00:00
|
|
|
// A counting bloom filter with 2 hash functions.
|
|
|
|
// NOTE: If a counter overflows, it's kept maxed-out until the whole filter is cleared.
|
|
|
|
template<typename CounterType, size_t key_bits>
|
|
|
|
class CountingBloomFilter {
|
|
|
|
public:
|
|
|
|
CountingBloomFilter() { }
|
|
|
|
|
|
|
|
void clear() { __builtin_memset(m_buckets, 0, sizeof(m_buckets)); }
|
|
|
|
|
|
|
|
void increment(u32 key)
|
|
|
|
{
|
|
|
|
auto& first = bucket1(key);
|
|
|
|
if (first < NumericLimits<CounterType>::max())
|
|
|
|
++first;
|
|
|
|
auto& second = bucket2(key);
|
|
|
|
if (second < NumericLimits<CounterType>::max())
|
|
|
|
++second;
|
|
|
|
}
|
|
|
|
|
|
|
|
void decrement(u32 key)
|
|
|
|
{
|
|
|
|
auto& first = bucket1(key);
|
|
|
|
if (first < NumericLimits<CounterType>::max())
|
|
|
|
--first;
|
|
|
|
auto& second = bucket2(key);
|
|
|
|
if (second < NumericLimits<CounterType>::max())
|
|
|
|
--second;
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] bool may_contain(u32 hash) const
|
|
|
|
{
|
|
|
|
return bucket1(hash) && bucket2(hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
static constexpr u32 bucket_count = 1 << key_bits;
|
|
|
|
static constexpr u32 key_mask = bucket_count - 1;
|
|
|
|
|
|
|
|
[[nodiscard]] u32 hash1(u32 key) const { return key & key_mask; }
|
|
|
|
[[nodiscard]] u32 hash2(u32 key) const { return (key >> 16) & key_mask; }
|
|
|
|
|
|
|
|
[[nodiscard]] CounterType& bucket1(u32 key) { return m_buckets[hash1(key)]; }
|
|
|
|
[[nodiscard]] CounterType& bucket2(u32 key) { return m_buckets[hash2(key)]; }
|
|
|
|
[[nodiscard]] CounterType bucket1(u32 key) const { return m_buckets[hash1(key)]; }
|
|
|
|
[[nodiscard]] CounterType bucket2(u32 key) const { return m_buckets[hash2(key)]; }
|
|
|
|
|
|
|
|
CounterType m_buckets[bucket_count];
|
|
|
|
};
|
|
|
|
|
2023-03-19 16:01:26 +00:00
|
|
|
// https://www.w3.org/TR/css-cascade/#origin
|
2024-03-18 15:01:47 +00:00
|
|
|
enum class CascadeOrigin : u8 {
|
2023-03-19 16:01:26 +00:00
|
|
|
Author,
|
|
|
|
User,
|
|
|
|
UserAgent,
|
|
|
|
Animation,
|
|
|
|
Transition,
|
|
|
|
};
|
|
|
|
|
2020-06-12 22:44:26 +00:00
|
|
|
struct MatchingRule {
|
2023-03-19 16:01:26 +00:00
|
|
|
JS::GCPtr<DOM::ShadowRoot const> shadow_root;
|
2023-02-26 23:09:02 +00:00
|
|
|
JS::GCPtr<CSSStyleRule const> rule;
|
2023-07-29 16:51:15 +00:00
|
|
|
JS::GCPtr<CSSStyleSheet const> sheet;
|
2020-06-12 22:44:26 +00:00
|
|
|
size_t style_sheet_index { 0 };
|
|
|
|
size_t rule_index { 0 };
|
|
|
|
size_t selector_index { 0 };
|
2024-03-13 10:47:25 +00:00
|
|
|
|
2021-05-24 21:01:24 +00:00
|
|
|
u32 specificity { 0 };
|
2024-03-18 15:01:47 +00:00
|
|
|
CascadeOrigin cascade_origin;
|
2023-03-09 18:27:23 +00:00
|
|
|
bool contains_pseudo_element { false };
|
2024-03-13 10:47:25 +00:00
|
|
|
bool contains_root_pseudo_class { false };
|
2024-03-19 09:36:08 +00:00
|
|
|
bool can_use_fast_matches { false };
|
2020-06-12 22:44:26 +00:00
|
|
|
};
|
|
|
|
|
2023-05-24 13:35:30 +00:00
|
|
|
struct FontFaceKey {
|
|
|
|
FlyString family_name;
|
|
|
|
int weight { 0 };
|
|
|
|
int slope { 0 };
|
|
|
|
|
|
|
|
[[nodiscard]] u32 hash() const { return pair_int_hash(family_name.hash(), pair_int_hash(weight, slope)); }
|
|
|
|
[[nodiscard]] bool operator==(FontFaceKey const&) const = default;
|
|
|
|
};
|
|
|
|
|
2024-05-15 20:58:24 +00:00
|
|
|
class FontLoader;
|
|
|
|
|
2021-09-24 11:49:57 +00:00
|
|
|
class StyleComputer {
|
2019-06-27 15:47:59 +00:00
|
|
|
public:
|
2024-03-18 00:29:31 +00:00
|
|
|
enum class AllowUnresolved {
|
|
|
|
Yes,
|
|
|
|
No,
|
|
|
|
};
|
2024-08-14 10:10:54 +00:00
|
|
|
static void for_each_property_expanding_shorthands(PropertyID, CSSStyleValue const&, AllowUnresolved, Function<void(PropertyID, CSSStyleValue const&)> const& set_longhand_property);
|
|
|
|
static void set_property_expanding_shorthands(StyleProperties&, PropertyID, CSSStyleValue const&, CSS::CSSStyleDeclaration const*, StyleProperties const& style_for_revert, Important = Important::No);
|
|
|
|
static NonnullRefPtr<CSSStyleValue const> get_inherit_value(JS::Realm& initial_value_context_realm, CSS::PropertyID, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type> = {});
|
2024-02-24 15:36:24 +00:00
|
|
|
|
2021-09-24 11:49:57 +00:00
|
|
|
explicit StyleComputer(DOM::Document&);
|
2022-03-29 00:14:20 +00:00
|
|
|
~StyleComputer();
|
2019-06-27 15:47:59 +00:00
|
|
|
|
2020-07-26 17:37:56 +00:00
|
|
|
DOM::Document& document() { return m_document; }
|
2021-07-14 15:56:11 +00:00
|
|
|
DOM::Document const& document() const { return m_document; }
|
2019-06-27 15:47:59 +00:00
|
|
|
|
LibWeb: Use an ancestor filter to quickly reject many CSS selectors
Given a selector like `.foo .bar #baz`, we know that elements with
the class names `foo` and `bar` must be present in the ancestor chain of
the candidate element, or the selector cannot match.
By keeping track of the current ancestor chain during style computation,
and which strings are used in tag names and attribute names, we can do
a quick check before evaluating the selector itself, to see if all the
required ancestors are present.
The way this works:
1. CSS::Selector now has a cache of up to 8 strings that must be present
in the ancestor chain of a matching element. Note that we actually
store string *hashes*, not the strings themselves.
2. When Document performs a recursive style update, we now push and pop
elements to the ancestor chain stack as they are entered and exited.
3. When entering/exiting an ancestor, StyleComputer collects all the
relevant string hashes from that ancestor element and updates a
counting bloom filter.
4. Before evaluating a selector, we first check if any of the hashes
required by the selector are definitely missing from the ancestor
filter. If so, it cannot be a match, and we reject it immediately.
5. Otherwise, we carry on and evaluate the selector as usual.
I originally tried doing this with a HashMap, but we ended up losing
a huge chunk of the time saved to HashMap instead. As it turns out,
a simple counting bloom filter is way better at handling this.
The cost is a flat 8KB per StyleComputer, and since it's a bloom filter,
false positives are a thing.
This is extremely efficient, and allows us to quickly reject the
majority of selectors on many huge websites.
Some example rejection rates:
- https://amazon.com: 77%
- https://github.com/SerenityOS/serenity: 61%
- https://nytimes.com: 57%
- https://store.steampowered.com: 55%
- https://en.wikipedia.org: 45%
- https://youtube.com: 32%
- https://shopify.com: 25%
This also yields a chunky 37% speedup on StyleBench. :^)
2024-03-22 12:50:33 +00:00
|
|
|
void reset_ancestor_filter();
|
|
|
|
void push_ancestor(DOM::Element const&);
|
|
|
|
void pop_ancestor(DOM::Element const&);
|
|
|
|
|
2021-09-23 17:48:41 +00:00
|
|
|
NonnullRefPtr<StyleProperties> create_document_style() const;
|
2023-03-14 15:36:20 +00:00
|
|
|
|
2024-03-06 01:10:02 +00:00
|
|
|
NonnullRefPtr<StyleProperties> compute_style(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type> = {}) const;
|
|
|
|
RefPtr<StyleProperties> compute_pseudo_element_style_if_needed(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>) const;
|
2019-06-27 15:47:59 +00:00
|
|
|
|
2023-12-10 08:00:03 +00:00
|
|
|
Vector<MatchingRule> collect_matching_rules(DOM::Element const&, CascadeOrigin, Optional<CSS::Selector::PseudoElement::Type>) const;
|
2019-06-27 18:40:21 +00:00
|
|
|
|
2022-02-10 16:49:50 +00:00
|
|
|
void invalidate_rule_cache();
|
|
|
|
|
2022-03-11 12:53:32 +00:00
|
|
|
Gfx::Font const& initial_font() const;
|
|
|
|
|
2023-02-17 14:06:55 +00:00
|
|
|
void did_load_font(FlyString const& family_name);
|
2022-03-29 00:14:20 +00:00
|
|
|
|
2024-05-18 00:14:06 +00:00
|
|
|
Optional<FontLoader&> load_font_face(ParsedFontFace const&, ESCAPING Function<void(FontLoader const&)> on_load = {}, ESCAPING Function<void()> on_fail = {});
|
2024-05-15 20:58:24 +00:00
|
|
|
|
2022-04-08 19:27:35 +00:00
|
|
|
void load_fonts_from_sheet(CSSStyleSheet const&);
|
|
|
|
|
2024-08-14 10:10:54 +00:00
|
|
|
RefPtr<Gfx::FontCascadeList const> compute_font_for_style_values(DOM::Element const* element, Optional<CSS::Selector::PseudoElement::Type> pseudo_element, CSSStyleValue const& font_family, CSSStyleValue const& font_size, CSSStyleValue const& font_style, CSSStyleValue const& font_weight, CSSStyleValue const& font_stretch, int math_depth = 0) const;
|
2023-08-07 19:48:18 +00:00
|
|
|
|
2024-01-11 13:04:18 +00:00
|
|
|
void set_viewport_rect(Badge<DOM::Document>, CSSPixelRect const& viewport_rect) { m_viewport_rect = viewport_rect; }
|
|
|
|
|
2024-03-16 06:44:48 +00:00
|
|
|
enum class AnimationRefresh {
|
|
|
|
No,
|
|
|
|
Yes,
|
|
|
|
};
|
2024-03-18 00:32:01 +00:00
|
|
|
void collect_animation_into(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, JS::NonnullGCPtr<Animations::KeyframeEffect> animation, StyleProperties& style_properties, AnimationRefresh = AnimationRefresh::No) const;
|
2024-03-16 06:44:48 +00:00
|
|
|
|
2019-06-27 15:47:59 +00:00
|
|
|
private:
|
2023-03-14 15:36:20 +00:00
|
|
|
enum class ComputeStyleMode {
|
|
|
|
Normal,
|
|
|
|
CreatePseudoElementStyleIfNeeded,
|
|
|
|
};
|
|
|
|
|
2023-08-17 16:45:06 +00:00
|
|
|
struct MatchingFontCandidate;
|
2023-05-29 02:16:16 +00:00
|
|
|
|
LibWeb: Use an ancestor filter to quickly reject many CSS selectors
Given a selector like `.foo .bar #baz`, we know that elements with
the class names `foo` and `bar` must be present in the ancestor chain of
the candidate element, or the selector cannot match.
By keeping track of the current ancestor chain during style computation,
and which strings are used in tag names and attribute names, we can do
a quick check before evaluating the selector itself, to see if all the
required ancestors are present.
The way this works:
1. CSS::Selector now has a cache of up to 8 strings that must be present
in the ancestor chain of a matching element. Note that we actually
store string *hashes*, not the strings themselves.
2. When Document performs a recursive style update, we now push and pop
elements to the ancestor chain stack as they are entered and exited.
3. When entering/exiting an ancestor, StyleComputer collects all the
relevant string hashes from that ancestor element and updates a
counting bloom filter.
4. Before evaluating a selector, we first check if any of the hashes
required by the selector are definitely missing from the ancestor
filter. If so, it cannot be a match, and we reject it immediately.
5. Otherwise, we carry on and evaluate the selector as usual.
I originally tried doing this with a HashMap, but we ended up losing
a huge chunk of the time saved to HashMap instead. As it turns out,
a simple counting bloom filter is way better at handling this.
The cost is a flat 8KB per StyleComputer, and since it's a bloom filter,
false positives are a thing.
This is extremely efficient, and allows us to quickly reject the
majority of selectors on many huge websites.
Some example rejection rates:
- https://amazon.com: 77%
- https://github.com/SerenityOS/serenity: 61%
- https://nytimes.com: 57%
- https://store.steampowered.com: 55%
- https://en.wikipedia.org: 45%
- https://youtube.com: 32%
- https://shopify.com: 25%
This also yields a chunky 37% speedup on StyleBench. :^)
2024-03-22 12:50:33 +00:00
|
|
|
[[nodiscard]] bool should_reject_with_ancestor_filter(Selector const&) const;
|
|
|
|
|
2024-03-06 01:10:02 +00:00
|
|
|
RefPtr<StyleProperties> compute_style_impl(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, ComputeStyleMode) const;
|
|
|
|
void compute_cascaded_values(StyleProperties&, DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, bool& did_match_any_pseudo_element_rules, ComputeStyleMode) const;
|
2023-12-09 22:42:02 +00:00
|
|
|
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);
|
|
|
|
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FontFaceKey const& key, float font_size_in_pt) const;
|
2023-12-10 08:00:03 +00:00
|
|
|
void compute_font(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
|
|
|
|
void compute_math_depth(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
|
|
|
|
void compute_defaulted_values(StyleProperties&, DOM::Element const*, Optional<CSS::Selector::PseudoElement::Type>) const;
|
2024-01-12 11:39:40 +00:00
|
|
|
void absolutize_values(StyleProperties&) const;
|
2024-02-04 14:27:04 +00:00
|
|
|
void resolve_effective_overflow_values(StyleProperties&) const;
|
2023-12-10 08:00:03 +00:00
|
|
|
void transform_box_type_if_needed(StyleProperties&, DOM::Element const&, Optional<CSS::Selector::PseudoElement::Type>) const;
|
2021-09-23 11:13:51 +00:00
|
|
|
|
2023-12-10 08:00:03 +00:00
|
|
|
void compute_defaulted_property_value(StyleProperties&, DOM::Element const*, CSS::PropertyID, Optional<CSS::Selector::PseudoElement::Type>) const;
|
2021-09-21 09:38:18 +00:00
|
|
|
|
2024-08-14 10:10:54 +00:00
|
|
|
void set_all_properties(DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, StyleProperties&, CSSStyleValue const&, DOM::Document&, CSS::CSSStyleDeclaration const*, StyleProperties const& style_for_revert, Important = Important::No) const;
|
2023-07-29 08:53:24 +00:00
|
|
|
|
2019-10-05 07:01:12 +00:00
|
|
|
template<typename Callback>
|
2021-09-21 09:38:18 +00:00
|
|
|
void for_each_stylesheet(CascadeOrigin, Callback) const;
|
|
|
|
|
2024-01-11 13:04:18 +00:00
|
|
|
[[nodiscard]] CSSPixelRect viewport_rect() const { return m_viewport_rect; }
|
|
|
|
|
2023-05-08 08:28:21 +00:00
|
|
|
[[nodiscard]] Length::FontMetrics calculate_root_element_font_metrics(StyleProperties const&) const;
|
2022-03-19 17:08:52 +00:00
|
|
|
|
2021-09-21 09:38:18 +00:00
|
|
|
struct MatchingRuleSet {
|
|
|
|
Vector<MatchingRule> user_agent_rules;
|
2023-08-21 14:50:01 +00:00
|
|
|
Vector<MatchingRule> user_rules;
|
2021-09-21 09:38:18 +00:00
|
|
|
Vector<MatchingRule> author_rules;
|
|
|
|
};
|
|
|
|
|
2023-12-10 08:00:03 +00:00
|
|
|
void cascade_declarations(StyleProperties&, DOM::Element&, Optional<CSS::Selector::PseudoElement::Type>, Vector<MatchingRule> const&, CascadeOrigin, Important) const;
|
2019-10-05 07:01:12 +00:00
|
|
|
|
2022-02-10 16:49:50 +00:00
|
|
|
void build_rule_cache();
|
|
|
|
void build_rule_cache_if_needed() const;
|
|
|
|
|
2023-02-26 23:09:02 +00:00
|
|
|
JS::NonnullGCPtr<DOM::Document> m_document;
|
2022-02-10 16:49:50 +00:00
|
|
|
|
|
|
|
struct RuleCache {
|
2023-02-17 14:19:16 +00:00
|
|
|
HashMap<FlyString, Vector<MatchingRule>> rules_by_id;
|
|
|
|
HashMap<FlyString, Vector<MatchingRule>> rules_by_class;
|
|
|
|
HashMap<FlyString, Vector<MatchingRule>> rules_by_tag_name;
|
2024-03-16 07:46:54 +00:00
|
|
|
HashMap<FlyString, Vector<MatchingRule>, AK::ASCIICaseInsensitiveFlyStringTraits> rules_by_attribute_name;
|
2024-03-13 10:47:25 +00:00
|
|
|
Vector<MatchingRule> pseudo_element_rules;
|
|
|
|
Vector<MatchingRule> root_rules;
|
2022-02-10 16:49:50 +00:00
|
|
|
Vector<MatchingRule> other_rules;
|
2023-05-26 20:00:54 +00:00
|
|
|
|
2024-02-22 13:56:15 +00:00
|
|
|
HashMap<FlyString, NonnullRefPtr<Animations::KeyframeEffect::KeyFrameSet>> rules_by_animation_keyframes;
|
2022-02-10 16:49:50 +00:00
|
|
|
};
|
2023-03-07 19:13:13 +00:00
|
|
|
|
|
|
|
NonnullOwnPtr<RuleCache> make_rule_cache_for_cascade_origin(CascadeOrigin);
|
|
|
|
|
|
|
|
RuleCache const& rule_cache_for_cascade_origin(CascadeOrigin) const;
|
|
|
|
|
|
|
|
OwnPtr<RuleCache> m_author_rule_cache;
|
2023-08-21 14:50:01 +00:00
|
|
|
OwnPtr<RuleCache> m_user_rule_cache;
|
2023-03-07 19:13:13 +00:00
|
|
|
OwnPtr<RuleCache> m_user_agent_rule_cache;
|
2023-08-21 14:50:01 +00:00
|
|
|
JS::Handle<CSSStyleSheet> m_user_style_sheet;
|
2022-03-29 00:14:20 +00:00
|
|
|
|
2023-12-09 22:42:02 +00:00
|
|
|
using FontLoaderList = Vector<NonnullOwnPtr<FontLoader>>;
|
|
|
|
HashMap<FontFaceKey, FontLoaderList> m_loaded_fonts;
|
2023-05-08 08:28:21 +00:00
|
|
|
|
|
|
|
Length::FontMetrics m_default_font_metrics;
|
|
|
|
Length::FontMetrics m_root_element_font_metrics;
|
2023-05-26 20:00:54 +00:00
|
|
|
|
2024-01-11 13:04:18 +00:00
|
|
|
CSSPixelRect m_viewport_rect;
|
LibWeb: Use an ancestor filter to quickly reject many CSS selectors
Given a selector like `.foo .bar #baz`, we know that elements with
the class names `foo` and `bar` must be present in the ancestor chain of
the candidate element, or the selector cannot match.
By keeping track of the current ancestor chain during style computation,
and which strings are used in tag names and attribute names, we can do
a quick check before evaluating the selector itself, to see if all the
required ancestors are present.
The way this works:
1. CSS::Selector now has a cache of up to 8 strings that must be present
in the ancestor chain of a matching element. Note that we actually
store string *hashes*, not the strings themselves.
2. When Document performs a recursive style update, we now push and pop
elements to the ancestor chain stack as they are entered and exited.
3. When entering/exiting an ancestor, StyleComputer collects all the
relevant string hashes from that ancestor element and updates a
counting bloom filter.
4. Before evaluating a selector, we first check if any of the hashes
required by the selector are definitely missing from the ancestor
filter. If so, it cannot be a match, and we reject it immediately.
5. Otherwise, we carry on and evaluate the selector as usual.
I originally tried doing this with a HashMap, but we ended up losing
a huge chunk of the time saved to HashMap instead. As it turns out,
a simple counting bloom filter is way better at handling this.
The cost is a flat 8KB per StyleComputer, and since it's a bloom filter,
false positives are a thing.
This is extremely efficient, and allows us to quickly reject the
majority of selectors on many huge websites.
Some example rejection rates:
- https://amazon.com: 77%
- https://github.com/SerenityOS/serenity: 61%
- https://nytimes.com: 57%
- https://store.steampowered.com: 55%
- https://en.wikipedia.org: 45%
- https://youtube.com: 32%
- https://shopify.com: 25%
This also yields a chunky 37% speedup on StyleBench. :^)
2024-03-22 12:50:33 +00:00
|
|
|
|
|
|
|
CountingBloomFilter<u8, 14> m_ancestor_filter;
|
2019-06-27 15:47:59 +00:00
|
|
|
};
|
2020-03-07 09:27:02 +00:00
|
|
|
|
2024-05-15 20:58:24 +00:00
|
|
|
class FontLoader : public ResourceClient {
|
|
|
|
public:
|
2024-05-18 00:14:06 +00:00
|
|
|
FontLoader(StyleComputer& style_computer, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL::URL> urls, ESCAPING Function<void(FontLoader const&)> on_load = {}, ESCAPING Function<void()> on_fail = {});
|
2024-05-15 20:58:24 +00:00
|
|
|
|
|
|
|
virtual ~FontLoader() override;
|
|
|
|
|
|
|
|
Vector<Gfx::UnicodeRange> const& unicode_ranges() const { return m_unicode_ranges; }
|
2024-06-28 18:27:00 +00:00
|
|
|
RefPtr<Gfx::Typeface> vector_font() const { return m_vector_font; }
|
2024-05-15 20:58:24 +00:00
|
|
|
|
|
|
|
virtual void resource_did_load() override;
|
|
|
|
virtual void resource_did_fail() override;
|
|
|
|
|
|
|
|
RefPtr<Gfx::Font> font_with_point_size(float point_size);
|
|
|
|
void start_loading_next_url();
|
|
|
|
|
|
|
|
private:
|
2024-06-28 18:27:00 +00:00
|
|
|
ErrorOr<NonnullRefPtr<Gfx::Typeface>> try_load_font();
|
2024-05-15 20:58:24 +00:00
|
|
|
|
|
|
|
StyleComputer& m_style_computer;
|
|
|
|
FlyString m_family_name;
|
|
|
|
Vector<Gfx::UnicodeRange> m_unicode_ranges;
|
2024-06-28 18:27:00 +00:00
|
|
|
RefPtr<Gfx::Typeface> m_vector_font;
|
2024-05-15 20:58:24 +00:00
|
|
|
Vector<URL::URL> m_urls;
|
|
|
|
Function<void(FontLoader const&)> m_on_load;
|
|
|
|
Function<void()> m_on_fail;
|
|
|
|
};
|
|
|
|
|
2020-03-07 09:27:02 +00:00
|
|
|
}
|