2020-01-18 09:38:21 +01:00
|
|
|
/*
|
2025-07-30 14:38:41 +02:00
|
|
|
* Copyright (c) 2018-2025, Andreas Kling <andreas@ladybird.org>
|
2024-08-23 11:03:05 +01:00
|
|
|
* Copyright (c) 2021-2024, Sam Atkins <sam@ladybird.org>
|
2020-01-18 09:38:21 +01:00
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 09:38:21 +01:00
|
|
|
*/
|
|
|
|
|
|
2019-06-27 17:47:59 +02: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 15:32:17 +03:00
|
|
|
#include <AK/OwnPtr.h>
|
2024-06-28 20:27:00 +02:00
|
|
|
#include <LibGfx/Font/Typeface.h>
|
2025-02-18 09:19:56 +01:00
|
|
|
#include <LibGfx/FontCascadeList.h>
|
2024-02-22 13:56:15 +00:00
|
|
|
#include <LibWeb/Animations/KeyframeEffect.h>
|
2022-03-29 02:14:20 +02:00
|
|
|
#include <LibWeb/CSS/CSSFontFaceRule.h>
|
2023-05-26 23:30:54 +03:30
|
|
|
#include <LibWeb/CSS/CSSKeyframesRule.h>
|
2021-05-24 23:02:58 +02:00
|
|
|
#include <LibWeb/CSS/CSSStyleDeclaration.h>
|
LibWeb: Split StyleComputer work into two phases with separate outputs
Before this change, StyleComputer would essentially take a DOM element,
find all the CSS rules that apply to it, and resolve the computed value
for each CSS property for that element.
This worked great, but it meant we had to do all the work of selector
matching and cascading every time.
To enable new optimizations, this change introduces a break in the
middle of this process where we've produced a "CascadedProperties".
This object contains the result of the cascade, before we've begun
turning cascaded values into computed values.
The cascaded properties are now stored with each element, which will
later allow us to do partial updates without re-running the full
StyleComputer machine. This will be particularly valuable for
re-implementing CSS inheritance, which is extremely heavy today.
Note that CSS animations and CSS transitions operate entirely on the
computed values, even though the cascade order would have you believe
they happen earlier. I'm not confident we have the right architecture
for this, but that's a separate issue.
2024-12-12 10:06:29 +01:00
|
|
|
#include <LibWeb/CSS/CascadeOrigin.h>
|
|
|
|
|
#include <LibWeb/CSS/CascadedProperties.h>
|
2022-02-24 15:54:12 +00:00
|
|
|
#include <LibWeb/CSS/Selector.h>
|
2025-01-12 18:38:05 +03:00
|
|
|
#include <LibWeb/CSS/StyleInvalidationData.h>
|
LibWeb: Add StyleScope to keep style caches per Document/ShadowRoot
Before this change, we've been maintaining various StyleComputer caches
at the document level.
This made sense for old-school documents without shadow trees, since
all the style information was document-wide anyway. However, documents
with many shadow trees ended up suffering since any time you mutated
a style sheet inside a shadow tree, *all* style caches for the entire
document would get invalidated.
This was particularly expensive on Reddit, which has tons of shadow
trees with their own style elements. Every time we'd create one of their
custom elements, we'd invalidate the document-level "rule cache" and
have to rebuild it, taking about ~60ms each time (ouch).
This commit introduces a new object called StyleScope.
Every Document and ShadowRoot has its own StyleScope. Rule caches etc
are moved from StyleComputer to StyleScope.
Rule cache invalidation now happens at StyleScope level. As an example,
rule cache rebuilds now take ~1ms on Reddit instead of ~60ms.
This is largely a mechanical change, moving things around, but there's
one key detail to be aware of: due to the :host selector, which works
across the shadow DOM boundary and reaches from inside a shadow tree out
into the light tree, there are various places where we have to check
both the shadow tree's StyleScope *and* the document-level StyleScope
in order to get all rules that may apply.
2025-11-13 19:08:08 +01:00
|
|
|
#include <LibWeb/CSS/StyleScope.h>
|
2025-07-19 19:35:33 -07:00
|
|
|
#include <LibWeb/Export.h>
|
2020-07-26 19:37:56 +02:00
|
|
|
#include <LibWeb/Forward.h>
|
2024-05-15 14:58:24 -06:00
|
|
|
#include <LibWeb/Loader/ResourceLoader.h>
|
2019-06-27 17:47:59 +02:00
|
|
|
|
2020-07-26 20:01:35 +02:00
|
|
|
namespace Web::CSS {
|
2020-03-07 10:27:02 +01: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 13:50:33 +01: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];
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-26 23:35:58 +02:00
|
|
|
struct FontFaceKey;
|
|
|
|
|
|
|
|
|
|
struct OwnFontFaceKey {
|
|
|
|
|
explicit OwnFontFaceKey(FontFaceKey const& other);
|
|
|
|
|
|
|
|
|
|
operator FontFaceKey() const;
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] u32 hash() const { return pair_int_hash(family_name.hash(), pair_int_hash(weight, slope)); }
|
|
|
|
|
[[nodiscard]] bool operator==(OwnFontFaceKey const& other) const = default;
|
|
|
|
|
[[nodiscard]] bool operator==(FontFaceKey const& other) const;
|
|
|
|
|
|
2023-05-24 15:35:30 +02:00
|
|
|
FlyString family_name;
|
|
|
|
|
int weight { 0 };
|
|
|
|
|
int slope { 0 };
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-04 23:25:15 +00:00
|
|
|
struct FontMatchingAlgorithmCacheKey {
|
|
|
|
|
FlyString family_name;
|
|
|
|
|
int weight;
|
|
|
|
|
int slope;
|
|
|
|
|
float font_size_in_pt;
|
|
|
|
|
|
|
|
|
|
[[nodiscard]] bool operator==(FontMatchingAlgorithmCacheKey const& other) const = default;
|
|
|
|
|
};
|
|
|
|
|
|
2024-05-15 14:58:24 -06:00
|
|
|
class FontLoader;
|
|
|
|
|
|
2025-07-19 19:35:33 -07:00
|
|
|
class WEB_API StyleComputer final : public GC::Cell {
|
2025-07-30 14:38:41 +02:00
|
|
|
GC_CELL(StyleComputer, GC::Cell);
|
|
|
|
|
GC_DECLARE_ALLOCATOR(StyleComputer);
|
|
|
|
|
|
2019-06-27 17:47:59 +02:00
|
|
|
public:
|
2025-08-08 10:11:51 +01:00
|
|
|
static void for_each_property_expanding_shorthands(PropertyID, StyleValue const&, Function<void(PropertyID, StyleValue const&)> const& set_longhand_property);
|
2025-08-28 23:42:46 +12:00
|
|
|
static NonnullRefPtr<StyleValue const> get_non_animated_inherit_value(PropertyID, DOM::AbstractElement);
|
2025-11-16 21:16:01 +13:00
|
|
|
struct AnimatedInheritValue {
|
|
|
|
|
NonnullRefPtr<StyleValue const> value;
|
|
|
|
|
AnimatedPropertyResultOfTransition is_result_of_transition;
|
|
|
|
|
};
|
|
|
|
|
static Optional<AnimatedInheritValue> get_animated_inherit_value(PropertyID, DOM::AbstractElement);
|
2024-02-24 08:36:24 -07:00
|
|
|
|
2024-08-23 11:03:05 +01:00
|
|
|
static Optional<String> user_agent_style_sheet_source(StringView name);
|
|
|
|
|
|
2021-09-24 13:49:57 +02:00
|
|
|
explicit StyleComputer(DOM::Document&);
|
2022-03-29 02:14:20 +02:00
|
|
|
~StyleComputer();
|
2019-06-27 17:47:59 +02:00
|
|
|
|
2020-07-26 19:37:56 +02:00
|
|
|
DOM::Document& document() { return m_document; }
|
2021-07-14 16:56:11 +01:00
|
|
|
DOM::Document const& document() const { return m_document; }
|
2019-06-27 17:47:59 +02: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 13:50:33 +01:00
|
|
|
void reset_ancestor_filter();
|
|
|
|
|
void push_ancestor(DOM::Element const&);
|
|
|
|
|
void pop_ancestor(DOM::Element const&);
|
|
|
|
|
|
2024-12-20 16:35:12 +01:00
|
|
|
[[nodiscard]] GC::Ref<ComputedProperties> create_document_style() const;
|
2023-03-14 16:36:20 +01:00
|
|
|
|
2025-09-05 14:56:12 +01:00
|
|
|
[[nodiscard]] GC::Ref<ComputedProperties> compute_style(DOM::AbstractElement, Optional<bool&> did_change_custom_properties = {}) const;
|
2025-09-09 11:40:06 +01:00
|
|
|
[[nodiscard]] GC::Ptr<ComputedProperties> compute_pseudo_element_style_if_needed(DOM::AbstractElement, Optional<bool&> did_change_custom_properties) const;
|
2019-06-27 17:47:59 +02:00
|
|
|
|
2025-09-09 14:01:23 +01:00
|
|
|
[[nodiscard]] Vector<MatchingRule const*> collect_matching_rules(DOM::AbstractElement, CascadeOrigin, PseudoClassBitmap& attempted_pseudo_class_matches, Optional<FlyString const> qualified_layer_name = {}) const;
|
2019-06-27 20:40:21 +02:00
|
|
|
|
LibWeb: Add StyleScope to keep style caches per Document/ShadowRoot
Before this change, we've been maintaining various StyleComputer caches
at the document level.
This made sense for old-school documents without shadow trees, since
all the style information was document-wide anyway. However, documents
with many shadow trees ended up suffering since any time you mutated
a style sheet inside a shadow tree, *all* style caches for the entire
document would get invalidated.
This was particularly expensive on Reddit, which has tons of shadow
trees with their own style elements. Every time we'd create one of their
custom elements, we'd invalidate the document-level "rule cache" and
have to rebuild it, taking about ~60ms each time (ouch).
This commit introduces a new object called StyleScope.
Every Document and ShadowRoot has its own StyleScope. Rule caches etc
are moved from StyleComputer to StyleScope.
Rule cache invalidation now happens at StyleScope level. As an example,
rule cache rebuilds now take ~1ms on Reddit instead of ~60ms.
This is largely a mechanical change, moving things around, but there's
one key detail to be aware of: due to the :host selector, which works
across the shadow DOM boundary and reaches from inside a shadow tree out
into the light tree, there are various places where we have to check
both the shadow tree's StyleScope *and* the document-level StyleScope
in order to get all rules that may apply.
2025-11-13 19:08:08 +01:00
|
|
|
InvalidationSet invalidation_set_for_properties(Vector<InvalidationSet::Property> const&, StyleScope const&) const;
|
|
|
|
|
bool invalidation_property_used_in_has_selector(InvalidationSet::Property const&, StyleScope const&) const;
|
2022-02-10 17:49:50 +01:00
|
|
|
|
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 02:14:20 +02:00
|
|
|
|
2025-07-30 14:38:41 +02:00
|
|
|
GC::Ptr<FontLoader> load_font_face(ParsedFontFace const&, ESCAPING Function<void(RefPtr<Gfx::Typeface const>)> on_load = {});
|
2024-05-15 14:58:24 -06:00
|
|
|
|
2024-09-22 18:10:46 +02:00
|
|
|
void load_fonts_from_sheet(CSSStyleSheet&);
|
|
|
|
|
void unload_fonts_from_sheet(CSSStyleSheet&);
|
2022-04-08 21:27:35 +02:00
|
|
|
|
2024-12-22 21:34:50 +01:00
|
|
|
static CSSPixels default_user_font_size();
|
2025-08-29 17:52:45 +12:00
|
|
|
static CSSPixels absolute_size_mapping(AbsoluteSize, CSSPixels default_font_size);
|
|
|
|
|
static CSSPixels relative_size_mapping(RelativeSize, CSSPixels inherited_font_size);
|
2025-10-14 16:21:00 +02:00
|
|
|
RefPtr<Gfx::FontCascadeList const> compute_font_for_style_values(StyleValue const& font_family, CSSPixels const& font_size, int font_slope, double font_weight, Percentage const& font_width, HashMap<FlyString, NumberOrCalculated> const& font_variation_settings, Length::ResolutionContext const& length_resolution_context) const;
|
2025-09-09 11:55:45 +01:00
|
|
|
[[nodiscard]] RefPtr<StyleValue const> recascade_font_size_if_needed(DOM::AbstractElement, CascadedProperties&) const;
|
LibWeb: Implement time-traveling inheritance for CSS font-size
When setting `font-family: monospace;` in CSS, we have to interpret
the keyword font sizes (small, medium, large, etc) as slightly smaller
for historical reasons. Normally the medium font size is 16px, but
for monospace it's 13px.
The way this needs to behave is extremely strange:
When encountering `font-family: monospace`, we have to go back and
replay the CSS cascade as if the medium font size had been 13px all
along. Otherwise relative values like 2em/200%/etc could have gotten
lost in the inheritance chain.
We implement this in a fairly naive way by explicitly checking for
`font-family: monospace` (note: it has to be *exactly* like that,
it can't be `font-family: monospace, Courier` or similar.)
When encountered, we simply walk the element ancestors and re-run the
cascade for the font-size property. This is clumsy and inefficient,
but it does work for the common cases.
Other browsers do more elaborate things that we should eventually care
about as well, such as user-configurable font settings, per-language
behavior, etc. For now, this is just something that allows us to handle
more WPT tests where things fall apart due to unexpected font sizes.
To learn more about the wonders of font-size, see this blog post:
https://manishearth.github.io/blog/2017/08/10/font-size-an-unexpectedly-complex-css-property/
2025-02-25 11:47:03 +01:00
|
|
|
|
2024-01-11 14:04:18 +01:00
|
|
|
void set_viewport_rect(Badge<DOM::Document>, CSSPixelRect const& viewport_rect) { m_viewport_rect = viewport_rect; }
|
|
|
|
|
|
2025-09-09 13:49:34 +01:00
|
|
|
void collect_animation_into(DOM::AbstractElement, GC::Ref<Animations::KeyframeEffect> animation, ComputedProperties&) const;
|
2024-03-16 07:44:48 +01:00
|
|
|
|
2024-09-29 23:38:49 +02:00
|
|
|
size_t number_of_css_font_faces_with_loading_in_progress() const;
|
|
|
|
|
|
2025-09-09 12:44:00 +01:00
|
|
|
[[nodiscard]] GC::Ref<ComputedProperties> compute_properties(DOM::AbstractElement, CascadedProperties&) const;
|
LibWeb: Split StyleComputer work into two phases with separate outputs
Before this change, StyleComputer would essentially take a DOM element,
find all the CSS rules that apply to it, and resolve the computed value
for each CSS property for that element.
This worked great, but it meant we had to do all the work of selector
matching and cascading every time.
To enable new optimizations, this change introduces a break in the
middle of this process where we've produced a "CascadedProperties".
This object contains the result of the cascade, before we've begun
turning cascaded values into computed values.
The cascaded properties are now stored with each element, which will
later allow us to do partial updates without re-running the full
StyleComputer machine. This will be particularly valuable for
re-implementing CSS inheritance, which is extremely heavy today.
Note that CSS animations and CSS transitions operate entirely on the
computed values, even though the cascade order would have you believe
they happen earlier. I'm not confident we have the right architecture
for this, but that's a separate issue.
2024-12-12 10:06:29 +01:00
|
|
|
|
2025-09-30 16:45:37 +13:00
|
|
|
void compute_property_values(ComputedProperties&, Optional<DOM::AbstractElement>) const;
|
2025-09-09 11:55:45 +01:00
|
|
|
void compute_font(ComputedProperties&, Optional<DOM::AbstractElement>) const;
|
2025-10-26 18:44:31 +13:00
|
|
|
void process_animation_definitions(ComputedProperties const& computed_properties, DOM::AbstractElement& abstract_element) const;
|
2024-12-22 11:59:58 +01:00
|
|
|
|
2025-02-19 20:14:00 +01:00
|
|
|
[[nodiscard]] inline bool should_reject_with_ancestor_filter(Selector const&) const;
|
2025-01-28 03:04:04 +01:00
|
|
|
|
2025-08-08 10:11:51 +01:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_value_of_custom_property(DOM::AbstractElement, FlyString const& custom_property, Optional<Parser::GuardedSubstitutionContexts&> = {});
|
LibWeb: Produce computed values for custom properties
Custom properties are required to produce a computed value just like
regular properties. The computed value is defined in the spec as
"specified value with variables substituted, or the guaranteed-invalid
value", though in reality all arbitrary substitution functions should be
substituted, not just `var()`.
To support this, we parse the CSS-wide keywords normally in custom
properties, instead of ignoring them. We don't yet handle all of them
properly, and because that will require us to cascade them like regular
properties. This is just enough to prevent regressions when implementing
ASFs.
Our output in this new test is not quite correct, because of the awkward
way we handle whitespace in property values - so it has 3 spaces in the
middle instead of 1, until that's fixed.
It's possible this computed-value production should go in
cascade_custom_properties(), but I had issues with that. Hopefully once
we start cascading custom properties properly, it'll be clearer how
this should all work.
2025-06-26 16:48:33 +01:00
|
|
|
|
2025-10-22 00:48:32 +13:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_value_of_property(PropertyID, NonnullRefPtr<StyleValue const> const& specified_value, Function<NonnullRefPtr<StyleValue const>(PropertyID)> const& get_property_specified_value, ComputationContext const&, double device_pixels_per_css_pixel);
|
2025-10-01 00:40:23 +13:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_animation_name(NonnullRefPtr<StyleValue const> const& absolutized_value);
|
2025-10-01 01:36:05 +13:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_border_or_outline_width(NonnullRefPtr<StyleValue const> const& absolutized_value, NonnullRefPtr<StyleValue const> const& style_specified_value, double device_pixels_per_css_pixel);
|
2025-10-09 01:20:48 +13:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_corner_shape(NonnullRefPtr<StyleValue const> const& absolutized_value);
|
2025-10-22 00:48:32 +13:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_font_size(NonnullRefPtr<StyleValue const> const& specified_value, int computed_math_depth, CSSPixels inherited_font_size, int inherited_math_depth, ComputationContext const&);
|
|
|
|
|
static NonnullRefPtr<StyleValue const> compute_font_style(NonnullRefPtr<StyleValue const> const& specified_value, ComputationContext const&);
|
|
|
|
|
static NonnullRefPtr<StyleValue const> compute_font_weight(NonnullRefPtr<StyleValue const> const& specified_value, double inherited_font_weight, ComputationContext const&);
|
|
|
|
|
static NonnullRefPtr<StyleValue const> compute_font_width(NonnullRefPtr<StyleValue const> const& specified_value, ComputationContext const&);
|
2025-10-28 11:05:31 +01:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_font_variation_settings(NonnullRefPtr<StyleValue const> const& specified_value, ComputationContext const&);
|
2025-10-22 00:48:32 +13:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_line_height(NonnullRefPtr<StyleValue const> const& specified_value, ComputationContext const&);
|
2025-10-01 00:40:23 +13:00
|
|
|
static NonnullRefPtr<StyleValue const> compute_opacity(NonnullRefPtr<StyleValue const> const& absolutized_value);
|
|
|
|
|
static NonnullRefPtr<StyleValue const> compute_position_area(NonnullRefPtr<StyleValue const> const& absolutized_value);
|
2025-08-26 14:48:47 +12:00
|
|
|
|
2019-06-27 17:47:59 +02:00
|
|
|
private:
|
2025-07-30 14:38:41 +02:00
|
|
|
virtual void visit_edges(Visitor&) override;
|
|
|
|
|
|
2023-03-14 16:36:20 +01:00
|
|
|
enum class ComputeStyleMode {
|
|
|
|
|
Normal,
|
|
|
|
|
CreatePseudoElementStyleIfNeeded,
|
|
|
|
|
};
|
|
|
|
|
|
2023-08-17 18:45:06 +02:00
|
|
|
struct MatchingFontCandidate;
|
2023-05-29 02:16:16 +00:00
|
|
|
|
2025-07-20 15:00:32 +02:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
LibWeb: Add StyleScope to keep style caches per Document/ShadowRoot
Before this change, we've been maintaining various StyleComputer caches
at the document level.
This made sense for old-school documents without shadow trees, since
all the style information was document-wide anyway. However, documents
with many shadow trees ended up suffering since any time you mutated
a style sheet inside a shadow tree, *all* style caches for the entire
document would get invalidated.
This was particularly expensive on Reddit, which has tons of shadow
trees with their own style elements. Every time we'd create one of their
custom elements, we'd invalidate the document-level "rule cache" and
have to rebuild it, taking about ~60ms each time (ouch).
This commit introduces a new object called StyleScope.
Every Document and ShadowRoot has its own StyleScope. Rule caches etc
are moved from StyleComputer to StyleScope.
Rule cache invalidation now happens at StyleScope level. As an example,
rule cache rebuilds now take ~1ms on Reddit instead of ~60ms.
This is largely a mechanical change, moving things around, but there's
one key detail to be aware of: due to the :host selector, which works
across the shadow DOM boundary and reaches from inside a shadow tree out
into the light tree, there are various places where we have to check
both the shadow tree's StyleScope *and* the document-level StyleScope
in order to get all rules that may apply.
2025-11-13 19:08:08 +01:00
|
|
|
[[nodiscard]] MatchingRuleSet build_matching_rule_set(DOM::AbstractElement, PseudoClassBitmap& attempted_pseudo_class_matches, bool& did_match_any_pseudo_element_rules, ComputeStyleMode, StyleScope const&) const;
|
2025-07-20 15:00:32 +02:00
|
|
|
|
2025-09-09 14:40:48 +01:00
|
|
|
LogicalAliasMappingContext compute_logical_alias_mapping_context(DOM::AbstractElement, ComputeStyleMode, MatchingRuleSet const&) const;
|
LibWeb: Add StyleScope to keep style caches per Document/ShadowRoot
Before this change, we've been maintaining various StyleComputer caches
at the document level.
This made sense for old-school documents without shadow trees, since
all the style information was document-wide anyway. However, documents
with many shadow trees ended up suffering since any time you mutated
a style sheet inside a shadow tree, *all* style caches for the entire
document would get invalidated.
This was particularly expensive on Reddit, which has tons of shadow
trees with their own style elements. Every time we'd create one of their
custom elements, we'd invalidate the document-level "rule cache" and
have to rebuild it, taking about ~60ms each time (ouch).
This commit introduces a new object called StyleScope.
Every Document and ShadowRoot has its own StyleScope. Rule caches etc
are moved from StyleComputer to StyleScope.
Rule cache invalidation now happens at StyleScope level. As an example,
rule cache rebuilds now take ~1ms on Reddit instead of ~60ms.
This is largely a mechanical change, moving things around, but there's
one key detail to be aware of: due to the :host selector, which works
across the shadow DOM boundary and reaches from inside a shadow tree out
into the light tree, there are various places where we have to check
both the shadow tree's StyleScope *and* the document-level StyleScope
in order to get all rules that may apply.
2025-11-13 19:08:08 +01:00
|
|
|
[[nodiscard]] GC::Ptr<ComputedProperties> compute_style_impl(DOM::AbstractElement, ComputeStyleMode, Optional<bool&> did_change_custom_properties, StyleScope const&) const;
|
2025-09-09 14:21:12 +01:00
|
|
|
[[nodiscard]] GC::Ref<CascadedProperties> compute_cascaded_values(DOM::AbstractElement, bool did_match_any_pseudo_element_rules, ComputeStyleMode, MatchingRuleSet const&, Optional<LogicalAliasMappingContext>, ReadonlySpan<PropertyID> properties_to_cascade) const;
|
2025-10-14 16:21:00 +02:00
|
|
|
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_ascending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive);
|
|
|
|
|
static RefPtr<Gfx::FontCascadeList const> find_matching_font_weight_descending(Vector<MatchingFontCandidate> const& candidates, int target_weight, float font_size_in_pt, Gfx::FontVariationSettings const& variations, bool inclusive);
|
2024-10-26 23:35:58 +02:00
|
|
|
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
|
2025-11-04 23:25:15 +00:00
|
|
|
RefPtr<Gfx::FontCascadeList const> font_matching_algorithm_impl(FlyString const& family_name, int weight, int slope, float font_size_in_pt) const;
|
LibWeb: Produce computed values for custom properties
Custom properties are required to produce a computed value just like
regular properties. The computed value is defined in the spec as
"specified value with variables substituted, or the guaranteed-invalid
value", though in reality all arbitrary substitution functions should be
substituted, not just `var()`.
To support this, we parse the CSS-wide keywords normally in custom
properties, instead of ignoring them. We don't yet handle all of them
properly, and because that will require us to cascade them like regular
properties. This is just enough to prevent regressions when implementing
ASFs.
Our output in this new test is not quite correct, because of the awkward
way we handle whitespace in property values - so it has 3 spaces in the
middle instead of 1, until that's fixed.
It's possible this computed-value production should go in
cascade_custom_properties(), but I had issues with that. Hopefully once
we start cascading custom properties properly, it'll be clearer how
this should all work.
2025-06-26 16:48:33 +01:00
|
|
|
void compute_custom_properties(ComputedProperties&, DOM::AbstractElement) const;
|
2025-08-21 17:12:56 +12:00
|
|
|
void compute_math_depth(ComputedProperties&, Optional<DOM::AbstractElement>) const;
|
2025-09-09 14:13:57 +01:00
|
|
|
void start_needed_transitions(ComputedProperties const& old_style, ComputedProperties& new_style, DOM::AbstractElement) const;
|
2024-12-20 11:32:17 +01:00
|
|
|
void resolve_effective_overflow_values(ComputedProperties&) const;
|
2025-09-09 14:34:26 +01:00
|
|
|
void transform_box_type_if_needed(ComputedProperties&, DOM::AbstractElement) const;
|
2021-09-21 11:38:18 +02:00
|
|
|
|
2024-01-11 14:04:18 +01:00
|
|
|
[[nodiscard]] CSSPixelRect viewport_rect() const { return m_viewport_rect; }
|
|
|
|
|
|
2024-12-20 11:32:17 +01:00
|
|
|
[[nodiscard]] Length::FontMetrics calculate_root_element_font_metrics(ComputedProperties const&) const;
|
2022-03-19 18:08:52 +01:00
|
|
|
|
LibWeb: Split StyleComputer work into two phases with separate outputs
Before this change, StyleComputer would essentially take a DOM element,
find all the CSS rules that apply to it, and resolve the computed value
for each CSS property for that element.
This worked great, but it meant we had to do all the work of selector
matching and cascading every time.
To enable new optimizations, this change introduces a break in the
middle of this process where we've produced a "CascadedProperties".
This object contains the result of the cascade, before we've begun
turning cascaded values into computed values.
The cascaded properties are now stored with each element, which will
later allow us to do partial updates without re-running the full
StyleComputer machine. This will be particularly valuable for
re-implementing CSS inheritance, which is extremely heavy today.
Note that CSS animations and CSS transitions operate entirely on the
computed values, even though the cascade order would have you believe
they happen earlier. I'm not confident we have the right architecture
for this, but that's a separate issue.
2024-12-12 10:06:29 +01:00
|
|
|
void cascade_declarations(
|
|
|
|
|
CascadedProperties&,
|
2025-09-09 11:34:09 +01:00
|
|
|
DOM::AbstractElement,
|
2025-01-24 15:47:42 +01:00
|
|
|
Vector<MatchingRule const*> const&,
|
LibWeb: Split StyleComputer work into two phases with separate outputs
Before this change, StyleComputer would essentially take a DOM element,
find all the CSS rules that apply to it, and resolve the computed value
for each CSS property for that element.
This worked great, but it meant we had to do all the work of selector
matching and cascading every time.
To enable new optimizations, this change introduces a break in the
middle of this process where we've produced a "CascadedProperties".
This object contains the result of the cascade, before we've begun
turning cascaded values into computed values.
The cascaded properties are now stored with each element, which will
later allow us to do partial updates without re-running the full
StyleComputer machine. This will be particularly valuable for
re-implementing CSS inheritance, which is extremely heavy today.
Note that CSS animations and CSS transitions operate entirely on the
computed values, even though the cascade order would have you believe
they happen earlier. I'm not confident we have the right architecture
for this, but that's a separate issue.
2024-12-12 10:06:29 +01:00
|
|
|
CascadeOrigin,
|
|
|
|
|
Important,
|
2025-06-18 17:45:26 +12:00
|
|
|
Optional<FlyString> layer_name,
|
2025-07-21 14:29:12 +02:00
|
|
|
Optional<LogicalAliasMappingContext>,
|
|
|
|
|
ReadonlySpan<PropertyID> properties_to_cascade) const;
|
2019-10-05 09:01:12 +02:00
|
|
|
|
2024-11-15 04:01:23 +13:00
|
|
|
GC::Ref<DOM::Document> m_document;
|
2022-02-10 17:49:50 +01:00
|
|
|
|
2025-07-21 12:59:00 +02:00
|
|
|
[[nodiscard]] RuleCache const* rule_cache_for_cascade_origin(CascadeOrigin, Optional<FlyString const> qualified_layer_name, GC::Ptr<DOM::ShadowRoot const>) const;
|
2023-03-07 20:13:13 +01:00
|
|
|
|
2025-07-30 14:38:41 +02:00
|
|
|
using FontLoaderList = Vector<GC::Ref<FontLoader>>;
|
2024-10-26 23:35:58 +02:00
|
|
|
HashMap<OwnFontFaceKey, FontLoaderList> m_loaded_fonts;
|
2023-05-08 10:28:21 +02:00
|
|
|
|
|
|
|
|
Length::FontMetrics m_default_font_metrics;
|
|
|
|
|
Length::FontMetrics m_root_element_font_metrics;
|
2023-05-26 23:30:54 +03:30
|
|
|
|
2024-01-11 14:04:18 +01: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 13:50:33 +01:00
|
|
|
|
2025-07-30 14:38:41 +02:00
|
|
|
OwnPtr<CountingBloomFilter<u8, 14>> m_ancestor_filter;
|
2025-11-04 23:25:15 +00:00
|
|
|
|
|
|
|
|
mutable HashMap<FontMatchingAlgorithmCacheKey, RefPtr<Gfx::FontCascadeList const>> m_font_matching_algorithm_cache;
|
2019-06-27 17:47:59 +02:00
|
|
|
};
|
2020-03-07 10:27:02 +01:00
|
|
|
|
2025-07-30 14:38:41 +02:00
|
|
|
class FontLoader final : public GC::Cell {
|
|
|
|
|
GC_CELL(FontLoader, GC::Cell);
|
|
|
|
|
GC_DECLARE_ALLOCATOR(FontLoader);
|
|
|
|
|
|
2024-05-15 14:58:24 -06:00
|
|
|
public:
|
2025-05-02 12:07:22 +01:00
|
|
|
FontLoader(StyleComputer& style_computer, GC::Ptr<CSSStyleSheet> parent_style_sheet, FlyString family_name, Vector<Gfx::UnicodeRange> unicode_ranges, Vector<URL> urls, ESCAPING Function<void(RefPtr<Gfx::Typeface const>)> on_load = {});
|
2024-05-15 14:58:24 -06:00
|
|
|
|
2025-05-01 16:16:57 +01:00
|
|
|
virtual ~FontLoader();
|
2024-05-15 14:58:24 -06:00
|
|
|
|
|
|
|
|
Vector<Gfx::UnicodeRange> const& unicode_ranges() const { return m_unicode_ranges; }
|
2025-04-15 15:48:14 -06:00
|
|
|
RefPtr<Gfx::Typeface const> vector_font() const { return m_vector_font; }
|
2024-05-15 14:58:24 -06:00
|
|
|
|
2025-10-14 16:21:00 +02:00
|
|
|
RefPtr<Gfx::Font const> font_with_point_size(float point_size, Gfx::FontVariationSettings const& variations = {});
|
2024-05-15 14:58:24 -06:00
|
|
|
void start_loading_next_url();
|
|
|
|
|
|
2025-05-01 16:16:57 +01:00
|
|
|
bool is_loading() const;
|
2024-09-29 23:38:49 +02:00
|
|
|
|
2024-05-15 14:58:24 -06:00
|
|
|
private:
|
2025-07-30 14:38:41 +02:00
|
|
|
virtual void visit_edges(Visitor&) override;
|
|
|
|
|
|
2025-05-01 16:16:57 +01:00
|
|
|
ErrorOr<NonnullRefPtr<Gfx::Typeface const>> try_load_font(Fetch::Infrastructure::Response const&, ByteBuffer const&);
|
2024-09-21 18:35:31 +02:00
|
|
|
|
2025-05-01 16:16:57 +01:00
|
|
|
void font_did_load_or_fail(RefPtr<Gfx::Typeface const>);
|
2024-05-15 14:58:24 -06:00
|
|
|
|
2025-07-30 14:38:41 +02:00
|
|
|
GC::Ref<StyleComputer> m_style_computer;
|
2025-05-02 12:07:22 +01:00
|
|
|
GC::Ptr<CSSStyleSheet> m_parent_style_sheet;
|
2024-05-15 14:58:24 -06:00
|
|
|
FlyString m_family_name;
|
|
|
|
|
Vector<Gfx::UnicodeRange> m_unicode_ranges;
|
2025-04-15 15:48:14 -06:00
|
|
|
RefPtr<Gfx::Typeface const> m_vector_font;
|
2025-05-02 12:07:22 +01:00
|
|
|
Vector<URL> m_urls;
|
2025-07-30 14:38:41 +02:00
|
|
|
GC::Ptr<Fetch::Infrastructure::FetchController> m_fetch_controller;
|
2025-05-01 16:16:57 +01:00
|
|
|
Function<void(RefPtr<Gfx::Typeface const>)> m_on_load;
|
2024-05-15 14:58:24 -06:00
|
|
|
};
|
|
|
|
|
|
2025-02-19 20:14:00 +01:00
|
|
|
inline bool StyleComputer::should_reject_with_ancestor_filter(Selector const& selector) const
|
|
|
|
|
{
|
|
|
|
|
for (u32 hash : selector.ancestor_hashes()) {
|
|
|
|
|
if (hash == 0)
|
|
|
|
|
break;
|
2025-07-30 14:38:41 +02:00
|
|
|
if (!m_ancestor_filter->may_contain(hash))
|
2025-02-19 20:14:00 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-07 10:27:02 +01:00
|
|
|
}
|