2020-01-18 09:38:21 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2018 - 2023 , Andreas Kling < andreas @ ladybird . org >
2021-02-21 13:45:26 +02:00
* Copyright ( c ) 2021 , the SerenityOS developers .
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
* Copyright ( c ) 2021 - 2025 , Sam Atkins < sam @ ladybird . org >
2024-02-22 13:56:15 +00:00
* Copyright ( c ) 2024 , Matthew Olsson < mattco @ serenityos . 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
*/
2023-07-06 16:56:25 +03:30
# include <AK/BinarySearch.h>
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
# include <AK/Bitmap.h>
2022-02-10 17:49:50 +01:00
# include <AK/Debug.h>
2022-12-06 03:08:20 -03:00
# include <AK/Error.h>
2023-05-29 02:16:16 +00:00
# include <AK/Find.h>
# include <AK/Function.h>
2022-11-30 16:17:57 +01:00
# include <AK/HashMap.h>
2024-02-22 13:56:15 +00:00
# include <AK/Math.h>
2024-10-26 23:35:58 +02:00
# include <AK/NonnullRawPtr.h>
2020-06-10 16:41:30 +02:00
# include <AK/QuickSort.h>
2022-04-09 09:28:38 +02:00
# include <LibGfx/Font/Font.h>
# include <LibGfx/Font/FontDatabase.h>
# include <LibGfx/Font/FontStyleMapping.h>
2024-06-28 20:27:00 +02:00
# include <LibGfx/Font/Typeface.h>
2024-07-22 14:03:58 +03:00
# include <LibGfx/Font/WOFF/Loader.h>
# include <LibGfx/Font/WOFF2/Loader.h>
2024-02-22 13:56:15 +00:00
# include <LibWeb/Animations/AnimationEffect.h>
# include <LibWeb/Animations/DocumentTimeline.h>
2025-01-15 15:06:39 -07:00
# include <LibWeb/Bindings/PrincipalHostDefined.h>
2024-02-22 13:56:15 +00:00
# include <LibWeb/CSS/AnimationEvent.h>
# include <LibWeb/CSS/CSSAnimation.h>
2022-03-29 02:14:20 +02:00
# include <LibWeb/CSS/CSSFontFaceRule.h>
2023-03-12 16:04:12 +01:00
# include <LibWeb/CSS/CSSImportRule.h>
2024-09-04 17:43:18 +01:00
# include <LibWeb/CSS/CSSLayerBlockRule.h>
# include <LibWeb/CSS/CSSLayerStatementRule.h>
2024-10-17 13:48:00 +01:00
# include <LibWeb/CSS/CSSNestedDeclarations.h>
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
# include <LibWeb/CSS/CSSStyleProperties.h>
2021-03-07 15:00:02 +01:00
# include <LibWeb/CSS/CSSStyleRule.h>
2024-09-19 14:13:20 +01:00
# include <LibWeb/CSS/CSSTransition.h>
2025-02-18 09:19:56 +01:00
# include <LibWeb/CSS/ComputedProperties.h>
2025-05-01 16:16:57 +01:00
# include <LibWeb/CSS/Fetch.h>
2024-09-19 12:58:38 +01:00
# include <LibWeb/CSS/Interpolation.h>
2025-01-12 18:38:05 +03:00
# include <LibWeb/CSS/InvalidationSet.h>
2025-06-19 17:03:26 +01:00
# include <LibWeb/CSS/Parser/ArbitrarySubstitutionFunctions.h>
2021-07-14 19:57:02 +01:00
# include <LibWeb/CSS/Parser/Parser.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/CSS/SelectorEngine.h>
2021-09-24 13:49:57 +02:00
# include <LibWeb/CSS/StyleComputer.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/CSS/StyleSheet.h>
2023-05-26 23:16:43 +03:30
# include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
2023-03-25 00:33:20 +00:00
# include <LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h>
2024-08-14 16:37:02 +01:00
# include <LibWeb/CSS/StyleValues/CSSColorValue.h>
2024-08-14 11:46:56 +01:00
# include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
2023-09-16 13:26:42 +02:00
# include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
2023-04-26 21:05:38 +02:00
# include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
2023-07-06 16:56:25 +03:30
# include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
2023-03-23 21:26:03 +00:00
# include <LibWeb/CSS/StyleValues/FilterValueListStyleValue.h>
2024-05-25 13:24:18 -07:00
# include <LibWeb/CSS/StyleValues/FrequencyStyleValue.h>
2023-03-24 14:53:08 +00:00
# include <LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h>
2023-04-29 19:32:56 +02:00
# include <LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h>
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
# include <LibWeb/CSS/StyleValues/GuaranteedInvalidStyleValue.h>
2023-06-01 17:10:28 +01:00
# include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
2023-03-24 17:04:04 +00:00
# include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
2023-09-07 15:29:54 +01:00
# include <LibWeb/CSS/StyleValues/MathDepthStyleValue.h>
2023-06-01 16:16:15 +01:00
# include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
# include <LibWeb/CSS/StyleValues/PendingSubstitutionStyleValue.h>
2023-03-24 17:28:43 +00:00
# include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
2023-04-03 00:04:00 +01:00
# include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
2024-03-02 20:09:33 -07:00
# include <LibWeb/CSS/StyleValues/RatioStyleValue.h>
2023-05-26 23:16:43 +03:30
# include <LibWeb/CSS/StyleValues/RectStyleValue.h>
2023-09-19 12:21:15 +01:00
# include <LibWeb/CSS/StyleValues/ShorthandStyleValue.h>
2023-09-12 11:33:11 +01:00
# include <LibWeb/CSS/StyleValues/StringStyleValue.h>
2023-03-25 00:12:21 +00:00
# include <LibWeb/CSS/StyleValues/StyleValueList.h>
2023-05-26 23:16:43 +03:30
# include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
# include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
2024-02-19 19:17:39 -07:00
# include <LibWeb/CSS/StyleValues/TransitionStyleValue.h>
2023-03-24 23:53:41 +00:00
# include <LibWeb/CSS/StyleValues/UnresolvedStyleValue.h>
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
# include <LibWeb/DOM/Attr.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Document.h>
# include <LibWeb/DOM/Element.h>
2023-03-19 17:01:26 +01:00
# include <LibWeb/DOM/ShadowRoot.h>
2025-05-01 16:16:57 +01:00
# include <LibWeb/Fetch/Infrastructure/FetchController.h>
# include <LibWeb/Fetch/Response.h>
2023-09-01 09:55:56 +02:00
# include <LibWeb/HTML/HTMLBRElement.h>
2022-03-16 12:30:06 +01:00
# include <LibWeb/HTML/HTMLHtmlElement.h>
2024-04-10 21:44:11 -04:00
# include <LibWeb/HTML/Parser/HTMLParser.h>
2024-02-22 13:56:15 +00:00
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
2023-05-08 07:51:03 +02:00
# include <LibWeb/Layout/Node.h>
2024-09-04 17:20:19 +02:00
# include <LibWeb/MimeSniff/MimeType.h>
# include <LibWeb/MimeSniff/Resource.h>
2023-09-07 16:03:20 +01:00
# include <LibWeb/Namespace.h>
2024-02-28 18:51:07 -07:00
# include <LibWeb/Painting/PaintableBox.h>
2022-09-08 11:55:12 +02:00
# include <LibWeb/Platform/FontPlugin.h>
2024-02-22 13:56:15 +00:00
# include <math.h>
2019-06-27 20:40:21 +02:00
# include <stdio.h>
2019-06-27 17:47:59 +02:00
2024-10-26 23:35:58 +02:00
namespace Web : : CSS {
struct FontFaceKey {
NonnullRawPtr < FlyString const > family_name ;
int weight { 0 } ;
int slope { 0 } ;
} ;
}
2023-05-24 15:35:30 +02:00
namespace AK {
2024-10-26 23:35:58 +02:00
namespace Detail {
2025-05-13 07:06:33 -04:00
2024-10-26 23:35:58 +02:00
template < >
inline constexpr bool IsHashCompatible < Web : : CSS : : FontFaceKey , Web : : CSS : : OwnFontFaceKey > = true ;
template < >
inline constexpr bool IsHashCompatible < Web : : CSS : : OwnFontFaceKey , Web : : CSS : : FontFaceKey > = true ;
2025-05-13 07:06:33 -04:00
2024-10-26 23:35:58 +02:00
}
2023-05-24 15:35:30 +02:00
template < >
2023-11-08 20:29:12 +01:00
struct Traits < Web : : CSS : : FontFaceKey > : public DefaultTraits < Web : : CSS : : FontFaceKey > {
2024-10-26 23:35:58 +02:00
static unsigned hash ( Web : : CSS : : FontFaceKey const & key ) { return pair_int_hash ( key . family_name - > hash ( ) , pair_int_hash ( key . weight , key . slope ) ) ; }
} ;
template < >
struct Traits < Web : : CSS : : OwnFontFaceKey > : public DefaultTraits < Web : : CSS : : OwnFontFaceKey > {
static unsigned hash ( Web : : CSS : : OwnFontFaceKey const & key ) { return pair_int_hash ( key . family_name . hash ( ) , pair_int_hash ( key . weight , key . slope ) ) ; }
2023-05-24 15:35:30 +02:00
} ;
}
2020-07-26 20:01:35 +02:00
namespace Web : : CSS {
2020-03-07 10:27:02 +01:00
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
CSSStyleProperties const & MatchingRule : : declaration ( ) const
2024-10-17 13:48:00 +01:00
{
if ( rule - > type ( ) = = CSSRule : : Type : : Style )
return static_cast < CSSStyleRule const & > ( * rule ) . declaration ( ) ;
if ( rule - > type ( ) = = CSSRule : : Type : : NestedDeclarations )
return static_cast < CSSNestedDeclarations const & > ( * rule ) . declaration ( ) ;
VERIFY_NOT_REACHED ( ) ;
}
SelectorList const & MatchingRule : : absolutized_selectors ( ) const
{
if ( rule - > type ( ) = = CSSRule : : Type : : Style )
return static_cast < CSSStyleRule const & > ( * rule ) . absolutized_selectors ( ) ;
if ( rule - > type ( ) = = CSSRule : : Type : : NestedDeclarations )
2024-11-06 17:43:30 +00:00
return static_cast < CSSNestedDeclarations const & > ( * rule ) . parent_style_rule ( ) . absolutized_selectors ( ) ;
2024-10-17 13:48:00 +01:00
VERIFY_NOT_REACHED ( ) ;
}
FlyString const & MatchingRule : : qualified_layer_name ( ) const
{
if ( rule - > type ( ) = = CSSRule : : Type : : Style )
return static_cast < CSSStyleRule const & > ( * rule ) . qualified_layer_name ( ) ;
if ( rule - > type ( ) = = CSSRule : : Type : : NestedDeclarations )
2024-11-06 17:43:30 +00:00
return static_cast < CSSNestedDeclarations const & > ( * rule ) . parent_style_rule ( ) . qualified_layer_name ( ) ;
2024-10-17 13:48:00 +01:00
VERIFY_NOT_REACHED ( ) ;
}
2024-10-26 23:35:58 +02:00
OwnFontFaceKey : : OwnFontFaceKey ( FontFaceKey const & other )
: family_name ( other . family_name )
, weight ( other . weight )
, slope ( other . slope )
{
}
OwnFontFaceKey : : operator FontFaceKey ( ) const
{
return FontFaceKey {
family_name ,
weight ,
slope
} ;
}
[[nodiscard]] bool OwnFontFaceKey : : operator = = ( FontFaceKey const & other ) const
{
return family_name = = other . family_name
& & weight = = other . weight
& & slope = = other . slope ;
}
2025-03-20 16:56:46 +00:00
static DOM : : Element const * element_to_inherit_style_from ( DOM : : Element const * , Optional < CSS : : PseudoElement > ) ;
2023-07-29 10:53:24 +02:00
2021-09-24 13:49:57 +02:00
StyleComputer : : StyleComputer ( DOM : : Document & document )
2019-06-27 17:47:59 +02:00
: m_document ( document )
2025-01-02 03:56:05 +03:00
, m_default_font_metrics ( 16 , Platform : : FontPlugin : : the ( ) . default_font ( 16 ) - > pixel_metrics ( ) )
2023-05-08 10:28:21 +02:00
, m_root_element_font_metrics ( m_default_font_metrics )
2019-06-27 17:47:59 +02:00
{
2024-09-04 17:43:18 +01:00
m_qualified_layer_names_in_order . append ( { } ) ;
2019-06-27 17:47:59 +02:00
}
2022-03-29 02:14:20 +02:00
StyleComputer : : ~ StyleComputer ( ) = default ;
2025-05-02 12:07:22 +01:00
FontLoader : : FontLoader ( StyleComputer & style_computer , GC : : Ptr < CSSStyleSheet > parent_style_sheet , FlyString family_name , Vector < Gfx : : UnicodeRange > unicode_ranges , Vector < URL > urls , Function < void ( RefPtr < Gfx : : Typeface const > ) > on_load )
2024-05-15 14:58:24 -06:00
: m_style_computer ( style_computer )
2025-05-02 12:07:22 +01:00
, m_parent_style_sheet ( parent_style_sheet )
2024-05-15 14:58:24 -06:00
, m_family_name ( move ( family_name ) )
, m_unicode_ranges ( move ( unicode_ranges ) )
, m_urls ( move ( urls ) )
, m_on_load ( move ( on_load ) )
{
}
2022-03-29 02:14:20 +02:00
2024-05-15 14:58:24 -06:00
FontLoader : : ~ FontLoader ( ) = default ;
2023-12-09 23:42:02 +01:00
2025-05-01 16:16:57 +01:00
bool FontLoader : : is_loading ( ) const
2024-05-15 14:58:24 -06:00
{
2025-05-01 16:16:57 +01:00
return m_fetch_controller & & ! m_vector_font ;
2024-09-21 18:35:31 +02:00
}
2025-04-15 15:48:14 -06:00
RefPtr < Gfx : : Font const > FontLoader : : font_with_point_size ( float point_size )
2024-05-15 14:58:24 -06:00
{
if ( ! m_vector_font ) {
2025-05-01 16:16:57 +01:00
if ( ! m_fetch_controller )
2024-12-20 14:54:08 +00:00
start_loading_next_url ( ) ;
2024-05-15 14:58:24 -06:00
return nullptr ;
2022-03-29 02:14:20 +02:00
}
2025-04-19 19:10:01 +02:00
return m_vector_font - > font ( point_size ) ;
2024-05-15 14:58:24 -06:00
}
2022-03-29 02:14:20 +02:00
2024-05-15 14:58:24 -06:00
void FontLoader : : start_loading_next_url ( )
{
2025-05-01 16:16:57 +01:00
// FIXME: Load local() fonts somehow.
if ( m_fetch_controller & & m_fetch_controller - > state ( ) = = Fetch : : Infrastructure : : FetchController : : State : : Ongoing )
2024-05-15 14:58:24 -06:00
return ;
if ( m_urls . is_empty ( ) )
return ;
2025-01-15 15:06:39 -07:00
2025-05-01 16:16:57 +01:00
// https://drafts.csswg.org/css-fonts-4/#fetch-a-font
// To fetch a font given a selected <url> url for @font-face rule, fetch url, with stylesheet being rule’ s parent
// CSS style sheet, destination "font", CORS mode "cors", and processResponse being the following steps given
// response res and null, failure or a byte stream stream:
2025-05-02 12:07:22 +01:00
auto style_sheet_or_document = m_parent_style_sheet ? StyleSheetOrDocument { * m_parent_style_sheet } : StyleSheetOrDocument { m_style_computer . document ( ) } ;
auto maybe_fetch_controller = fetch_a_style_resource ( m_urls . take_first ( ) , style_sheet_or_document , Fetch : : Infrastructure : : Request : : Destination : : Font , CorsMode : : Cors ,
2025-05-01 16:16:57 +01:00
[ weak_loader = make_weak_ptr ( ) ] ( auto response , auto stream ) {
// NB: If the FontLoader died before this fetch completed, nobody wants the data.
if ( weak_loader . is_null ( ) )
return ;
auto & loader = * weak_loader ;
// 1. If stream is null, return.
// 2. Load a font from stream according to its type.
// NB: We need to fetch the next source if this one fails to fetch OR decode. So, first try to decode it.
RefPtr < Gfx : : Typeface const > typeface ;
if ( auto * bytes = stream . template get_pointer < ByteBuffer > ( ) ) {
if ( auto maybe_typeface = loader . try_load_font ( response , * bytes ) ; ! maybe_typeface . is_error ( ) )
typeface = maybe_typeface . release_value ( ) ;
}
if ( ! typeface ) {
// NB: If we have other sources available, try the next one.
if ( loader . m_urls . is_empty ( ) ) {
loader . font_did_load_or_fail ( nullptr ) ;
} else {
loader . m_fetch_controller = nullptr ;
loader . start_loading_next_url ( ) ;
}
} else {
loader . font_did_load_or_fail ( move ( typeface ) ) ;
}
} ) ;
2023-08-25 10:22:30 +02:00
2025-05-01 16:16:57 +01:00
if ( maybe_fetch_controller . is_error ( ) ) {
font_did_load_or_fail ( nullptr ) ;
} else {
m_fetch_controller = maybe_fetch_controller . release_value ( ) ;
}
}
2023-08-25 10:22:30 +02:00
2025-05-01 16:16:57 +01:00
void FontLoader : : font_did_load_or_fail ( RefPtr < Gfx : : Typeface const > typeface )
{
if ( typeface ) {
m_vector_font = typeface . release_nonnull ( ) ;
m_style_computer . did_load_font ( m_family_name ) ;
if ( m_on_load )
m_on_load ( m_vector_font ) ;
} else {
if ( m_on_load )
m_on_load ( nullptr ) ;
}
m_fetch_controller = nullptr ;
2024-05-15 14:58:24 -06:00
}
LibWeb: Load alternative font urls if others fail
We don't support all parts of the font formats we assume as "supported"
in the CSS parser. For example, if an open type font has a CFF table, we
reject loading it. This meant that until now, when such an
unsupported-supported font url was first in the list of urls, we
couldn't load it at all, even when we would support a later url.
To resolve that, try loading all font urls one after each other, in case
we are not able to load the higher priority one.
This also resolves a FIXME related to spec compliant url prioritization.
Our CSS parser already filters and prioritizes font src urls in
compliance with the spec. However, we still had to resort to brittle
file extension matching, because some websites don't set the `format`
and if the first url in a src list happened to be one we don't support,
the font could not be loaded at all. This now is unnecessary because we
can try and discard the urls instead.
2023-03-18 12:44:10 +01:00
2025-05-01 16:16:57 +01:00
ErrorOr < NonnullRefPtr < Gfx : : Typeface const > > FontLoader : : try_load_font ( Fetch : : Infrastructure : : Response const & response , ByteBuffer const & bytes )
2024-05-15 14:58:24 -06:00
{
// FIXME: This could maybe use the format() provided in @font-face as well, since often the mime type is just application/octet-stream and we have to try every format
2025-05-01 16:16:57 +01:00
auto mime_type = response . header_list ( ) - > extract_mime_type ( ) ;
2024-09-04 17:20:19 +02:00
if ( ! mime_type . has_value ( ) | | ! mime_type - > is_font ( ) ) {
2025-05-01 16:16:57 +01:00
mime_type = MimeSniff : : Resource : : sniff ( bytes , MimeSniff : : SniffingConfiguration { . sniffing_context = MimeSniff : : SniffingContext : : Font } ) ;
2024-05-15 14:58:24 -06:00
}
2024-09-04 17:20:19 +02:00
if ( mime_type . has_value ( ) ) {
2024-12-05 01:18:54 +01:00
if ( mime_type - > essence ( ) = = " font/ttf " sv | | mime_type - > essence ( ) = = " application/x-font-ttf " sv | | mime_type - > essence ( ) = = " font/otf " sv ) {
2025-05-01 16:16:57 +01:00
if ( auto result = Gfx : : Typeface : : try_load_from_temporary_memory ( bytes ) ; ! result . is_error ( ) ) {
2024-09-04 17:20:19 +02:00
return result ;
}
2024-04-14 08:18:16 +02:00
}
2024-09-04 17:20:19 +02:00
if ( mime_type - > essence ( ) = = " font/woff " sv | | mime_type - > essence ( ) = = " application/font-woff " sv ) {
2025-05-01 16:16:57 +01:00
if ( auto result = WOFF : : try_load_from_bytes ( bytes ) ; ! result . is_error ( ) ) {
2024-09-04 17:20:19 +02:00
return result ;
}
}
if ( mime_type - > essence ( ) = = " font/woff2 " sv | | mime_type - > essence ( ) = = " application/font-woff2 " sv ) {
2025-05-01 16:16:57 +01:00
if ( auto result = WOFF2 : : try_load_from_bytes ( bytes ) ; ! result . is_error ( ) ) {
2024-09-04 17:20:19 +02:00
return result ;
}
2024-04-14 08:18:16 +02:00
}
2024-05-15 14:58:24 -06:00
}
2024-04-07 19:15:23 +02:00
2024-05-15 14:58:24 -06:00
return Error : : from_string_literal ( " Automatic format detection failed " ) ;
}
2022-03-29 02:14:20 +02:00
2023-08-17 18:45:06 +02:00
struct StyleComputer : : MatchingFontCandidate {
FontFaceKey key ;
2024-06-28 20:27:00 +02:00
Variant < FontLoaderList * , Gfx : : Typeface const * > loader_or_typeface ;
2023-08-17 18:45:06 +02:00
2023-12-09 23:42:02 +01:00
[[nodiscard]] RefPtr < Gfx : : FontCascadeList const > font_with_point_size ( float point_size ) const
2023-08-17 18:45:06 +02:00
{
2025-04-15 15:48:14 -06:00
auto font_list = Gfx : : FontCascadeList : : create ( ) ;
2023-12-09 23:42:02 +01:00
if ( auto * loader_list = loader_or_typeface . get_pointer < FontLoaderList * > ( ) ; loader_list ) {
for ( auto const & loader : * * loader_list ) {
if ( auto font = loader - > font_with_point_size ( point_size ) ; font )
font_list - > add ( * font , loader - > unicode_ranges ( ) ) ;
}
return font_list ;
2023-08-17 18:45:06 +02:00
}
2023-12-09 23:42:02 +01:00
2025-04-19 19:10:01 +02:00
font_list - > add ( loader_or_typeface . get < Gfx : : Typeface const * > ( ) - > font ( point_size ) ) ;
2023-12-09 23:42:02 +01:00
return font_list ;
2023-08-17 18:45:06 +02:00
}
} ;
2025-03-25 10:04:42 +00:00
static CSSStyleSheet & default_stylesheet ( )
2019-10-05 09:01:12 +02:00
{
2024-11-15 04:01:23 +13:00
static GC : : Root < CSSStyleSheet > sheet ;
2022-08-07 13:14:54 +02:00
if ( ! sheet . cell ( ) ) {
2024-08-22 12:42:12 +01:00
extern String default_stylesheet_source ;
2025-03-25 10:04:42 +00:00
sheet = GC : : make_root ( parse_css_stylesheet ( CSS : : Parser : : ParsingParams ( internal_css_realm ( ) ) , default_stylesheet_source ) ) ;
2019-10-05 09:01:12 +02:00
}
return * sheet ;
}
2025-03-25 10:04:42 +00:00
static CSSStyleSheet & quirks_mode_stylesheet ( )
2020-09-24 10:33:33 +02:00
{
2024-11-15 04:01:23 +13:00
static GC : : Root < CSSStyleSheet > sheet ;
2022-08-07 13:14:54 +02:00
if ( ! sheet . cell ( ) ) {
2024-08-22 12:42:12 +01:00
extern String quirks_mode_stylesheet_source ;
2025-03-25 10:04:42 +00:00
sheet = GC : : make_root ( parse_css_stylesheet ( CSS : : Parser : : ParsingParams ( internal_css_realm ( ) ) , quirks_mode_stylesheet_source ) ) ;
2020-09-24 10:33:33 +02:00
}
return * sheet ;
}
2025-03-25 10:04:42 +00:00
static CSSStyleSheet & mathml_stylesheet ( )
2023-07-30 17:35:10 -05:00
{
2024-11-15 04:01:23 +13:00
static GC : : Root < CSSStyleSheet > sheet ;
2023-07-30 17:35:10 -05:00
if ( ! sheet . cell ( ) ) {
2024-08-22 12:42:12 +01:00
extern String mathml_stylesheet_source ;
2025-03-25 10:04:42 +00:00
sheet = GC : : make_root ( parse_css_stylesheet ( CSS : : Parser : : ParsingParams ( internal_css_realm ( ) ) , mathml_stylesheet_source ) ) ;
2023-07-30 17:35:10 -05:00
}
return * sheet ;
}
2025-03-25 10:04:42 +00:00
static CSSStyleSheet & svg_stylesheet ( )
2023-09-23 14:23:47 +01:00
{
2024-11-15 04:01:23 +13:00
static GC : : Root < CSSStyleSheet > sheet ;
2023-09-23 14:23:47 +01:00
if ( ! sheet . cell ( ) ) {
2024-08-22 12:42:12 +01:00
extern String svg_stylesheet_source ;
2025-03-25 10:04:42 +00:00
sheet = GC : : make_root ( parse_css_stylesheet ( CSS : : Parser : : ParsingParams ( internal_css_realm ( ) ) , svg_stylesheet_source ) ) ;
2023-09-23 14:23:47 +01:00
}
return * sheet ;
}
2024-08-23 11:03:05 +01:00
Optional < String > StyleComputer : : user_agent_style_sheet_source ( StringView name )
{
extern String default_stylesheet_source ;
extern String quirks_mode_stylesheet_source ;
extern String mathml_stylesheet_source ;
extern String svg_stylesheet_source ;
if ( name = = " CSS/Default.css " sv )
return default_stylesheet_source ;
if ( name = = " CSS/QuirksMode.css " sv )
return quirks_mode_stylesheet_source ;
if ( name = = " MathML/Default.css " sv )
return mathml_stylesheet_source ;
if ( name = = " SVG/Default.css " sv )
return svg_stylesheet_source ;
return { } ;
}
2019-10-05 09:01:12 +02:00
template < typename Callback >
2021-09-24 13:49:57 +02:00
void StyleComputer : : for_each_stylesheet ( CascadeOrigin cascade_origin , Callback callback ) const
2019-10-05 09:01:12 +02:00
{
2022-02-11 12:08:27 +01:00
if ( cascade_origin = = CascadeOrigin : : UserAgent ) {
2025-03-25 10:04:42 +00:00
callback ( default_stylesheet ( ) , { } ) ;
2021-09-21 11:38:18 +02:00
if ( document ( ) . in_quirks_mode ( ) )
2025-03-25 10:04:42 +00:00
callback ( quirks_mode_stylesheet ( ) , { } ) ;
callback ( mathml_stylesheet ( ) , { } ) ;
callback ( svg_stylesheet ( ) , { } ) ;
2021-09-21 11:38:18 +02:00
}
2023-08-21 15:50:01 +01:00
if ( cascade_origin = = CascadeOrigin : : User ) {
if ( m_user_style_sheet )
2023-03-19 17:01:26 +01:00
callback ( * m_user_style_sheet , { } ) ;
2023-08-21 15:50:01 +01:00
}
2022-02-11 12:08:27 +01:00
if ( cascade_origin = = CascadeOrigin : : Author ) {
2024-10-19 15:35:41 -05:00
document ( ) . for_each_active_css_style_sheet ( [ & ] ( auto & sheet , auto shadow_root ) {
callback ( sheet , shadow_root ) ;
2024-03-07 23:36:52 +01:00
} ) ;
2019-10-05 09:01:12 +02:00
}
}
2025-02-20 21:07:22 +01:00
RuleCache const * StyleComputer : : rule_cache_for_cascade_origin ( CascadeOrigin cascade_origin , FlyString const & qualified_layer_name , GC : : Ptr < DOM : : ShadowRoot const > shadow_root ) const
2023-03-07 20:13:13 +01:00
{
2025-01-27 18:28:48 +01:00
auto const * rule_caches_for_document_and_shadow_roots = [ & ] ( ) - > RuleCachesForDocumentAndShadowRoots const * {
switch ( cascade_origin ) {
case CascadeOrigin : : Author :
2025-01-24 10:27:52 +01:00
return m_author_rule_cache ;
2025-01-27 18:28:48 +01:00
case CascadeOrigin : : User :
return m_user_rule_cache ;
case CascadeOrigin : : UserAgent :
return m_user_agent_rule_cache ;
default :
VERIFY_NOT_REACHED ( ) ;
}
} ( ) ;
auto const * rule_caches_by_layer = [ & ] ( ) - > RuleCaches const * {
if ( shadow_root )
return rule_caches_for_document_and_shadow_roots - > for_shadow_roots . get ( * shadow_root ) . value_or ( nullptr ) ;
return & rule_caches_for_document_and_shadow_roots - > for_document ;
} ( ) ;
if ( ! rule_caches_by_layer )
return nullptr ;
if ( qualified_layer_name . is_empty ( ) )
return & rule_caches_by_layer - > main ;
return rule_caches_by_layer - > by_layer . get ( qualified_layer_name ) . value_or ( nullptr ) ;
2023-03-07 20:13:13 +01:00
}
2025-01-26 13:58:28 +01:00
[[nodiscard]] static bool filter_namespace_rule ( Optional < FlyString > const & element_namespace_uri , MatchingRule const & rule )
2023-07-29 11:51:15 -05:00
{
// FIXME: Filter out non-default namespace using prefixes
2025-01-26 13:58:28 +01:00
if ( rule . default_namespace . has_value ( ) & & element_namespace_uri ! = rule . default_namespace )
return false ;
2023-08-03 14:08:02 +02:00
return true ;
2023-07-29 11:51:15 -05:00
}
2025-04-17 13:39:30 +02:00
RuleCache const & StyleComputer : : get_pseudo_class_rule_cache ( PseudoClass pseudo_class ) const
2025-01-04 18:09:21 +03:00
{
build_rule_cache_if_needed ( ) ;
2025-04-17 13:39:30 +02:00
return * m_pseudo_class_rule_cache [ to_underlying ( pseudo_class ) ] ;
2025-01-04 18:09:21 +03: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
2025-01-12 18:38:05 +03:00
InvalidationSet StyleComputer : : invalidation_set_for_properties ( Vector < InvalidationSet : : Property > const & properties ) const
{
if ( ! m_style_invalidation_data )
return { } ;
auto const & descendant_invalidation_sets = m_style_invalidation_data - > descendant_invalidation_sets ;
InvalidationSet result ;
for ( auto const & property : properties ) {
if ( auto it = descendant_invalidation_sets . find ( property ) ; it ! = descendant_invalidation_sets . end ( ) )
result . include_all_from ( it - > value ) ;
}
return result ;
}
bool StyleComputer : : invalidation_property_used_in_has_selector ( InvalidationSet : : Property const & property ) const
{
if ( ! m_style_invalidation_data )
return true ;
switch ( property . type ) {
case InvalidationSet : : Property : : Type : : Id :
2025-01-19 17:22:44 +01:00
if ( m_style_invalidation_data - > ids_used_in_has_selectors . contains ( property . name ( ) ) )
2025-01-12 18:38:05 +03:00
return true ;
break ;
case InvalidationSet : : Property : : Type : : Class :
2025-01-19 17:22:44 +01:00
if ( m_style_invalidation_data - > class_names_used_in_has_selectors . contains ( property . name ( ) ) )
2025-01-12 18:38:05 +03:00
return true ;
break ;
case InvalidationSet : : Property : : Type : : Attribute :
2025-01-19 17:22:44 +01:00
if ( m_style_invalidation_data - > attribute_names_used_in_has_selectors . contains ( property . name ( ) ) )
2025-01-12 18:38:05 +03:00
return true ;
break ;
case InvalidationSet : : Property : : Type : : TagName :
2025-01-19 17:22:44 +01:00
if ( m_style_invalidation_data - > tag_names_used_in_has_selectors . contains ( property . name ( ) ) )
return true ;
break ;
case InvalidationSet : : Property : : Type : : PseudoClass :
if ( m_style_invalidation_data - > pseudo_classes_used_in_has_selectors . contains ( property . value . get < PseudoClass > ( ) ) )
2025-01-12 18:38:05 +03:00
return true ;
break ;
default :
break ;
}
return false ;
}
2025-04-17 13:39:30 +02:00
Vector < MatchingRule const * > StyleComputer : : collect_matching_rules ( DOM : : Element const & element , CascadeOrigin cascade_origin , Optional < CSS : : PseudoElement > pseudo_element , PseudoClassBitmap & attempted_pseudo_class_matches , FlyString const & qualified_layer_name ) const
2019-06-27 20:40:21 +02:00
{
2023-03-19 17:01:26 +01:00
auto const & root_node = element . root ( ) ;
auto shadow_root = is < DOM : : ShadowRoot > ( root_node ) ? static_cast < DOM : : ShadowRoot const * > ( & root_node ) : nullptr ;
2025-01-25 20:10:17 +01:00
auto element_shadow_root = element . shadow_root ( ) ;
2025-01-26 13:58:28 +01:00
auto const & element_namespace_uri = element . namespace_uri ( ) ;
2023-03-19 17:01:26 +01:00
2024-11-15 04:01:23 +13:00
GC : : Ptr < DOM : : Element const > shadow_host ;
2025-01-25 20:10:17 +01:00
if ( element_shadow_root )
2024-07-23 15:22:28 +02:00
shadow_host = element ;
else if ( shadow_root )
shadow_host = shadow_root - > host ( ) ;
2025-01-24 15:47:42 +01:00
Vector < MatchingRule const & , 512 > rules_to_run ;
auto add_rule_to_run = [ & ] ( MatchingRule const & rule_to_run ) {
// FIXME: This needs to be revised when adding support for the ::shadow selector, as it needs to cross shadow boundaries.
auto rule_root = rule_to_run . shadow_root ;
auto from_user_agent_or_user_stylesheet = rule_to_run . cascade_origin = = CascadeOrigin : : UserAgent | | rule_to_run . cascade_origin = = CascadeOrigin : : User ;
// NOTE: Inside shadow trees, we only match rules that are defined in the shadow tree's style sheets.
// The key exception is the shadow tree's *shadow host*, which needs to match :host rules from inside the shadow root.
// Also note that UA or User style sheets don't have a scope, so they are always relevant.
// FIXME: We should reorganize the data so that the document-level StyleComputer doesn't cache *all* rules,
// but instead we'd have some kind of "style scope" at the document level, and also one for each shadow root.
// Then we could only evaluate rules from the current style scope.
bool rule_is_relevant_for_current_scope = rule_root = = shadow_root
2025-01-25 20:10:17 +01:00
| | ( element_shadow_root & & rule_root = = element_shadow_root )
2025-01-24 15:47:42 +01:00
| | from_user_agent_or_user_stylesheet ;
if ( ! rule_is_relevant_for_current_scope )
return ;
2025-02-20 16:25:29 +01:00
auto const & selector = rule_to_run . selector ;
if ( selector . can_use_ancestor_filter ( ) & & should_reject_with_ancestor_filter ( selector ) )
2025-01-24 15:47:42 +01:00
return ;
rules_to_run . unchecked_append ( rule_to_run ) ;
} ;
2023-03-09 19:27:23 +01:00
auto add_rules_to_run = [ & ] ( Vector < MatchingRule > const & rules ) {
2023-08-03 14:08:02 +02:00
rules_to_run . grow_capacity ( rules_to_run . size ( ) + rules . size ( ) ) ;
2023-03-09 19:27:23 +01:00
if ( pseudo_element . has_value ( ) ) {
2023-08-03 14:08:02 +02:00
for ( auto const & rule : rules ) {
2025-01-26 13:58:28 +01:00
if ( rule . contains_pseudo_element & & filter_namespace_rule ( element_namespace_uri , rule ) )
2025-01-24 15:47:42 +01:00
add_rule_to_run ( rule ) ;
2023-03-09 19:27:23 +01:00
}
} else {
2023-08-03 14:08:02 +02:00
for ( auto const & rule : rules ) {
2025-01-26 13:58:28 +01:00
if ( ! rule . contains_pseudo_element & & filter_namespace_rule ( element_namespace_uri , rule ) )
2025-01-24 15:47:42 +01:00
add_rule_to_run ( rule ) ;
2023-08-03 14:08:02 +02:00
}
2022-02-10 17:49:50 +01:00
}
2023-03-09 19:27:23 +01:00
} ;
2025-01-27 18:28:48 +01:00
auto add_rules_from_cache = [ & ] ( RuleCache const & rule_cache ) {
2025-02-20 21:07:22 +01:00
rule_cache . for_each_matching_rules ( element , pseudo_element , [ & ] ( auto const & matching_rules ) {
add_rules_to_run ( matching_rules ) ;
return IterationDecision : : Continue ;
2025-01-27 18:28:48 +01:00
} ) ;
} ;
2024-03-16 08:46:54 +01:00
2025-01-27 18:28:48 +01:00
if ( auto const * rule_cache = rule_cache_for_cascade_origin ( cascade_origin , qualified_layer_name , nullptr ) )
add_rules_from_cache ( * rule_cache ) ;
if ( shadow_root ) {
if ( auto const * rule_cache = rule_cache_for_cascade_origin ( cascade_origin , qualified_layer_name , shadow_root ) )
add_rules_from_cache ( * rule_cache ) ;
}
if ( element_shadow_root ) {
if ( auto const * rule_cache = rule_cache_for_cascade_origin ( cascade_origin , qualified_layer_name , element_shadow_root ) )
add_rules_from_cache ( * rule_cache ) ;
}
2019-10-05 09:01:12 +02:00
2025-01-24 15:47:42 +01:00
Vector < MatchingRule const * > matching_rules ;
matching_rules . ensure_capacity ( rules_to_run . size ( ) ) ;
2024-09-09 13:44:35 +02:00
for ( auto const & rule_to_run : rules_to_run ) {
2024-07-23 15:22:28 +02:00
// NOTE: When matching an element against a rule from outside the shadow root's style scope,
// we have to pass in null for the shadow host, otherwise combinator traversal will
// be confined to the element itself (since it refuses to cross the shadow boundary).
2024-09-09 13:44:35 +02:00
auto rule_root = rule_to_run . shadow_root ;
2024-07-23 15:22:28 +02:00
auto shadow_host_to_use = shadow_host ;
if ( element . is_shadow_host ( ) & & rule_root ! = element . shadow_root ( ) )
shadow_host_to_use = nullptr ;
2025-01-25 19:57:11 +01:00
auto const & selector = rule_to_run . selector ;
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-01-29 03:31:46 +01:00
SelectorEngine : : MatchContext context {
. style_sheet_for_rule = * rule_to_run . sheet ,
. subject = element ,
. collect_per_element_selector_involvement_metadata = true ,
} ;
2025-01-03 20:39:25 +03:00
ScopeGuard guard = [ & ] {
2025-04-17 13:39:30 +02:00
attempted_pseudo_class_matches | = context . attempted_pseudo_class_matches ;
2025-01-03 20:39:25 +03:00
} ;
2025-02-02 20:35:29 +01:00
if ( ! SelectorEngine : : matches ( selector , element , shadow_host_to_use , context , pseudo_element ) )
continue ;
2025-01-24 15:47:42 +01:00
matching_rules . append ( & rule_to_run ) ;
2023-03-07 20:13:13 +01:00
}
2025-01-25 19:57:11 +01:00
2019-06-27 20:40:21 +02:00
return matching_rules ;
}
2025-01-24 15:47:42 +01:00
static void sort_matching_rules ( Vector < MatchingRule const * > & matching_rules )
2020-12-14 21:31:10 +00:00
{
2025-01-24 15:47:42 +01:00
quick_sort ( matching_rules , [ & ] ( MatchingRule const * a , MatchingRule const * b ) {
2025-01-25 19:57:11 +01:00
auto const & a_selector = a - > selector ;
auto const & b_selector = b - > selector ;
auto a_specificity = a_selector . specificity ( ) ;
auto b_specificity = b_selector . specificity ( ) ;
2024-03-18 15:59:58 +01:00
if ( a_specificity = = b_specificity ) {
2025-01-24 15:47:42 +01:00
if ( a - > style_sheet_index = = b - > style_sheet_index )
return a - > rule_index < b - > rule_index ;
return a - > style_sheet_index < b - > style_sheet_index ;
2020-12-14 21:31:10 +00:00
}
return a_specificity < b_specificity ;
} ) ;
}
2025-05-07 13:45:55 +01:00
void StyleComputer : : for_each_property_expanding_shorthands ( PropertyID property_id , CSSStyleValue const & value , Function < void ( PropertyID , CSSStyleValue const & ) > const & set_longhand_property )
2019-11-18 11:49:19 +01:00
{
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
if ( property_is_shorthand ( property_id ) & & ( value . is_unresolved ( ) | | value . is_pending_substitution ( ) ) ) {
// If a shorthand property contains an arbitrary substitution function in its value, the longhand properties
// it’ s associated with must instead be filled in with a special, unobservable-to-authors pending-substitution
// value that indicates the shorthand contains an arbitrary substitution function, and thus the longhand’ s
// value can’ t be determined until after substituted.
// https://drafts.csswg.org/css-values-5/#pending-substitution-value
// Ensure we keep the longhand around until it can be resolved.
set_longhand_property ( property_id , value ) ;
auto pending_substitution_value = PendingSubstitutionStyleValue : : create ( ) ;
for ( auto longhand_id : longhands_for_shorthand ( property_id ) ) {
2025-05-07 13:45:55 +01:00
for_each_property_expanding_shorthands ( longhand_id , pending_substitution_value , set_longhand_property ) ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
}
return ;
}
2023-09-19 12:21:15 +01:00
if ( value . is_shorthand ( ) ) {
auto & shorthand_value = value . as_shorthand ( ) ;
auto & properties = shorthand_value . sub_properties ( ) ;
auto & values = shorthand_value . values ( ) ;
2023-05-26 23:16:43 +03:30
for ( size_t i = 0 ; i < properties . size ( ) ; + + i )
2025-05-07 13:45:55 +01:00
for_each_property_expanding_shorthands ( properties [ i ] , values [ i ] , set_longhand_property ) ;
2023-09-19 14:46:24 +01:00
return ;
2023-05-26 23:16:43 +03:30
}
2025-07-10 22:23:07 +12:00
// FIXME: We should add logic in parse_css_value to parse "positional-value-list-shorthand"s as
// ShorthandStyleValues to avoid the need for this (and assign_start_and_end_values).
2025-07-09 18:57:10 +12:00
auto assign_edge_values = [ & ] ( PropertyID top_property , PropertyID right_property , PropertyID bottom_property , PropertyID left_property , CSSStyleValue const & value ) {
if ( value . is_value_list ( ) ) {
auto values = value . as_value_list ( ) . values ( ) ;
if ( values . size ( ) = = 4 ) {
set_longhand_property ( top_property , values [ 0 ] ) ;
set_longhand_property ( right_property , values [ 1 ] ) ;
set_longhand_property ( bottom_property , values [ 2 ] ) ;
set_longhand_property ( left_property , values [ 3 ] ) ;
} else if ( values . size ( ) = = 3 ) {
set_longhand_property ( top_property , values [ 0 ] ) ;
set_longhand_property ( right_property , values [ 1 ] ) ;
set_longhand_property ( bottom_property , values [ 2 ] ) ;
set_longhand_property ( left_property , values [ 1 ] ) ;
} else if ( values . size ( ) = = 2 ) {
set_longhand_property ( top_property , values [ 0 ] ) ;
set_longhand_property ( right_property , values [ 1 ] ) ;
set_longhand_property ( bottom_property , values [ 0 ] ) ;
set_longhand_property ( left_property , values [ 1 ] ) ;
} else if ( values . size ( ) = = 1 ) {
set_longhand_property ( top_property , values [ 0 ] ) ;
set_longhand_property ( right_property , values [ 0 ] ) ;
set_longhand_property ( bottom_property , values [ 0 ] ) ;
set_longhand_property ( left_property , values [ 0 ] ) ;
}
} else {
set_longhand_property ( top_property , value ) ;
set_longhand_property ( right_property , value ) ;
set_longhand_property ( bottom_property , value ) ;
set_longhand_property ( left_property , value ) ;
2021-08-06 14:34:59 +01:00
}
} ;
2025-06-15 16:14:28 +12:00
auto assign_start_and_end_values = [ & ] ( PropertyID start_property , PropertyID end_property , auto const & values ) {
if ( values . is_value_list ( ) ) {
set_longhand_property ( start_property , value . as_value_list ( ) . values ( ) [ 0 ] ) ;
set_longhand_property ( end_property , value . as_value_list ( ) . values ( ) [ 1 ] ) ;
} else {
set_longhand_property ( start_property , value ) ;
set_longhand_property ( end_property , value ) ;
}
} ;
2019-11-27 20:51:15 +01:00
if ( property_id = = CSS : : PropertyID : : BorderStyle ) {
2025-07-09 18:57:10 +12:00
assign_edge_values ( PropertyID : : BorderTopStyle , PropertyID : : BorderRightStyle , PropertyID : : BorderBottomStyle , PropertyID : : BorderLeftStyle , value ) ;
2019-11-27 20:51:15 +01:00
return ;
}
2025-07-09 19:42:07 +12:00
if ( property_id = = CSS : : PropertyID : : BorderBlockStyle ) {
assign_start_and_end_values ( PropertyID : : BorderBlockStartStyle , PropertyID : : BorderBlockEndStyle , value ) ;
return ;
}
if ( property_id = = CSS : : PropertyID : : BorderInlineStyle ) {
assign_start_and_end_values ( PropertyID : : BorderInlineStartStyle , PropertyID : : BorderInlineEndStyle , value ) ;
return ;
}
2019-11-27 20:51:15 +01:00
if ( property_id = = CSS : : PropertyID : : BorderWidth ) {
2025-07-09 18:57:10 +12:00
assign_edge_values ( PropertyID : : BorderTopWidth , PropertyID : : BorderRightWidth , PropertyID : : BorderBottomWidth , PropertyID : : BorderLeftWidth , value ) ;
2019-11-27 20:51:15 +01:00
return ;
}
2025-07-09 19:42:07 +12:00
if ( property_id = = CSS : : PropertyID : : BorderBlockWidth ) {
assign_start_and_end_values ( PropertyID : : BorderBlockStartWidth , PropertyID : : BorderBlockEndWidth , value ) ;
return ;
}
if ( property_id = = CSS : : PropertyID : : BorderInlineWidth ) {
assign_start_and_end_values ( PropertyID : : BorderInlineStartWidth , PropertyID : : BorderInlineEndWidth , value ) ;
return ;
}
2019-11-27 20:51:15 +01:00
if ( property_id = = CSS : : PropertyID : : BorderColor ) {
2025-07-09 18:57:10 +12:00
assign_edge_values ( PropertyID : : BorderTopColor , PropertyID : : BorderRightColor , PropertyID : : BorderBottomColor , PropertyID : : BorderLeftColor , value ) ;
2019-11-27 20:51:15 +01:00
return ;
}
2025-07-09 19:42:07 +12:00
if ( property_id = = CSS : : PropertyID : : BorderBlockColor ) {
assign_start_and_end_values ( PropertyID : : BorderBlockStartColor , PropertyID : : BorderBlockEndColor , value ) ;
return ;
}
if ( property_id = = CSS : : PropertyID : : BorderInlineColor ) {
assign_start_and_end_values ( PropertyID : : BorderInlineStartColor , PropertyID : : BorderInlineEndColor , value ) ;
return ;
}
2023-04-03 00:04:00 +01:00
if ( property_id = = CSS : : PropertyID : : BackgroundPosition ) {
if ( value . is_position ( ) ) {
auto const & position = value . as_position ( ) ;
2023-07-29 17:14:18 +02:00
set_longhand_property ( CSS : : PropertyID : : BackgroundPositionX , position . edge_x ( ) ) ;
set_longhand_property ( CSS : : PropertyID : : BackgroundPositionY , position . edge_y ( ) ) ;
2023-04-03 18:25:49 +01:00
} else if ( value . is_value_list ( ) ) {
// Expand background-position layer list into separate lists for x and y positions:
auto const & values_list = value . as_value_list ( ) ;
StyleValueVector x_positions { } ;
StyleValueVector y_positions { } ;
x_positions . ensure_capacity ( values_list . size ( ) ) ;
y_positions . ensure_capacity ( values_list . size ( ) ) ;
for ( auto & layer : values_list . values ( ) ) {
if ( layer - > is_position ( ) ) {
auto const & position = layer - > as_position ( ) ;
x_positions . unchecked_append ( position . edge_x ( ) ) ;
y_positions . unchecked_append ( position . edge_y ( ) ) ;
} else {
x_positions . unchecked_append ( layer ) ;
y_positions . unchecked_append ( layer ) ;
}
}
2023-08-19 14:00:10 +01:00
set_longhand_property ( CSS : : PropertyID : : BackgroundPositionX , StyleValueList : : create ( move ( x_positions ) , values_list . separator ( ) ) ) ;
set_longhand_property ( CSS : : PropertyID : : BackgroundPositionY , StyleValueList : : create ( move ( y_positions ) , values_list . separator ( ) ) ) ;
2023-04-03 18:25:49 +01:00
} else {
2023-07-29 17:14:18 +02:00
set_longhand_property ( CSS : : PropertyID : : BackgroundPositionX , value ) ;
set_longhand_property ( CSS : : PropertyID : : BackgroundPositionY , value ) ;
2023-04-03 00:04:00 +01:00
}
return ;
}
2023-05-29 21:43:37 -04:00
if ( property_id = = CSS : : PropertyID : : Inset ) {
2025-07-09 18:57:10 +12:00
assign_edge_values ( PropertyID : : Top , PropertyID : : Right , PropertyID : : Bottom , PropertyID : : Left , value ) ;
2023-05-29 21:43:37 -04:00
return ;
}
2025-06-15 16:14:28 +12:00
if ( property_id = = CSS : : PropertyID : : InsetBlock ) {
assign_start_and_end_values ( PropertyID : : InsetBlockStart , PropertyID : : InsetBlockEnd , value ) ;
return ;
}
if ( property_id = = CSS : : PropertyID : : InsetInline ) {
assign_start_and_end_values ( PropertyID : : InsetInlineStart , PropertyID : : InsetInlineEnd , value ) ;
return ;
}
2019-11-18 11:49:19 +01:00
if ( property_id = = CSS : : PropertyID : : Margin ) {
2025-07-09 18:57:10 +12:00
assign_edge_values ( PropertyID : : MarginTop , PropertyID : : MarginRight , PropertyID : : MarginBottom , PropertyID : : MarginLeft , value ) ;
2019-11-18 11:49:19 +01:00
return ;
}
2025-06-15 16:14:28 +12:00
if ( property_id = = CSS : : PropertyID : : MarginBlock ) {
assign_start_and_end_values ( PropertyID : : MarginBlockStart , PropertyID : : MarginBlockEnd , value ) ;
return ;
}
if ( property_id = = CSS : : PropertyID : : MarginInline ) {
assign_start_and_end_values ( PropertyID : : MarginInlineStart , PropertyID : : MarginInlineEnd , value ) ;
return ;
}
2019-11-18 12:25:22 +01:00
if ( property_id = = CSS : : PropertyID : : Padding ) {
2025-07-09 18:57:10 +12:00
assign_edge_values ( PropertyID : : PaddingTop , PropertyID : : PaddingRight , PropertyID : : PaddingBottom , PropertyID : : PaddingLeft , value ) ;
2019-11-18 12:25:22 +01:00
return ;
}
2025-06-15 16:14:28 +12:00
if ( property_id = = CSS : : PropertyID : : PaddingBlock ) {
assign_start_and_end_values ( PropertyID : : PaddingBlockStart , PropertyID : : PaddingBlockEnd , value ) ;
return ;
}
if ( property_id = = CSS : : PropertyID : : PaddingInline ) {
assign_start_and_end_values ( PropertyID : : PaddingInlineStart , PropertyID : : PaddingInlineEnd , value ) ;
return ;
}
2024-10-14 11:24:48 +01:00
if ( property_id = = CSS : : PropertyID : : Gap ) {
2022-11-06 12:42:22 +01:00
if ( value . is_value_list ( ) ) {
auto const & values_list = value . as_value_list ( ) ;
2023-07-29 17:14:18 +02:00
set_longhand_property ( CSS : : PropertyID : : RowGap , values_list . values ( ) [ 0 ] ) ;
set_longhand_property ( CSS : : PropertyID : : ColumnGap , values_list . values ( ) [ 1 ] ) ;
2022-11-06 12:42:22 +01:00
return ;
}
2023-07-29 17:14:18 +02:00
set_longhand_property ( CSS : : PropertyID : : RowGap , value ) ;
set_longhand_property ( CSS : : PropertyID : : ColumnGap , value ) ;
2022-11-06 12:42:22 +01:00
return ;
}
2024-02-19 19:17:39 -07:00
if ( property_id = = CSS : : PropertyID : : Transition ) {
2025-06-10 17:45:32 +12:00
if ( value . to_keyword ( ) = = Keyword : : None ) {
2024-03-29 22:19:16 +01:00
// Handle `none` as a shorthand for `all 0s ease 0s`.
2024-08-14 14:06:03 +01:00
set_longhand_property ( CSS : : PropertyID : : TransitionProperty , CSSKeywordValue : : create ( Keyword : : All ) ) ;
2024-03-29 22:19:16 +01:00
set_longhand_property ( CSS : : PropertyID : : TransitionDuration , TimeStyleValue : : create ( CSS : : Time : : make_seconds ( 0 ) ) ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionDelay , TimeStyleValue : : create ( CSS : : Time : : make_seconds ( 0 ) ) ) ;
2025-04-23 22:24:35 +01:00
set_longhand_property ( CSS : : PropertyID : : TransitionTimingFunction , EasingStyleValue : : create ( EasingStyleValue : : CubicBezier : : ease ( ) ) ) ;
2025-04-17 19:11:14 +01:00
set_longhand_property ( CSS : : PropertyID : : TransitionBehavior , CSSKeywordValue : : create ( Keyword : : Normal ) ) ;
2025-06-10 17:45:32 +12:00
} else if ( value . is_transition ( ) ) {
auto const & transitions = value . as_transition ( ) . transitions ( ) ;
Array < Vector < ValueComparingNonnullRefPtr < CSSStyleValue const > > , 5 > transition_values ;
for ( auto const & transition : transitions ) {
transition_values [ 0 ] . append ( * transition . property_name ) ;
transition_values [ 1 ] . append ( transition . duration . as_style_value ( ) ) ;
transition_values [ 2 ] . append ( transition . delay . as_style_value ( ) ) ;
if ( transition . easing )
transition_values [ 3 ] . append ( * transition . easing ) ;
transition_values [ 4 ] . append ( CSSKeywordValue : : create ( to_keyword ( transition . transition_behavior ) ) ) ;
}
set_longhand_property ( CSS : : PropertyID : : TransitionProperty , StyleValueList : : create ( move ( transition_values [ 0 ] ) , StyleValueList : : Separator : : Comma ) ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionDuration , StyleValueList : : create ( move ( transition_values [ 1 ] ) , StyleValueList : : Separator : : Comma ) ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionDelay , StyleValueList : : create ( move ( transition_values [ 2 ] ) , StyleValueList : : Separator : : Comma ) ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionTimingFunction , StyleValueList : : create ( move ( transition_values [ 3 ] ) , StyleValueList : : Separator : : Comma ) ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionBehavior , StyleValueList : : create ( move ( transition_values [ 4 ] ) , StyleValueList : : Separator : : Comma ) ) ;
} else {
set_longhand_property ( CSS : : PropertyID : : TransitionProperty , value ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionDuration , value ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionDelay , value ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionTimingFunction , value ) ;
set_longhand_property ( CSS : : PropertyID : : TransitionBehavior , value ) ;
2024-02-19 19:17:39 -07:00
}
return ;
}
2023-09-20 17:00:49 +01:00
if ( property_is_shorthand ( property_id ) ) {
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
// ShorthandStyleValue was handled already, as were unresolved shorthands.
// That means the only values we should see are the CSS-wide keywords, or the guaranteed-invalid value.
// Both should be applied to our longhand properties.
2023-09-20 17:00:49 +01:00
// We don't directly call `set_longhand_property()` because the longhands might have longhands of their own.
// (eg `grid` -> `grid-template` -> `grid-template-areas` & `grid-template-rows` & `grid-template-columns`)
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
VERIFY ( value . is_css_wide_keyword ( ) | | value . is_guaranteed_invalid ( ) ) ;
2023-09-20 17:00:49 +01:00
for ( auto longhand : longhands_for_shorthand ( property_id ) )
2025-05-07 13:45:55 +01:00
for_each_property_expanding_shorthands ( longhand , value , set_longhand_property ) ;
2023-09-20 17:00:49 +01:00
return ;
}
2023-07-29 17:14:18 +02:00
set_longhand_property ( property_id , value ) ;
2019-11-18 11:49:19 +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 StyleComputer : : cascade_declarations (
CascadedProperties & cascaded_properties ,
DOM : : Element & element ,
2025-03-20 16:56:46 +00:00
Optional < CSS : : PseudoElement > pseudo_element ,
2025-01-24 15:47:42 +01:00
Vector < MatchingRule const * > const & matching_rules ,
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 cascade_origin ,
Important important ,
2025-06-18 17:45:26 +12:00
Optional < FlyString > layer_name ,
Optional < LogicalAliasMappingContext > logical_alias_mapping_context ) const
2021-09-21 11:38:18 +02:00
{
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
auto seen_properties = MUST ( Bitmap : : create ( to_underlying ( last_property_id ) + 1 , false ) ) ;
2025-03-12 17:45:57 +00:00
auto cascade_style_declaration = [ & ] ( CSSStyleProperties const & declaration ) {
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
seen_properties . fill ( false ) ;
2025-03-12 17:45:57 +00:00
for ( auto const & property : declaration . properties ( ) ) {
2021-09-21 11:38:18 +02:00
if ( important ! = property . important )
continue ;
2023-07-29 10:53:24 +02:00
2025-03-20 16:35:02 +00:00
if ( pseudo_element . has_value ( ) & & ! pseudo_element_supports_property ( * pseudo_element , property . property_id ) )
continue ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
auto property_value = property . value ;
if ( property_value - > is_unresolved ( ) )
property_value = Parser : : Parser : : resolve_unresolved_style_value ( Parser : : ParsingParams { element . document ( ) } , element , pseudo_element , property . property_id , property_value - > as_unresolved ( ) ) ;
if ( property_value - > is_guaranteed_invalid ( ) ) {
// https://drafts.csswg.org/css-values-5/#invalid-at-computed-value-time
// When substitution results in a property’ s value containing the guaranteed-invalid value, this makes the
// declaration invalid at computed-value time. When this happens, the computed value is one of the
// following depending on the property’ s type:
// -> The property is a non-registered custom property
// -> The property is a registered custom property with universal syntax
// FIXME: Process custom properties here?
if ( false ) {
// The computed value is the guaranteed-invalid value.
}
// -> Otherwise
else {
// Either the property’ s inherited value or its initial value depending on whether the property is
// inherited or not, respectively, as if the property’ s value had been specified as the unset keyword.
property_value = CSSKeywordValue : : create ( Keyword : : Unset ) ;
}
}
2025-05-07 13:45:55 +01:00
for_each_property_expanding_shorthands ( property . property_id , property_value , [ & ] ( PropertyID longhand_id , CSSStyleValue const & longhand_value ) {
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
// If we're a PSV that's already been seen, that should mean that our shorthand already got
// resolved and gave us a value, so we don't want to overwrite it with a PSV.
if ( seen_properties . get ( to_underlying ( longhand_id ) ) & & property_value - > is_pending_substitution ( ) )
return ;
seen_properties . set ( to_underlying ( longhand_id ) , true ) ;
2025-06-18 17:45:26 +12:00
PropertyID physical_property_id ;
if ( property_is_logical_alias ( longhand_id ) ) {
if ( ! logical_alias_mapping_context . has_value ( ) )
return ;
2025-07-03 14:31:26 +01:00
physical_property_id = map_logical_alias_to_physical_property ( longhand_id , logical_alias_mapping_context . value ( ) ) ;
2025-06-18 17:45:26 +12:00
} else {
physical_property_id = longhand_id ;
}
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
if ( longhand_value . is_revert ( ) ) {
2025-06-18 17:45:26 +12:00
cascaded_properties . revert_property ( physical_property_id , important , cascade_origin ) ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
} else if ( longhand_value . is_revert_layer ( ) ) {
2025-06-18 17:45:26 +12:00
cascaded_properties . revert_layer_property ( physical_property_id , important , layer_name ) ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
} else {
2025-06-18 17:45:26 +12:00
cascaded_properties . set_property ( physical_property_id , longhand_value , important , cascade_origin , layer_name , declaration ) ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
}
} ) ;
2021-09-21 11:38:18 +02:00
}
2025-03-12 17:45:57 +00:00
} ;
for ( auto const & match : matching_rules ) {
cascade_style_declaration ( match - > declaration ( ) ) ;
2021-09-21 11:38:18 +02:00
}
2023-04-02 08:35:03 +02:00
if ( cascade_origin = = CascadeOrigin : : Author & & ! pseudo_element . has_value ( ) ) {
2024-03-18 20:06:36 +01:00
if ( auto const inline_style = element . inline_style ( ) ) {
2025-03-12 17:45:57 +00:00
cascade_style_declaration ( * inline_style ) ;
2019-06-28 21:17:34 +02:00
}
}
2021-09-21 11:38:18 +02:00
}
2025-03-20 16:56:46 +00:00
static void cascade_custom_properties ( DOM : : Element & element , Optional < CSS : : PseudoElement > pseudo_element , Vector < MatchingRule const * > const & matching_rules , HashMap < FlyString , StyleProperty > & custom_properties )
2022-02-10 17:04:31 +01:00
{
2022-03-03 13:40:06 +01:00
size_t needed_capacity = 0 ;
for ( auto const & matching_rule : matching_rules )
2025-01-24 15:47:42 +01:00
needed_capacity + = matching_rule - > declaration ( ) . custom_properties ( ) . size ( ) ;
2023-05-17 17:05:36 +02:00
if ( ! pseudo_element . has_value ( ) ) {
2024-03-18 20:06:36 +01:00
if ( auto const inline_style = element . inline_style ( ) )
2023-05-17 17:05:36 +02:00
needed_capacity + = inline_style - > custom_properties ( ) . size ( ) ;
}
2022-03-03 13:40:06 +01:00
2024-09-07 11:07:04 +02:00
custom_properties . ensure_capacity ( custom_properties . size ( ) + needed_capacity ) ;
2022-02-10 17:04:31 +01:00
for ( auto const & matching_rule : matching_rules ) {
2025-01-24 15:47:42 +01:00
for ( auto const & it : matching_rule - > declaration ( ) . custom_properties ( ) ) {
2024-09-11 20:13:44 +08:00
auto style_value = it . value . value ;
if ( style_value - > is_revert_layer ( ) )
continue ;
2022-03-26 16:10:30 +01:00
custom_properties . set ( it . key , it . value ) ;
2024-09-11 20:13:44 +08:00
}
2022-03-26 16:10:30 +01:00
}
2023-05-17 17:05:36 +02:00
if ( ! pseudo_element . has_value ( ) ) {
2024-03-18 20:06:36 +01:00
if ( auto const inline_style = element . inline_style ( ) ) {
2023-05-17 17:05:36 +02:00
for ( auto const & it : inline_style - > custom_properties ( ) )
custom_properties . set ( it . key , it . value ) ;
}
2022-02-10 17:04:31 +01:00
}
}
2025-03-20 16:56:46 +00:00
void StyleComputer : : collect_animation_into ( DOM : : Element & element , Optional < CSS : : PseudoElement > pseudo_element , GC : : Ref < Animations : : KeyframeEffect > effect , ComputedProperties & computed_properties , AnimationRefresh refresh ) const
2023-05-29 05:02:03 +03:30
{
2024-02-22 13:56:15 +00:00
auto animation = effect - > associated_animation ( ) ;
if ( ! animation )
2024-03-05 18:10:02 -07:00
return ;
2023-05-26 23:30:54 +03:30
2024-02-22 13:56:15 +00:00
auto output_progress = effect - > transformed_progress ( ) ;
if ( ! output_progress . has_value ( ) )
2024-03-05 18:10:02 -07:00
return ;
2023-05-26 23:30:54 +03:30
2024-02-22 13:56:15 +00:00
if ( ! effect - > key_frame_set ( ) )
2024-03-05 18:10:02 -07:00
return ;
2023-05-26 23:30:54 +03:30
2024-02-22 13:56:15 +00:00
auto & keyframes = effect - > key_frame_set ( ) - > keyframes_by_key ;
2024-11-25 13:54:18 +01:00
if ( keyframes . size ( ) < 2 ) {
2023-05-26 23:30:54 +03:30
if constexpr ( LIBWEB_CSS_ANIMATION_DEBUG ) {
2024-11-25 13:54:18 +01:00
dbgln ( " Did not find enough keyframes ({} keyframes) " , keyframes . size ( ) ) ;
2023-05-26 23:30:54 +03:30
for ( auto it = keyframes . begin ( ) ; it ! = keyframes . end ( ) ; + + it )
dbgln ( " - {} " , it . key ( ) ) ;
}
2024-03-05 18:10:02 -07:00
return ;
2023-05-26 23:30:54 +03:30
}
2024-11-25 13:54:18 +01:00
auto key = static_cast < i64 > ( round ( output_progress . value ( ) * 100.0 * Animations : : KeyframeEffect : : AnimationKeyFrameKeyScaleFactor ) ) ;
auto keyframe_start_it = [ & ] {
if ( output_progress . value ( ) < = 0 ) {
return keyframes . begin ( ) ;
}
auto potential_match = keyframes . find_largest_not_above_iterator ( key ) ;
2025-06-19 13:13:53 +10:00
auto next = potential_match ;
+ + next ;
if ( next . is_end ( ) ) {
- - potential_match ;
2024-11-25 13:54:18 +01:00
}
return potential_match ;
} ( ) ;
auto keyframe_start = static_cast < i64 > ( keyframe_start_it . key ( ) ) ;
auto keyframe_values = * keyframe_start_it ;
2023-05-26 23:30:54 +03:30
2024-11-25 13:54:18 +01:00
auto keyframe_end_it = + + keyframe_start_it ;
VERIFY ( ! keyframe_end_it . is_end ( ) ) ;
auto keyframe_end = static_cast < i64 > ( keyframe_end_it . key ( ) ) ;
2023-05-26 23:30:54 +03:30
auto keyframe_end_values = * keyframe_end_it ;
2024-11-25 13:54:18 +01:00
auto progress_in_keyframe
= static_cast < float > ( key - keyframe_start ) / static_cast < float > ( keyframe_end - keyframe_start ) ;
2023-05-26 23:30:54 +03:30
2024-02-22 13:56:15 +00:00
if constexpr ( LIBWEB_CSS_ANIMATION_DEBUG ) {
2024-03-17 17:22:12 -07:00
auto valid_properties = keyframe_values . properties . size ( ) ;
2024-02-22 13:56:15 +00:00
dbgln ( " Animation {} contains {} properties to interpolate, progress = {}% " , animation - > id ( ) , valid_properties , progress_in_keyframe * 100 ) ;
2023-05-26 23:30:54 +03:30
}
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
// FIXME: Follow https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes in whatever the right place is.
2025-06-24 11:11:17 +01:00
auto compute_keyframe_values = [ refresh , & computed_properties , & element , & pseudo_element , this ] ( auto const & keyframe_values ) {
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
HashMap < PropertyID , RefPtr < CSSStyleValue const > > result ;
2025-06-17 21:43:56 +12:00
HashMap < PropertyID , PropertyID > longhands_set_by_property_id ;
auto property_is_set_by_use_initial = MUST ( Bitmap : : create ( to_underlying ( last_longhand_property_id ) - to_underlying ( first_longhand_property_id ) + 1 , false ) ) ;
2025-06-18 17:45:26 +12:00
auto property_is_logical_alias_including_shorthands = [ & ] ( PropertyID property_id ) {
if ( property_is_shorthand ( property_id ) )
// NOTE: All expanded longhands for a logical alias shorthand are logical aliases so we only need to check the first one.
return property_is_logical_alias ( expanded_longhands_for_shorthand ( property_id ) [ 0 ] ) ;
return property_is_logical_alias ( property_id ) ;
} ;
2025-06-17 21:43:56 +12:00
// https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes
auto is_property_preferred = [ & ] ( PropertyID a , PropertyID b ) {
// If conflicts arise when expanding shorthand properties or replacing logical properties with physical properties, apply the following rules in order until the conflict is resolved:
// 1. Longhand properties override shorthand properties (e.g. border-top-color overrides border-top).
if ( property_is_shorthand ( a ) ! = property_is_shorthand ( b ) )
return ! property_is_shorthand ( a ) ;
// 2. Shorthand properties with fewer longhand components override those with more longhand components (e.g. border-top overrides border-color).
if ( property_is_shorthand ( a ) ) {
auto number_of_expanded_shorthands_a = expanded_longhands_for_shorthand ( a ) . size ( ) ;
auto number_of_expanded_shorthands_b = expanded_longhands_for_shorthand ( b ) . size ( ) ;
if ( number_of_expanded_shorthands_a ! = number_of_expanded_shorthands_b )
return number_of_expanded_shorthands_a < number_of_expanded_shorthands_b ;
}
2025-06-18 17:45:26 +12:00
auto property_a_is_logical_alias = property_is_logical_alias_including_shorthands ( a ) ;
auto property_b_is_logical_alias = property_is_logical_alias_including_shorthands ( b ) ;
// 3. Physical properties override logical properties.
if ( property_a_is_logical_alias ! = property_b_is_logical_alias )
return ! property_a_is_logical_alias ;
2025-06-17 21:43:56 +12:00
// 4. For shorthand properties with an equal number of longhand components, properties whose IDL name (see
// the CSS property to IDL attribute algorithm [CSSOM]) appears earlier when sorted in ascending order
// by the Unicode codepoints that make up each IDL name, override those who appear later.
return camel_case_string_from_property_id ( a ) < camel_case_string_from_property_id ( b ) ;
} ;
2025-06-24 11:11:17 +01:00
compute_font ( computed_properties , & element , pseudo_element ) ;
Length : : FontMetrics font_metrics {
root_element_font_metrics_for_element ( element ) . font_size ,
computed_properties . first_available_computed_font ( ) . pixel_metrics ( )
} ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
for ( auto const & [ property_id , value ] : keyframe_values . properties ) {
2025-06-17 21:43:56 +12:00
bool is_use_initial = false ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
auto style_value = value . visit (
2024-08-14 11:10:54 +01:00
[ & ] ( Animations : : KeyframeEffect : : KeyFrameSet : : UseInitial ) - > RefPtr < CSSStyleValue const > {
2024-03-16 07:44:48 +01:00
if ( refresh = = AnimationRefresh : : Yes )
return { } ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
if ( property_is_shorthand ( property_id ) )
return { } ;
2025-06-17 21:43:56 +12:00
is_use_initial = true ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
return computed_properties . property ( property_id ) ;
2023-05-26 23:30:54 +03:30
} ,
2024-08-14 11:10:54 +01:00
[ & ] ( RefPtr < CSSStyleValue const > value ) - > RefPtr < CSSStyleValue const > {
2024-03-17 17:32:01 -07:00
return value ;
} ) ;
2023-05-26 23:30:54 +03:30
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
if ( ! style_value ) {
result . set ( property_id , nullptr ) ;
continue ;
}
2025-06-14 00:06:20 +12:00
// If the style value is a PendingSubstitutionStyleValue we should skip it to avoid overwriting any value
// already set by resolving the relevant shorthand's value.
if ( style_value - > is_pending_substitution ( ) )
continue ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
if ( style_value - > is_revert ( ) | | style_value - > is_revert_layer ( ) )
style_value = computed_properties . property ( property_id ) ;
if ( style_value - > is_unresolved ( ) )
style_value = Parser : : Parser : : resolve_unresolved_style_value ( Parser : : ParsingParams { element . document ( ) } , element , pseudo_element , property_id , style_value - > as_unresolved ( ) ) ;
2025-06-17 21:43:56 +12:00
for_each_property_expanding_shorthands ( property_id , * style_value , [ & ] ( PropertyID longhand_id , CSSStyleValue const & longhand_value ) {
2025-07-03 14:31:26 +01:00
auto physical_longhand_id = map_logical_alias_to_physical_property ( longhand_id , LogicalAliasMappingContext { computed_properties . writing_mode ( ) , computed_properties . direction ( ) } ) ;
2025-06-18 17:45:26 +12:00
auto physical_longhand_id_bitmap_index = to_underlying ( physical_longhand_id ) - to_underlying ( first_longhand_property_id ) ;
2025-06-17 21:43:56 +12:00
// Don't overwrite values if this is the result of a UseInitial
2025-06-18 17:45:26 +12:00
if ( result . contains ( physical_longhand_id ) & & result . get ( physical_longhand_id ) ! = nullptr & & is_use_initial )
2025-06-17 21:43:56 +12:00
return ;
// Don't overwrite unless the value was originally set by a UseInitial or this property is preferred over the one that set it originally
2025-06-18 17:45:26 +12:00
if ( result . contains ( physical_longhand_id ) & & result . get ( physical_longhand_id ) ! = nullptr & & ! property_is_set_by_use_initial . get ( physical_longhand_id_bitmap_index ) & & ! is_property_preferred ( property_id , longhands_set_by_property_id . get ( physical_longhand_id ) . value ( ) ) )
2025-06-17 21:43:56 +12:00
return ;
2025-06-18 17:45:26 +12:00
longhands_set_by_property_id . set ( physical_longhand_id , property_id ) ;
property_is_set_by_use_initial . set ( physical_longhand_id_bitmap_index , is_use_initial ) ;
2025-06-24 11:11:17 +01:00
result . set ( physical_longhand_id , { longhand_value . absolutized ( viewport_rect ( ) , font_metrics , m_root_element_font_metrics ) } ) ;
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
} ) ;
}
return result ;
} ;
HashMap < PropertyID , RefPtr < CSSStyleValue const > > computed_start_values = compute_keyframe_values ( keyframe_values ) ;
HashMap < PropertyID , RefPtr < CSSStyleValue const > > computed_end_values = compute_keyframe_values ( keyframe_end_values ) ;
for ( auto const & it : computed_start_values ) {
auto resolved_start_property = it . value ;
RefPtr resolved_end_property = computed_end_values . get ( it . key ) . value_or ( nullptr ) ;
2023-05-26 23:30:54 +03:30
LibWeb/CSS: Use PendingSubstitutionValue for unresolved shorthands
Previously, we would just assign the UnresolvedStyleValue to each
longhand, which was completely wrong but happened to work if it was a
ShorthandStyleValue (because that's basically a list of "set property X
to Y", and doesn't care which property it's the value of).
For example, the included `var-in-margin-shorthand.html` test would:
1. Set `margin-top` to `var(--a) 10px`
2. Resolve it to `margin-top: 5px 10px`
3. Reject that as invalid
What now happens is:
1. Set `margin-top` to a PendingSubstitutionValue
2. Resolve `margin` to `5px 10px`
3. Expand that out into its longhands
4. `margin-top` is `5px` 🎉
In order to support this, `for_each_property_expanding_shorthands()` now
runs the callback for the shorthand too if it's an unresolved or
pending-substitution value. This is so that we can store those in the
CascadedProperties until they can be resolved - otherwise, by the time
we want to resolve them, we don't have them any more.
`cascade_declarations()` has an unfortunate hack: it tracks, for each
declaration, which properties have already been given values, so that
it can avoid overwriting an actual value with a pending one. This is
necessary because of the unfortunate way that CSSStyleProperties holds
expanded longhands, and not just the original declarations. The spec
disagrees with itself about this, but we do need to do that expansion
for `element.style` to work correctly. This HashTable is unfortunate
but it does solve the problem until a better solution can be found.
2025-05-07 13:01:51 +01:00
if ( ! resolved_end_property ) {
2023-05-26 23:30:54 +03:30
if ( resolved_start_property ) {
2024-12-20 11:32:17 +01:00
computed_properties . set_animated_property ( it . key , * resolved_start_property ) ;
2025-05-16 19:20:24 +01:00
dbgln_if ( LIBWEB_CSS_ANIMATION_DEBUG , " No end property for property {}, using {} " , string_from_property_id ( it . key ) , resolved_start_property - > to_string ( SerializationMode : : Normal ) ) ;
2023-05-26 23:30:54 +03:30
}
continue ;
}
2024-02-11 14:21:35 -07:00
if ( resolved_end_property & & ! resolved_start_property )
2024-12-05 10:58:21 +00:00
resolved_start_property = property_initial_value ( it . key ) ;
2024-02-11 14:21:35 -07:00
2023-05-26 23:30:54 +03:30
if ( ! resolved_start_property | | ! resolved_end_property )
continue ;
auto start = resolved_start_property . release_nonnull ( ) ;
auto end = resolved_end_property . release_nonnull ( ) ;
2024-12-20 11:32:17 +01:00
if ( computed_properties . is_property_important ( it . key ) ) {
2024-03-16 07:44:48 +01:00
continue ;
}
2025-05-22 17:16:17 +01:00
if ( auto next_value = interpolate_property ( * effect - > target ( ) , it . key , * start , * end , progress_in_keyframe , AllowDiscrete : : Yes ) ) {
2025-05-16 19:20:24 +01:00
dbgln_if ( LIBWEB_CSS_ANIMATION_DEBUG , " Interpolated value for property {} at {}: {} -> {} = {} " , string_from_property_id ( it . key ) , progress_in_keyframe , start - > to_string ( SerializationMode : : Normal ) , end - > to_string ( SerializationMode : : Normal ) , next_value - > to_string ( SerializationMode : : Normal ) ) ;
2024-12-20 11:32:17 +01:00
computed_properties . set_animated_property ( it . key , * next_value ) ;
2024-03-05 19:48:36 -07:00
} else {
// If interpolate_property() fails, the element should not be rendered
2025-05-16 19:20:24 +01:00
dbgln_if ( LIBWEB_CSS_ANIMATION_DEBUG , " Interpolated value for property {} at {}: {} -> {} is invalid " , string_from_property_id ( it . key ) , progress_in_keyframe , start - > to_string ( SerializationMode : : Normal ) , end - > to_string ( SerializationMode : : Normal ) ) ;
2024-12-20 11:32:17 +01:00
computed_properties . set_animated_property ( PropertyID : : Visibility , CSSKeywordValue : : create ( Keyword : : Hidden ) ) ;
2024-03-05 19:48:36 -07:00
}
2023-05-26 23:30:54 +03:30
}
}
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
static void apply_animation_properties ( DOM : : Document & document , CascadedProperties & cascaded_properties , Animations : : Animation & animation )
2024-02-25 10:25:18 -07:00
{
2024-12-22 21:29:02 -05:00
if ( ! animation . effect ( ) )
return ;
2025-01-21 09:12:05 -05:00
auto & effect = as < Animations : : KeyframeEffect > ( * animation . effect ( ) ) ;
2024-02-25 10:25:18 -07:00
Optional < CSS : : Time > duration ;
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
if ( auto duration_value = cascaded_properties . property ( PropertyID : : AnimationDuration ) ; duration_value ) {
2024-02-25 10:25:18 -07:00
if ( duration_value - > is_time ( ) ) {
duration = duration_value - > as_time ( ) . time ( ) ;
2024-08-14 14:06:03 +01:00
} else if ( duration_value - > is_keyword ( ) & & duration_value - > as_keyword ( ) . keyword ( ) = = Keyword : : Auto ) {
2024-02-25 10:25:18 -07:00
// We use empty optional to represent "auto".
duration = { } ;
}
}
CSS : : Time delay { 0 , CSS : : Time : : Type : : S } ;
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
if ( auto delay_value = cascaded_properties . property ( PropertyID : : AnimationDelay ) ; delay_value & & delay_value - > is_time ( ) )
2024-02-25 10:25:18 -07:00
delay = delay_value - > as_time ( ) . time ( ) ;
double iteration_count = 1.0 ;
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
if ( auto iteration_count_value = cascaded_properties . property ( PropertyID : : AnimationIterationCount ) ; iteration_count_value ) {
2024-08-14 14:06:03 +01:00
if ( iteration_count_value - > is_keyword ( ) & & iteration_count_value - > to_keyword ( ) = = Keyword : : Infinite )
2024-02-25 10:25:18 -07:00
iteration_count = HUGE_VAL ;
else if ( iteration_count_value - > is_number ( ) )
iteration_count = iteration_count_value - > as_number ( ) . number ( ) ;
}
CSS : : AnimationFillMode fill_mode { CSS : : AnimationFillMode : : None } ;
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
if ( auto fill_mode_property = cascaded_properties . property ( PropertyID : : AnimationFillMode ) ; fill_mode_property & & fill_mode_property - > is_keyword ( ) ) {
2024-08-14 14:06:03 +01:00
if ( auto fill_mode_value = keyword_to_animation_fill_mode ( fill_mode_property - > to_keyword ( ) ) ; fill_mode_value . has_value ( ) )
2024-02-25 10:25:18 -07:00
fill_mode = * fill_mode_value ;
}
CSS : : AnimationDirection direction { CSS : : AnimationDirection : : Normal } ;
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
if ( auto direction_property = cascaded_properties . property ( PropertyID : : AnimationDirection ) ; direction_property & & direction_property - > is_keyword ( ) ) {
2024-08-14 14:06:03 +01:00
if ( auto direction_value = keyword_to_animation_direction ( direction_property - > to_keyword ( ) ) ; direction_value . has_value ( ) )
2024-02-25 10:25:18 -07:00
direction = * direction_value ;
}
CSS : : AnimationPlayState play_state { CSS : : AnimationPlayState : : Running } ;
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
if ( auto play_state_property = cascaded_properties . property ( PropertyID : : AnimationPlayState ) ; play_state_property & & play_state_property - > is_keyword ( ) ) {
2024-08-14 14:06:03 +01:00
if ( auto play_state_value = keyword_to_animation_play_state ( play_state_property - > to_keyword ( ) ) ; play_state_value . has_value ( ) )
2024-02-25 10:25:18 -07:00
play_state = * play_state_value ;
}
2024-06-14 21:50:25 -07:00
CSS : : EasingStyleValue : : Function timing_function { CSS : : EasingStyleValue : : CubicBezier : : ease ( ) } ;
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
if ( auto timing_property = cascaded_properties . property ( PropertyID : : AnimationTimingFunction ) ; timing_property & & timing_property - > is_easing ( ) )
2024-06-14 21:50:25 -07:00
timing_function = timing_property - > as_easing ( ) . function ( ) ;
2024-02-25 10:25:18 -07:00
auto iteration_duration = duration . has_value ( )
? Variant < double , String > { duration . release_value ( ) . to_milliseconds ( ) }
: " auto " _string ;
effect . set_iteration_duration ( iteration_duration ) ;
effect . set_start_delay ( delay . to_milliseconds ( ) ) ;
effect . set_iteration_count ( iteration_count ) ;
effect . set_timing_function ( move ( timing_function ) ) ;
effect . set_fill_mode ( Animations : : css_fill_mode_to_bindings_fill_mode ( fill_mode ) ) ;
effect . set_playback_direction ( Animations : : css_animation_direction_to_bindings_playback_direction ( direction ) ) ;
2024-03-26 17:02:27 -07:00
if ( play_state ! = effect . last_css_animation_play_state ( ) ) {
2025-06-16 12:26:42 +10:00
if ( play_state = = CSS : : AnimationPlayState : : Running & & animation . play_state ( ) ! = Bindings : : AnimationPlayState : : Running ) {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext context ( document . realm ( ) ) ;
2024-03-26 17:02:27 -07:00
animation . play ( ) . release_value_but_fixme_should_propagate_errors ( ) ;
} else if ( play_state = = CSS : : AnimationPlayState : : Paused & & animation . play_state ( ) ! = Bindings : : AnimationPlayState : : Paused ) {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext context ( document . realm ( ) ) ;
2024-03-26 17:02:27 -07:00
animation . pause ( ) . release_value_but_fixme_should_propagate_errors ( ) ;
}
effect . set_last_css_animation_play_state ( play_state ) ;
2024-02-25 10:25:18 -07: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
static void apply_dimension_attribute ( CascadedProperties & cascaded_properties , DOM : : Element const & element , FlyString const & attribute_name , CSS : : PropertyID property_id )
2024-04-10 21:44:11 -04:00
{
auto attribute = element . attribute ( attribute_name ) ;
if ( ! attribute . has_value ( ) )
return ;
auto parsed_value = HTML : : parse_dimension_value ( * attribute ) ;
if ( ! parsed_value )
return ;
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
cascaded_properties . set_property_from_presentational_hint ( property_id , parsed_value . release_nonnull ( ) ) ;
2024-04-10 21:44:11 -04:00
}
2025-03-20 16:56:46 +00:00
static void compute_transitioned_properties ( ComputedProperties const & style , DOM : : Element & element , Optional < PseudoElement > pseudo_element )
2024-09-19 14:14:13 +01:00
{
2024-09-24 20:19:54 +02:00
auto const source_declaration = style . transition_property_source ( ) ;
if ( ! source_declaration )
return ;
2024-12-20 16:35:12 +01:00
if ( ! element . computed_properties ( ) )
2024-09-24 20:19:54 +02:00
return ;
2025-05-28 22:12:13 +02:00
if ( source_declaration = = element . cached_transition_property_source ( pseudo_element ) )
2024-09-24 20:19:54 +02:00
return ;
// Reparse this transition property
2025-05-28 22:12:13 +02:00
element . clear_transitions ( pseudo_element ) ;
element . set_cached_transition_property_source ( pseudo_element , * source_declaration ) ;
2024-09-19 14:14:13 +01:00
2024-11-03 13:20:04 +01:00
auto const & transition_properties_value = style . property ( PropertyID : : TransitionProperty ) ;
auto transition_properties = transition_properties_value . is_value_list ( )
? transition_properties_value . as_value_list ( ) . values ( )
2024-09-24 20:19:54 +02:00
: StyleValueVector { transition_properties_value } ;
2024-09-19 14:14:13 +01:00
2024-09-24 20:19:54 +02:00
Vector < Vector < PropertyID > > properties ;
for ( size_t i = 0 ; i < transition_properties . size ( ) ; i + + ) {
auto property_value = transition_properties [ i ] ;
Vector < PropertyID > properties_for_this_transition ;
if ( property_value - > is_keyword ( ) ) {
auto keyword = property_value - > as_keyword ( ) . keyword ( ) ;
if ( keyword = = Keyword : : None )
continue ;
if ( keyword = = Keyword : : All ) {
for ( auto prop = first_property_id ; prop ! = last_property_id ; prop = static_cast < PropertyID > ( to_underlying ( prop ) + 1 ) )
properties_for_this_transition . append ( prop ) ;
2024-09-19 14:14:13 +01:00
}
2024-09-24 20:19:54 +02:00
} else {
auto maybe_property = property_id_from_string ( property_value - > as_custom_ident ( ) . custom_ident ( ) ) ;
if ( ! maybe_property . has_value ( ) )
continue ;
2024-09-19 14:14:13 +01:00
2024-09-24 20:19:54 +02:00
auto transition_property = maybe_property . release_value ( ) ;
if ( property_is_shorthand ( transition_property ) ) {
for ( auto const & prop : longhands_for_shorthand ( transition_property ) )
properties_for_this_transition . append ( prop ) ;
} else {
properties_for_this_transition . append ( transition_property ) ;
}
2024-09-19 14:14:13 +01:00
}
2024-09-24 20:19:54 +02:00
properties . append ( move ( properties_for_this_transition ) ) ;
2024-09-19 14:14:13 +01:00
}
2024-09-24 20:19:54 +02:00
auto normalize_transition_length_list = [ & properties , & style ] ( PropertyID property , auto make_default_value ) {
2024-11-03 13:20:04 +01:00
auto const * style_value = style . maybe_null_property ( property ) ;
2024-09-24 20:19:54 +02:00
StyleValueVector list ;
2025-04-23 18:35:45 +01:00
if ( style_value & & ! style_value - > is_value_list ( ) ) {
for ( size_t i = 0 ; i < properties . size ( ) ; i + + )
list . append ( * style_value ) ;
return list ;
}
2024-09-24 20:19:54 +02:00
if ( ! style_value | | ! style_value - > is_value_list ( ) | | style_value - > as_value_list ( ) . size ( ) = = 0 ) {
auto default_value = make_default_value ( ) ;
for ( size_t i = 0 ; i < properties . size ( ) ; i + + )
list . append ( default_value ) ;
return list ;
}
auto const & value_list = style_value - > as_value_list ( ) ;
for ( size_t i = 0 ; i < properties . size ( ) ; i + + )
list . append ( value_list . value_at ( i , true ) ) ;
return list ;
} ;
auto delays = normalize_transition_length_list (
PropertyID : : TransitionDelay ,
[ ] { return TimeStyleValue : : create ( Time : : make_seconds ( 0.0 ) ) ; } ) ;
auto durations = normalize_transition_length_list (
PropertyID : : TransitionDuration ,
[ ] { return TimeStyleValue : : create ( Time : : make_seconds ( 0.0 ) ) ; } ) ;
auto timing_functions = normalize_transition_length_list (
PropertyID : : TransitionTimingFunction ,
[ ] { return EasingStyleValue : : create ( EasingStyleValue : : CubicBezier : : ease ( ) ) ; } ) ;
2025-04-17 19:11:14 +01:00
auto transition_behaviors = normalize_transition_length_list (
PropertyID : : TransitionBehavior ,
[ ] { return CSSKeywordValue : : create ( Keyword : : None ) ; } ) ;
2024-09-24 20:19:54 +02:00
2025-05-28 22:12:13 +02:00
element . add_transitioned_properties ( pseudo_element , move ( properties ) , move ( delays ) , move ( durations ) , move ( timing_functions ) , move ( transition_behaviors ) ) ;
2024-09-19 14:14:13 +01:00
}
2024-09-19 14:13:20 +01:00
// https://drafts.csswg.org/css-transitions/#starting
2025-03-20 16:56:46 +00:00
void StyleComputer : : start_needed_transitions ( ComputedProperties const & previous_style , ComputedProperties & new_style , DOM : : Element & element , Optional < PseudoElement > pseudo_element ) const
2024-09-19 14:13:20 +01:00
{
// https://drafts.csswg.org/css-transitions/#transition-combined-duration
auto combined_duration = [ ] ( Animations : : Animatable : : TransitionAttributes const & transition_attributes ) {
// Define the combined duration of the transition as the sum of max(matching transition duration, 0s) and the matching transition delay.
return max ( transition_attributes . duration , 0 ) + transition_attributes . delay ;
} ;
// For each element and property, the implementation must act as follows:
auto style_change_event_time = m_document - > timeline ( ) - > current_time ( ) . value ( ) ;
for ( auto i = to_underlying ( CSS : : first_longhand_property_id ) ; i < = to_underlying ( CSS : : last_longhand_property_id ) ; + + i ) {
auto property_id = static_cast < CSS : : PropertyID > ( i ) ;
2025-05-28 22:12:13 +02:00
auto matching_transition_properties = element . property_transition_attributes ( pseudo_element , property_id ) ;
2025-05-28 15:37:53 +02:00
auto const & before_change_value = previous_style . property ( property_id , ComputedProperties : : WithAnimationsApplied : : Yes ) ;
2024-12-20 11:32:17 +01:00
auto const & after_change_value = new_style . property ( property_id , ComputedProperties : : WithAnimationsApplied : : No ) ;
2024-09-19 14:13:20 +01:00
2025-05-28 22:12:13 +02:00
auto existing_transition = element . property_transition ( pseudo_element , property_id ) ;
2024-09-19 14:13:20 +01:00
bool has_running_transition = existing_transition & & ! existing_transition - > is_finished ( ) ;
bool has_completed_transition = existing_transition & & existing_transition - > is_finished ( ) ;
2024-11-03 13:20:04 +01:00
auto start_a_transition = [ & ] ( auto start_time , auto end_time , auto const & start_value , auto const & end_value , auto const & reversing_adjusted_start_value , auto reversing_shortening_factor ) {
2024-09-22 10:21:57 +02:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Starting a transition of {} from {} to {} " , string_from_property_id ( property_id ) , start_value - > to_string ( ) , end_value - > to_string ( ) ) ;
2024-09-19 14:13:20 +01:00
2025-05-28 22:12:13 +02:00
auto transition = CSSTransition : : start_a_transition ( element , pseudo_element , property_id ,
document ( ) . transition_generation ( ) , start_time , end_time , start_value , end_value , reversing_adjusted_start_value , reversing_shortening_factor ) ;
2024-09-19 14:13:20 +01:00
// Immediately set the property's value to the transition's current value, to prevent single-frame jumps.
2025-05-28 15:37:53 +02:00
collect_animation_into ( element , { } , as < Animations : : KeyframeEffect > ( * transition - > effect ( ) ) , new_style , AnimationRefresh : : No ) ;
2024-09-19 14:13:20 +01:00
} ;
// 1. If all of the following are true:
if (
// - the element does not have a running transition for the property,
( ! has_running_transition ) & &
2025-04-17 19:11:14 +01:00
// - there is a matching transition-property value, and
( matching_transition_properties . has_value ( ) ) & &
2024-09-19 14:13:20 +01:00
// - the before-change style is different from the after-change style for that property, and the values for the property are transitionable,
2025-05-22 17:16:17 +01:00
( ! before_change_value . equals ( after_change_value ) & & property_values_are_transitionable ( property_id , before_change_value , after_change_value , element , matching_transition_properties - > transition_behavior ) ) & &
2024-09-19 14:13:20 +01:00
// - the element does not have a completed transition for the property
// or the end value of the completed transition is different from the after-change style for the property,
( ! has_completed_transition | | ! existing_transition - > transition_end_value ( ) - > equals ( after_change_value ) ) & &
// - the combined duration is greater than 0s,
( combined_duration ( matching_transition_properties . value ( ) ) > 0 ) ) {
2024-09-22 10:21:57 +02:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Transition step 1. " ) ;
2024-09-19 14:13:20 +01:00
// then implementations must remove the completed transition (if present) from the set of completed transitions
if ( has_completed_transition )
2025-05-28 22:12:13 +02:00
element . remove_transition ( pseudo_element , property_id ) ;
2024-09-19 14:13:20 +01:00
// and start a transition whose:
// - start time is the time of the style change event plus the matching transition delay,
auto start_time = style_change_event_time + matching_transition_properties - > delay ;
// - end time is the start time plus the matching transition duration,
auto end_time = start_time + matching_transition_properties - > duration ;
// - start value is the value of the transitioning property in the before-change style,
2024-11-03 13:20:04 +01:00
auto const & start_value = before_change_value ;
2024-09-19 14:13:20 +01:00
// - end value is the value of the transitioning property in the after-change style,
2024-11-03 13:20:04 +01:00
auto const & end_value = after_change_value ;
2024-09-19 14:13:20 +01:00
// - reversing-adjusted start value is the same as the start value, and
2024-11-03 13:20:04 +01:00
auto const & reversing_adjusted_start_value = start_value ;
2024-09-19 14:13:20 +01:00
// - reversing shortening factor is 1.
double reversing_shortening_factor = 1 ;
start_a_transition ( start_time , end_time , start_value , end_value , reversing_adjusted_start_value , reversing_shortening_factor ) ;
}
// 2. Otherwise, if the element has a completed transition for the property
// and the end value of the completed transition is different from the after-change style for the property,
// then implementations must remove the completed transition from the set of completed transitions.
else if ( has_completed_transition & & ! existing_transition - > transition_end_value ( ) - > equals ( after_change_value ) ) {
2024-09-22 10:21:57 +02:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Transition step 2. " ) ;
2025-05-28 22:12:13 +02:00
element . remove_transition ( pseudo_element , property_id ) ;
2024-09-19 14:13:20 +01:00
}
// 3. If the element has a running transition or completed transition for the property,
// and there is not a matching transition-property value,
if ( existing_transition & & ! matching_transition_properties . has_value ( ) ) {
// then implementations must cancel the running transition or remove the completed transition from the set of completed transitions.
2024-09-22 10:21:57 +02:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Transition step 3. " ) ;
2024-09-19 14:13:20 +01:00
if ( has_running_transition )
existing_transition - > cancel ( ) ;
else
2025-05-28 22:12:13 +02:00
element . remove_transition ( pseudo_element , property_id ) ;
2024-09-19 14:13:20 +01:00
}
// 4. If the element has a running transition for the property,
// there is a matching transition-property value,
// and the end value of the running transition is not equal to the value of the property in the after-change style, then:
if ( has_running_transition & & matching_transition_properties . has_value ( ) & & ! existing_transition - > transition_end_value ( ) - > equals ( after_change_value ) ) {
2025-05-16 19:20:24 +01:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Transition step 4. existing end value = {}, after change value = {} " , existing_transition - > transition_end_value ( ) - > to_string ( SerializationMode : : Normal ) , after_change_value . to_string ( SerializationMode : : Normal ) ) ;
2024-09-19 14:13:20 +01:00
// 1. If the current value of the property in the running transition is equal to the value of the property in the after-change style,
// or if these two values are not transitionable,
// then implementations must cancel the running transition.
2025-05-28 15:37:53 +02:00
auto & current_value = new_style . property ( property_id , ComputedProperties : : WithAnimationsApplied : : Yes ) ;
if ( current_value . equals ( after_change_value ) | | ! property_values_are_transitionable ( property_id , current_value , after_change_value , element , matching_transition_properties - > transition_behavior ) ) {
2024-09-22 10:21:57 +02:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Transition step 4.1 " ) ;
2024-09-19 14:13:20 +01:00
existing_transition - > cancel ( ) ;
}
// 2. Otherwise, if the combined duration is less than or equal to 0s,
// or if the current value of the property in the running transition is not transitionable with the value of the property in the after-change style,
// then implementations must cancel the running transition.
else if ( ( combined_duration ( matching_transition_properties . value ( ) ) < = 0 )
2025-05-22 17:16:17 +01:00
| | ! property_values_are_transitionable ( property_id , current_value , after_change_value , element , matching_transition_properties - > transition_behavior ) ) {
2024-09-22 10:21:57 +02:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Transition step 4.2 " ) ;
2024-09-19 14:13:20 +01:00
existing_transition - > cancel ( ) ;
}
// 3. Otherwise, if the reversing-adjusted start value of the running transition is the same as the value of the property in the after-change style
// (see the section on reversing of transitions for why these case exists),
else if ( existing_transition - > reversing_adjusted_start_value ( ) - > equals ( after_change_value ) ) {
2024-09-22 10:21:57 +02:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Transition step 4.3 " ) ;
2024-09-19 14:13:20 +01:00
// implementations must cancel the running transition and start a new transition whose:
existing_transition - > cancel ( ) ;
// AD-HOC: Remove the cancelled transition, otherwise it breaks the invariant that there is only one
// running or completed transition for a property at once.
2025-05-28 22:12:13 +02:00
element . remove_transition ( pseudo_element , property_id ) ;
2024-09-19 14:13:20 +01:00
// - reversing-adjusted start value is the end value of the running transition,
auto reversing_adjusted_start_value = existing_transition - > transition_end_value ( ) ;
// - reversing shortening factor is the absolute value, clamped to the range [0, 1], of the sum of:
// 1. the output of the timing function of the old transition at the time of the style change event,
// times the reversing shortening factor of the old transition
auto term_1 = existing_transition - > timing_function_output_at_time ( style_change_event_time ) * existing_transition - > reversing_shortening_factor ( ) ;
// 2. 1 minus the reversing shortening factor of the old transition.
auto term_2 = 1 - existing_transition - > reversing_shortening_factor ( ) ;
double reversing_shortening_factor = clamp ( abs ( term_1 + term_2 ) , 0.0 , 1.0 ) ;
// - start time is the time of the style change event plus:
// 1. if the matching transition delay is nonnegative, the matching transition delay, or
// 2. if the matching transition delay is negative, the product of the new transition’ s reversing shortening factor and the matching transition delay,
auto start_time = style_change_event_time
+ ( matching_transition_properties - > delay > = 0
? ( matching_transition_properties - > delay )
: ( reversing_shortening_factor * matching_transition_properties - > delay ) ) ;
// - end time is the start time plus the product of the matching transition duration and the new transition’ s reversing shortening factor,
auto end_time = start_time + ( matching_transition_properties - > duration * reversing_shortening_factor ) ;
// - start value is the current value of the property in the running transition,
2024-11-03 13:20:04 +01:00
auto const & start_value = current_value ;
2024-09-19 14:13:20 +01:00
// - end value is the value of the property in the after-change style,
2024-11-03 13:20:04 +01:00
auto const & end_value = after_change_value ;
2024-09-19 14:13:20 +01:00
start_a_transition ( start_time , end_time , start_value , end_value , reversing_adjusted_start_value , reversing_shortening_factor ) ;
}
// 4. Otherwise,
else {
2024-09-22 10:21:57 +02:00
dbgln_if ( CSS_TRANSITIONS_DEBUG , " Transition step 4.4 " ) ;
2024-09-19 14:13:20 +01:00
// implementations must cancel the running transition and start a new transition whose:
existing_transition - > cancel ( ) ;
// AD-HOC: Remove the cancelled transition, otherwise it breaks the invariant that there is only one
// running or completed transition for a property at once.
2025-05-28 22:12:13 +02:00
element . remove_transition ( pseudo_element , property_id ) ;
2024-09-19 14:13:20 +01:00
// - start time is the time of the style change event plus the matching transition delay,
auto start_time = style_change_event_time + matching_transition_properties - > delay ;
// - end time is the start time plus the matching transition duration,
auto end_time = start_time + matching_transition_properties - > duration ;
// - start value is the current value of the property in the running transition,
2024-11-03 13:20:04 +01:00
auto const & start_value = current_value ;
2024-09-19 14:13:20 +01:00
// - end value is the value of the property in the after-change style,
2024-11-03 13:20:04 +01:00
auto const & end_value = after_change_value ;
2024-09-19 14:13:20 +01:00
// - reversing-adjusted start value is the same as the start value, and
2024-11-03 13:20:04 +01:00
auto const & reversing_adjusted_start_value = start_value ;
2024-09-19 14:13:20 +01:00
// - reversing shortening factor is 1.
double reversing_shortening_factor = 1 ;
start_a_transition ( start_time , end_time , start_value , end_value , reversing_adjusted_start_value , reversing_shortening_factor ) ;
}
}
}
}
2021-10-15 19:46:52 +01:00
// https://www.w3.org/TR/css-cascade/#cascading
2024-09-04 17:43:18 +01:00
// https://drafts.csswg.org/css-cascade-5/#layering
2025-06-18 17:45:26 +12:00
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
2021-09-21 11:38:18 +02: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
auto cascaded_properties = m_document - > heap ( ) . allocate < CascadedProperties > ( ) ;
2021-09-21 11:38:18 +02:00
// First, we collect all the CSS rules whose selectors match `element`:
MatchingRuleSet matching_rule_set ;
2025-04-17 13:39:30 +02:00
matching_rule_set . user_agent_rules = collect_matching_rules ( element , CascadeOrigin : : UserAgent , pseudo_element , attempted_pseudo_class_matches ) ;
2021-09-21 11:38:18 +02:00
sort_matching_rules ( matching_rule_set . user_agent_rules ) ;
2025-04-17 13:39:30 +02:00
matching_rule_set . user_rules = collect_matching_rules ( element , CascadeOrigin : : User , pseudo_element , attempted_pseudo_class_matches ) ;
2023-08-21 15:50:01 +01:00
sort_matching_rules ( matching_rule_set . user_rules ) ;
2024-09-04 17:43:18 +01:00
// @layer-ed author rules
for ( auto const & layer_name : m_qualified_layer_names_in_order ) {
2025-04-17 13:39:30 +02:00
auto layer_rules = collect_matching_rules ( element , CascadeOrigin : : Author , pseudo_element , attempted_pseudo_class_matches , layer_name ) ;
2024-09-04 17:43:18 +01:00
sort_matching_rules ( layer_rules ) ;
matching_rule_set . author_rules . append ( { layer_name , layer_rules } ) ;
}
// Un-@layer-ed author rules
2025-04-17 13:39:30 +02:00
auto unlayered_author_rules = collect_matching_rules ( element , CascadeOrigin : : Author , pseudo_element , attempted_pseudo_class_matches ) ;
2024-09-04 17:43:18 +01:00
sort_matching_rules ( unlayered_author_rules ) ;
matching_rule_set . author_rules . append ( { { } , unlayered_author_rules } ) ;
2021-09-21 11:38:18 +02:00
2023-03-14 18:45:24 +01:00
if ( mode = = ComputeStyleMode : : CreatePseudoElementStyleIfNeeded ) {
VERIFY ( pseudo_element . has_value ( ) ) ;
2023-08-21 15:50:01 +01:00
if ( matching_rule_set . author_rules . is_empty ( ) & & matching_rule_set . user_rules . is_empty ( ) & & matching_rule_set . user_agent_rules . is_empty ( ) ) {
2023-03-14 18:45:24 +01:00
did_match_any_pseudo_element_rules = false ;
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
return cascaded_properties ;
2023-03-14 18:45:24 +01:00
}
did_match_any_pseudo_element_rules = true ;
2023-03-14 16:36:20 +01:00
}
2024-02-22 13:56:15 +00:00
// Then we resolve all the CSS custom properties ("variables") for this element:
2024-09-04 17:43:18 +01:00
// FIXME: Also resolve !important custom properties, in a second cascade.
2024-09-07 11:07:04 +02:00
2025-03-20 16:35:02 +00:00
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 ) ) ;
2024-09-04 17:43:18 +01:00
}
2022-02-10 17:04:31 +01:00
2021-09-21 11:38:18 +02:00
// Then we apply the declarations from the matched rules in cascade order:
// Normal user agent declarations
2025-06-18 17:45:26 +12:00
cascade_declarations ( * cascaded_properties , element , pseudo_element , matching_rule_set . user_agent_rules , CascadeOrigin : : UserAgent , Important : : No , { } , logical_alias_mapping_context ) ;
2021-09-21 11:38:18 +02:00
2023-08-21 15:50:01 +01:00
// Normal user declarations
2025-06-18 17:45:26 +12:00
cascade_declarations ( * cascaded_properties , element , pseudo_element , matching_rule_set . user_rules , CascadeOrigin : : User , Important : : No , { } , logical_alias_mapping_context ) ;
2021-09-21 11:38:18 +02:00
2024-09-25 15:11:48 +01:00
// Author presentational hints
// The spec calls this a special "Author presentational hint origin":
// "For the purpose of cascading this author presentational hint origin is treated as an independent origin;
// however for the purpose of the revert keyword (but not for the revert-layer keyword) it is considered
// part of the author origin."
// https://drafts.csswg.org/css-cascade-5/#author-presentational-hint-origin
2023-05-23 18:14:47 +02:00
if ( ! pseudo_element . has_value ( ) ) {
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
element . apply_presentational_hints ( cascaded_properties ) ;
2024-04-10 21:44:11 -04:00
if ( element . supports_dimension_attributes ( ) ) {
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
apply_dimension_attribute ( cascaded_properties , element , HTML : : AttributeNames : : width , CSS : : PropertyID : : Width ) ;
apply_dimension_attribute ( cascaded_properties , element , HTML : : AttributeNames : : height , CSS : : PropertyID : : Height ) ;
2024-04-10 21:44:11 -04:00
}
2023-05-23 18:14:47 +02:00
// SVG presentation attributes are parsed as CSS values, so we need to handle potential custom properties here.
if ( element . is_svg_element ( ) ) {
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
cascaded_properties - > resolve_unresolved_properties ( element , pseudo_element ) ;
2023-05-23 18:14:47 +02:00
}
}
2024-09-04 17:43:18 +01:00
// Normal author declarations, ordered by @layer, with un-@layer-ed rules last
for ( auto const & layer : matching_rule_set . author_rules ) {
2025-06-18 17:45:26 +12:00
cascade_declarations ( cascaded_properties , element , pseudo_element , layer . rules , CascadeOrigin : : Author , Important : : No , layer . qualified_layer_name , logical_alias_mapping_context ) ;
2023-05-26 23:30:54 +03:30
}
2021-09-21 11:38:18 +02:00
2024-09-04 17:43:18 +01:00
// Important author declarations, with un-@layer-ed rules first, followed by each @layer in reverse order.
for ( auto const & layer : matching_rule_set . author_rules . in_reverse ( ) ) {
2025-06-18 17:45:26 +12:00
cascade_declarations ( cascaded_properties , element , pseudo_element , layer . rules , CascadeOrigin : : Author , Important : : Yes , { } , logical_alias_mapping_context ) ;
2024-09-04 17:43:18 +01:00
}
2021-09-21 11:38:18 +02:00
2023-08-21 15:50:01 +01:00
// Important user declarations
2025-06-18 17:45:26 +12:00
cascade_declarations ( cascaded_properties , element , pseudo_element , matching_rule_set . user_rules , CascadeOrigin : : User , Important : : Yes , { } , logical_alias_mapping_context ) ;
2021-09-21 11:38:18 +02:00
// Important user agent declarations
2025-06-18 17:45:26 +12:00
cascade_declarations ( cascaded_properties , element , pseudo_element , matching_rule_set . user_agent_rules , CascadeOrigin : : UserAgent , Important : : Yes , { } , logical_alias_mapping_context ) ;
2021-09-21 11:38:18 +02:00
2024-09-19 14:13:20 +01:00
// Transition declarations [css-transitions-1]
// Note that we have to do these after finishing computing the style,
2025-06-18 17:45:26 +12:00
// so they're not done here, but as the final step in compute_properties()
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
return cascaded_properties ;
2021-09-21 11:38:18 +02:00
}
2025-03-20 16:56:46 +00:00
DOM : : Element const * element_to_inherit_style_from ( DOM : : Element const * element , Optional < CSS : : PseudoElement > pseudo_element )
2021-09-23 13:13:51 +02:00
{
2022-02-24 15:54:12 +00:00
// Pseudo-elements treat their originating element as their parent.
DOM : : Element const * parent_element = nullptr ;
if ( pseudo_element . has_value ( ) ) {
parent_element = element ;
} else if ( element ) {
2022-11-05 17:08:16 +01:00
parent_element = element - > parent_or_shadow_host_element ( ) ;
2022-02-24 15:54:12 +00:00
}
return parent_element ;
}
2025-03-20 16:56:46 +00:00
NonnullRefPtr < CSSStyleValue const > StyleComputer : : get_inherit_value ( CSS : : PropertyID property_id , DOM : : Element const * element , Optional < CSS : : PseudoElement > pseudo_element )
2022-02-24 15:54:12 +00:00
{
2022-11-05 17:08:16 +01:00
auto * parent_element = element_to_inherit_style_from ( element , pseudo_element ) ;
2022-02-24 15:54:12 +00:00
2024-12-20 16:35:12 +01:00
if ( ! parent_element | | ! parent_element - > computed_properties ( ) )
2024-12-05 10:58:21 +00:00
return property_initial_value ( property_id ) ;
2024-12-20 16:35:12 +01:00
return parent_element - > computed_properties ( ) - > property ( property_id ) ;
2023-07-07 22:48:11 -04:00
}
2021-09-23 13:13:51 +02:00
2025-03-20 16:56:46 +00:00
void StyleComputer : : compute_defaulted_property_value ( ComputedProperties & style , DOM : : Element const * element , CSS : : PropertyID property_id , Optional < CSS : : PseudoElement > pseudo_element ) const
2021-09-21 11:38:18 +02:00
{
2024-12-20 16:35:12 +01:00
auto & value_slot = style . m_property_values [ to_underlying ( property_id ) ] ;
2024-08-02 13:59:19 +02:00
if ( ! value_slot ) {
if ( is_inherited_property ( property_id ) ) {
style . set_property (
property_id ,
2024-12-05 11:08:58 +00:00
get_inherit_value ( property_id , element , pseudo_element ) ,
2024-12-20 11:32:17 +01:00
ComputedProperties : : Inherited : : Yes ,
2024-08-02 13:59:19 +02:00
Important : : No ) ;
} else {
2024-12-05 10:58:21 +00:00
style . set_property ( property_id , property_initial_value ( property_id ) ) ;
2024-08-02 13:59:19 +02:00
}
2021-09-23 13:13:51 +02:00
return ;
}
2021-09-21 11:38:18 +02:00
2024-08-02 13:59:19 +02:00
if ( value_slot - > is_initial ( ) ) {
2024-12-05 10:58:21 +00:00
value_slot = property_initial_value ( property_id ) ;
2021-09-23 13:13:51 +02:00
return ;
}
2021-09-21 11:38:18 +02:00
2024-08-02 13:59:19 +02:00
if ( value_slot - > is_inherit ( ) ) {
2024-12-05 11:08:58 +00:00
value_slot = get_inherit_value ( property_id , element , pseudo_element ) ;
2024-12-20 11:32:17 +01:00
style . set_property_inherited ( property_id , ComputedProperties : : Inherited : : Yes ) ;
2021-09-23 13:13:51 +02:00
return ;
}
2022-03-12 17:00:29 +01:00
// https://www.w3.org/TR/css-cascade-4/#inherit-initial
// If the cascaded value of a property is the unset keyword,
2024-08-02 13:59:19 +02:00
if ( value_slot - > is_unset ( ) ) {
2022-03-12 17:00:29 +01:00
if ( is_inherited_property ( property_id ) ) {
// then if it is an inherited property, this is treated as inherit,
2024-12-05 11:08:58 +00:00
value_slot = get_inherit_value ( property_id , element , pseudo_element ) ;
2024-12-20 11:32:17 +01:00
style . set_property_inherited ( property_id , ComputedProperties : : Inherited : : Yes ) ;
2022-03-12 17:00:29 +01:00
} else {
// and if it is not, this is treated as initial.
2024-12-05 10:58:21 +00:00
value_slot = property_initial_value ( property_id ) ;
2022-03-12 17:00:29 +01:00
}
}
2021-09-23 13:13:51 +02:00
}
2021-10-15 19:46:52 +01:00
// https://www.w3.org/TR/css-cascade/#defaulting
2025-03-20 16:56:46 +00:00
void StyleComputer : : compute_defaulted_values ( ComputedProperties & style , DOM : : Element const * element , Optional < CSS : : PseudoElement > pseudo_element ) const
2021-09-23 13:13:51 +02:00
{
2021-09-21 11:38:18 +02:00
// Walk the list of all known CSS properties and:
// - Add them to `style` if they are missing.
// - Resolve `inherit` and `initial` as needed.
2021-09-21 15:51:51 +02:00
for ( auto i = to_underlying ( CSS : : first_longhand_property_id ) ; i < = to_underlying ( CSS : : last_longhand_property_id ) ; + + i ) {
2021-09-21 11:38:18 +02:00
auto property_id = ( CSS : : PropertyID ) i ;
2022-02-24 15:54:12 +00:00
compute_defaulted_property_value ( style , element , property_id , pseudo_element ) ;
2021-09-23 13:13:51 +02:00
}
2023-05-10 14:14:04 +02:00
// https://www.w3.org/TR/css-color-4/#resolving-other-colors
// In the color property, the used value of currentcolor is the inherited value.
2024-11-03 13:20:04 +01:00
auto const & color = style . property ( CSS : : PropertyID : : Color ) ;
if ( color . to_keyword ( ) = = Keyword : : Currentcolor ) {
2024-12-05 11:08:58 +00:00
auto const & inherited_value = get_inherit_value ( CSS : : PropertyID : : Color , element , pseudo_element ) ;
2024-11-03 13:20:04 +01:00
style . set_property ( CSS : : PropertyID : : Color , inherited_value ) ;
2023-05-10 14:14:04 +02:00
}
2025-07-14 15:03:37 +02:00
// AD-HOC: The -libweb-inherit-or-center style defaults to centering, unless a style value usually would have been
// inherited. This is used to support the ad-hoc default <th> text-align behavior.
if ( element & & element - > local_name ( ) = = HTML : : TagNames : : th
& & style . property ( PropertyID : : TextAlign ) . to_keyword ( ) = = Keyword : : LibwebInheritOrCenter ) {
auto const * parent_element = element ;
while ( ( parent_element = element_to_inherit_style_from ( parent_element , { } ) ) ) {
auto parent_computed = parent_element - > computed_properties ( ) ;
auto parent_cascaded = parent_element - > cascaded_properties ( { } ) ;
if ( ! parent_computed | | ! parent_cascaded )
break ;
if ( parent_cascaded - > property ( PropertyID : : TextAlign ) ) {
auto const & style_value = parent_computed - > property ( PropertyID : : TextAlign ) ;
style . set_property ( PropertyID : : TextAlign , style_value , ComputedProperties : : Inherited : : Yes ) ;
break ;
}
}
}
2021-09-23 13:13:51 +02:00
}
2021-09-21 11:38:18 +02:00
2024-12-20 11:32:17 +01:00
Length : : FontMetrics StyleComputer : : calculate_root_element_font_metrics ( ComputedProperties const & style ) const
2022-03-16 12:30:06 +01:00
{
2024-11-03 13:20:04 +01:00
auto const & root_value = style . property ( CSS : : PropertyID : : FontSize ) ;
2022-03-16 12:30:06 +01:00
2023-12-09 23:42:02 +01:00
auto font_pixel_metrics = style . first_available_computed_font ( ) . pixel_metrics ( ) ;
2024-01-12 12:39:40 +01:00
Length : : FontMetrics font_metrics { m_default_font_metrics . font_size , font_pixel_metrics } ;
2024-11-03 13:20:04 +01:00
font_metrics . font_size = root_value . as_length ( ) . length ( ) . to_px ( viewport_rect ( ) , font_metrics , font_metrics ) ;
2024-01-12 12:39:40 +01:00
font_metrics . line_height = style . compute_line_height ( viewport_rect ( ) , font_metrics , font_metrics ) ;
2023-03-17 23:08:45 +01:00
2023-04-28 16:42:21 +01:00
return font_metrics ;
2022-03-16 12:30:06 +01:00
}
2023-12-09 23:42:02 +01:00
RefPtr < Gfx : : FontCascadeList const > StyleComputer : : find_matching_font_weight_ascending ( Vector < MatchingFontCandidate > const & candidates , int target_weight , float font_size_in_pt , bool inclusive )
2023-05-29 02:16:16 +00:00
{
using Fn = AK : : Function < bool ( MatchingFontCandidate const & ) > ;
auto pred = inclusive ? Fn ( [ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight > = target_weight ; } )
: Fn ( [ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight > target_weight ; } ) ;
auto it = find_if ( candidates . begin ( ) , candidates . end ( ) , pred ) ;
2023-08-17 18:45:06 +02:00
for ( ; it ! = candidates . end ( ) ; + + it ) {
if ( auto found_font = it - > font_with_point_size ( font_size_in_pt ) )
2023-05-29 02:16:16 +00:00
return found_font ;
2023-08-17 18:45:06 +02:00
}
2023-05-29 02:16:16 +00:00
return { } ;
}
2023-12-09 23:42:02 +01:00
RefPtr < Gfx : : FontCascadeList const > StyleComputer : : find_matching_font_weight_descending ( Vector < MatchingFontCandidate > const & candidates , int target_weight , float font_size_in_pt , bool inclusive )
2023-05-29 02:16:16 +00:00
{
using Fn = AK : : Function < bool ( MatchingFontCandidate const & ) > ;
auto pred = inclusive ? Fn ( [ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight < = target_weight ; } )
: Fn ( [ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight < target_weight ; } ) ;
auto it = find_if ( candidates . rbegin ( ) , candidates . rend ( ) , pred ) ;
2023-08-17 18:45:06 +02:00
for ( ; it ! = candidates . rend ( ) ; + + it ) {
if ( auto found_font = it - > font_with_point_size ( font_size_in_pt ) )
2023-05-29 02:16:16 +00:00
return found_font ;
2023-08-17 18:45:06 +02:00
}
2023-05-29 02:16:16 +00:00
return { } ;
}
// Partial implementation of the font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm
// FIXME: This should be replaced by the full CSS font selection algorithm.
2024-10-26 23:35:58 +02:00
RefPtr < Gfx : : FontCascadeList const > StyleComputer : : font_matching_algorithm ( FlyString const & family_name , int weight , int slope , float font_size_in_pt ) const
2023-05-29 02:16:16 +00:00
{
// If a font family match occurs, the user agent assembles the set of font faces in that family and then
// narrows the set to a single face using other font properties in the order given below.
Vector < MatchingFontCandidate > matching_family_fonts ;
for ( auto const & font_key_and_loader : m_loaded_fonts ) {
2024-10-26 23:35:58 +02:00
if ( font_key_and_loader . key . family_name . equals_ignoring_ascii_case ( family_name ) )
2023-12-09 23:42:02 +01:00
matching_family_fonts . empend ( font_key_and_loader . key , const_cast < FontLoaderList * > ( & font_key_and_loader . value ) ) ;
2023-05-29 02:16:16 +00:00
}
2024-10-26 23:35:58 +02:00
Gfx : : FontDatabase : : the ( ) . for_each_typeface_with_family_name ( family_name , [ & ] ( Gfx : : Typeface const & typeface ) {
2023-08-24 11:35:54 +02:00
matching_family_fonts . empend (
FontFaceKey {
2023-09-05 20:19:33 +02:00
. family_name = typeface . family ( ) ,
2023-08-24 11:35:54 +02:00
. weight = static_cast < int > ( typeface . weight ( ) ) ,
. slope = typeface . slope ( ) ,
} ,
& typeface ) ;
2023-08-17 18:45:06 +02:00
} ) ;
quick_sort ( matching_family_fonts , [ ] ( auto const & a , auto const & b ) {
return a . key . weight < b . key . weight ;
} ) ;
2023-05-29 02:16:16 +00:00
// FIXME: 1. font-stretch is tried first.
// FIXME: 2. font-style is tried next.
// We don't have complete support of italic and oblique fonts, so matching on font-style can be simplified to:
// If a matching slope is found, all faces which don't have that matching slope are excluded from the matching set.
auto style_it = find_if ( matching_family_fonts . begin ( ) , matching_family_fonts . end ( ) ,
2024-10-26 23:35:58 +02:00
[ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . slope = = slope ; } ) ;
2023-05-29 02:16:16 +00:00
if ( style_it ! = matching_family_fonts . end ( ) ) {
matching_family_fonts . remove_all_matching ( [ & ] ( auto const & matching_font_candidate ) {
2024-10-26 23:35:58 +02:00
return matching_font_candidate . key . slope ! = slope ;
2023-05-29 02:16:16 +00:00
} ) ;
}
// 3. font-weight is matched next.
// If the desired weight is inclusively between 400 and 500, weights greater than or equal to the target weight
// are checked in ascending order until 500 is hit and checked, followed by weights less than the target weight
// in descending order, followed by weights greater than 500, until a match is found.
2024-10-26 23:35:58 +02:00
if ( weight > = 400 & & weight < = 500 ) {
2023-05-29 02:16:16 +00:00
auto it = find_if ( matching_family_fonts . begin ( ) , matching_family_fonts . end ( ) ,
2024-10-26 23:35:58 +02:00
[ & ] ( auto const & matching_font_candidate ) { return matching_font_candidate . key . weight > = weight ; } ) ;
2023-05-29 02:16:16 +00:00
for ( ; it ! = matching_family_fonts . end ( ) & & it - > key . weight < = 500 ; + + it ) {
2023-08-17 18:45:06 +02:00
if ( auto found_font = it - > font_with_point_size ( font_size_in_pt ) )
2023-05-29 02:16:16 +00:00
return found_font ;
}
2024-10-26 23:35:58 +02:00
if ( auto found_font = find_matching_font_weight_descending ( matching_family_fonts , weight , font_size_in_pt , false ) )
2023-05-29 02:16:16 +00:00
return found_font ;
for ( ; it ! = matching_family_fonts . end ( ) ; + + it ) {
2023-08-17 18:45:06 +02:00
if ( auto found_font = it - > font_with_point_size ( font_size_in_pt ) )
2023-05-29 02:16:16 +00:00
return found_font ;
}
}
// If the desired weight is less than 400, weights less than or equal to the desired weight are checked in descending order
// followed by weights above the desired weight in ascending order until a match is found.
2024-10-26 23:35:58 +02:00
if ( weight < 400 ) {
if ( auto found_font = find_matching_font_weight_descending ( matching_family_fonts , weight , font_size_in_pt , true ) )
2023-05-29 02:16:16 +00:00
return found_font ;
2024-10-26 23:35:58 +02:00
if ( auto found_font = find_matching_font_weight_ascending ( matching_family_fonts , weight , font_size_in_pt , false ) )
2023-05-29 02:16:16 +00:00
return found_font ;
}
// If the desired weight is greater than 500, weights greater than or equal to the desired weight are checked in ascending order
// followed by weights below the desired weight in descending order until a match is found.
2024-10-26 23:35:58 +02:00
if ( weight > 500 ) {
if ( auto found_font = find_matching_font_weight_ascending ( matching_family_fonts , weight , font_size_in_pt , true ) )
2023-05-29 02:16:16 +00:00
return found_font ;
2024-10-26 23:35:58 +02:00
if ( auto found_font = find_matching_font_weight_descending ( matching_family_fonts , weight , font_size_in_pt , false ) )
2023-05-29 02:16:16 +00:00
return found_font ;
}
return { } ;
}
2024-12-22 21:34:50 +01:00
CSSPixels StyleComputer : : default_user_font_size ( )
{
// FIXME: This value should be configurable by the user.
return 16 ;
}
// https://w3c.github.io/csswg-drafts/css-fonts/#absolute-size-mapping
CSSPixelFraction StyleComputer : : absolute_size_mapping ( Keyword keyword )
{
switch ( keyword ) {
case Keyword : : XxSmall :
return CSSPixels ( 3 ) / 5 ;
case Keyword : : XSmall :
return CSSPixels ( 3 ) / 4 ;
case Keyword : : Small :
return CSSPixels ( 8 ) / 9 ;
case Keyword : : Medium :
return 1 ;
case Keyword : : Large :
return CSSPixels ( 6 ) / 5 ;
case Keyword : : XLarge :
return CSSPixels ( 3 ) / 2 ;
case Keyword : : XxLarge :
return 2 ;
case Keyword : : XxxLarge :
return 3 ;
case Keyword : : Smaller :
return CSSPixels ( 4 ) / 5 ;
case Keyword : : Larger :
return CSSPixels ( 5 ) / 4 ;
default :
return 1 ;
}
}
2025-03-20 16:56:46 +00:00
RefPtr < Gfx : : FontCascadeList const > StyleComputer : : compute_font_for_style_values ( DOM : : Element const * element , Optional < CSS : : PseudoElement > 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 ) const
2021-09-23 13:13:51 +02:00
{
2022-11-05 17:08:16 +01:00
auto * parent_element = element_to_inherit_style_from ( element , pseudo_element ) ;
2021-09-23 13:13:51 +02:00
2024-09-27 14:04:59 +01:00
auto width = font_stretch . to_font_width ( ) ;
2023-08-07 21:48:18 +02:00
auto weight = font_weight . to_font_weight ( ) ;
2021-09-23 13:13:51 +02:00
2024-12-22 21:34:50 +01:00
auto font_size_in_px = default_user_font_size ( ) ;
2021-09-23 13:13:51 +02:00
2023-06-02 12:47:21 +02:00
Gfx : : FontPixelMetrics font_pixel_metrics ;
2024-12-20 16:35:12 +01:00
if ( parent_element & & parent_element - > computed_properties ( ) )
font_pixel_metrics = parent_element - > computed_properties ( ) - > first_available_computed_font ( ) . pixel_metrics ( ) ;
2023-06-02 12:47:21 +02:00
else
2025-01-02 03:56:05 +03:00
font_pixel_metrics = Platform : : FontPlugin : : the ( ) . default_font ( font_size_in_px . to_float ( ) ) - > pixel_metrics ( ) ;
2023-06-02 12:47:21 +02:00
auto parent_font_size = [ & ] ( ) - > CSSPixels {
2024-12-20 16:35:12 +01:00
if ( ! parent_element | | ! parent_element - > computed_properties ( ) )
2023-06-02 12:47:21 +02:00
return font_size_in_px ;
2024-12-20 16:35:12 +01:00
auto const & value = parent_element - > computed_properties ( ) - > property ( CSS : : PropertyID : : FontSize ) ;
2024-11-03 13:20:04 +01:00
if ( value . is_length ( ) ) {
auto length = value . as_length ( ) . length ( ) ;
2023-06-02 12:47:21 +02:00
if ( length . is_absolute ( ) | | length . is_relative ( ) ) {
2024-01-12 12:39:40 +01:00
Length : : FontMetrics font_metrics { font_size_in_px , font_pixel_metrics } ;
2025-03-05 18:22:05 +01:00
return length . to_px ( viewport_rect ( ) , font_metrics , root_element_font_metrics_for_element ( element ) ) ;
2023-06-02 12:47:21 +02:00
}
}
return font_size_in_px ;
2025-02-23 12:28:16 +01:00
} ( ) ;
2023-06-02 12:47:21 +02:00
2024-08-14 11:46:56 +01:00
if ( font_size . is_keyword ( ) ) {
2024-08-14 14:06:03 +01:00
auto const keyword = font_size . to_keyword ( ) ;
2022-11-30 16:56:33 +01:00
2024-08-14 14:06:03 +01:00
if ( keyword = = Keyword : : Math ) {
2023-09-07 17:39:07 +01:00
auto math_scaling_factor = [ & ] ( ) {
// https://w3c.github.io/mathml-core/#the-math-script-level-property
// If the specified value font-size is math then the computed value of font-size is obtained by multiplying
// the inherited value of font-size by a nonzero scale factor calculated by the following procedure:
// 1. Let A be the inherited math-depth value, B the computed math-depth value, C be 0.71 and S be 1.0
2024-12-20 16:35:12 +01:00
int inherited_math_depth = parent_element & & parent_element - > computed_properties ( )
? parent_element - > computed_properties ( ) - > math_depth ( )
2023-09-07 17:39:07 +01:00
: InitialValues : : math_depth ( ) ;
int computed_math_depth = math_depth ;
auto size_ratio = 0.71 ;
auto scale = 1.0 ;
// 2. If A = B then return S.
bool invert_scale_factor = false ;
if ( inherited_math_depth = = computed_math_depth ) {
return scale ;
}
// If B < A, swap A and B and set InvertScaleFactor to true.
else if ( computed_math_depth < inherited_math_depth ) {
AK : : swap ( inherited_math_depth , computed_math_depth ) ;
invert_scale_factor = true ;
}
// Otherwise B > A and set InvertScaleFactor to false.
else {
invert_scale_factor = false ;
}
// 3. Let E be B - A > 0.
double e = ( computed_math_depth - inherited_math_depth ) > 0 ;
// FIXME: 4. If the inherited first available font has an OpenType MATH table:
// - If A ≤ 0 and B ≥ 2 then multiply S by scriptScriptPercentScaleDown and decrement E by 2.
// - Otherwise if A = 1 then multiply S by scriptScriptPercentScaleDown / scriptPercentScaleDown and decrement E by 1.
// - Otherwise if B = 1 then multiply S by scriptPercentScaleDown and decrement E by 1.
// 5. Multiply S by C^E.
scale * = AK : : pow ( size_ratio , e ) ;
// 6. Return S if InvertScaleFactor is false and 1/S otherwise.
if ( ! invert_scale_factor )
return scale ;
return 1.0 / scale ;
} ;
2025-02-23 12:28:16 +01:00
font_size_in_px = parent_font_size . scale_by ( math_scaling_factor ( ) ) ;
2023-09-07 17:39:07 +01:00
} else {
// https://w3c.github.io/csswg-drafts/css-fonts/#valdef-font-size-relative-size
// TODO: If the parent element has a keyword font size in the absolute size keyword mapping table,
// larger may compute the font size to the next entry in the table,
// and smaller may compute the font size to the previous entry in the table.
2024-08-14 14:06:03 +01:00
if ( keyword = = Keyword : : Smaller | | keyword = = Keyword : : Larger ) {
2024-12-20 16:35:12 +01:00
if ( parent_element & & parent_element - > computed_properties ( ) ) {
font_size_in_px = CSSPixels : : nearest_value_for ( parent_element - > computed_properties ( ) - > first_available_computed_font ( ) . pixel_metrics ( ) . size ) ;
2023-09-07 17:39:07 +01:00
}
2022-11-30 16:56:33 +01:00
}
2024-12-22 21:34:50 +01:00
font_size_in_px * = absolute_size_mapping ( keyword ) ;
2021-09-23 13:13:51 +02:00
}
} else {
2023-06-02 12:47:21 +02:00
Length : : ResolutionContext const length_resolution_context {
. viewport_rect = viewport_rect ( ) ,
2025-02-23 12:28:16 +01:00
. font_metrics = Length : : FontMetrics { parent_font_size , font_pixel_metrics } ,
2025-03-05 18:22:05 +01:00
. root_font_metrics = root_element_font_metrics_for_element ( element ) ,
2022-03-16 12:30:06 +01:00
} ;
2021-09-23 13:13:51 +02:00
Optional < Length > maybe_length ;
2023-08-07 21:48:18 +02:00
if ( font_size . is_percentage ( ) ) {
2022-01-18 17:01:15 +00:00
// Percentages refer to parent element's font size
2025-02-23 12:28:16 +01:00
maybe_length = Length : : make_px ( CSSPixels : : nearest_value_for ( font_size . as_percentage ( ) . percentage ( ) . as_fraction ( ) * parent_font_size . to_double ( ) ) ) ;
2022-01-18 17:01:15 +00:00
2023-08-07 21:48:18 +02:00
} else if ( font_size . is_length ( ) ) {
maybe_length = font_size . as_length ( ) . length ( ) ;
2024-12-11 15:05:56 +00:00
} else if ( font_size . is_calculated ( ) ) {
2025-07-02 19:12:33 +12:00
maybe_length = font_size . as_calculated ( ) . resolve_length_deprecated ( {
2025-02-23 12:28:16 +01:00
. percentage_basis = Length : : make_px ( parent_font_size ) ,
LibWeb/CSS: Wrap calc()-resolution data in a struct
Initially I added this to the existing CalculationContext, but in
reality, we have some data at parse-time and different data at
resolve-time, so it made more sense to keep those separate.
Instead of needing a variety of methods for resolving a Foo, depending
on whether we have a Layout::Node available, or a percentage basis, or
a length resolution context... put those in a
CalculationResolutionContext, and just pass that one thing to these
methods. This also removes the need for separate resolve_*_percentage()
methods, because we can just pass the percentage basis in to the regular
resolve_foo() method.
This also corrects the issue that *any* calculation may need to resolve
lengths, but we previously only passed a length resolution context to
specific types in some situations. Now, they can all have one available,
though it's up to the caller to provide it.
2025-01-22 16:05:32 +00:00
. length_resolution_context = length_resolution_context ,
} ) ;
2023-03-30 15:46:05 +01:00
}
if ( maybe_length . has_value ( ) ) {
2023-09-15 15:29:31 +02:00
font_size_in_px = maybe_length . value ( ) . to_px ( length_resolution_context ) ;
2021-09-21 11:38:18 +02:00
}
2021-09-23 13:13:51 +02:00
}
2023-08-07 21:48:18 +02:00
auto slope = font_style . to_font_slope ( ) ;
2022-02-19 21:21:20 +01:00
2021-09-23 13:13:51 +02:00
// FIXME: Implement the full font-matching algorithm: https://www.w3.org/TR/css-fonts-4/#font-matching-algorithm
2021-09-21 11:38:18 +02:00
2023-05-24 12:11:04 +02:00
float const font_size_in_pt = font_size_in_px * 0.75f ;
2023-12-09 23:42:02 +01:00
auto find_font = [ & ] ( FlyString const & family ) - > RefPtr < Gfx : : FontCascadeList const > {
2023-05-24 15:35:30 +02:00
FontFaceKey key {
. family_name = family ,
. weight = weight ,
. slope = slope ,
} ;
2023-12-09 23:42:02 +01:00
auto result = Gfx : : FontCascadeList : : create ( ) ;
2023-05-24 15:35:30 +02:00
if ( auto it = m_loaded_fonts . find ( key ) ; it ! = m_loaded_fonts . end ( ) ) {
2023-12-09 23:42:02 +01:00
auto const & loaders = it - > value ;
for ( auto const & loader : loaders ) {
if ( auto found_font = loader - > font_with_point_size ( font_size_in_pt ) )
result - > add ( * found_font , loader - > unicode_ranges ( ) ) ;
}
return result ;
2022-03-29 02:14:20 +02:00
}
2024-10-26 23:35:58 +02:00
if ( auto found_font = font_matching_algorithm ( family , weight , slope , font_size_in_pt ) ; found_font & & ! found_font - > is_empty ( ) ) {
2021-09-23 13:13:51 +02:00
return found_font ;
2023-12-09 23:42:02 +01:00
}
2021-09-23 13:13:51 +02:00
2024-06-03 17:14:05 +02:00
if ( auto found_font = Gfx : : FontDatabase : : the ( ) . get ( family , font_size_in_pt , weight , width , slope ) ) {
2023-12-09 23:42:02 +01:00
result - > add ( * found_font ) ;
return result ;
}
2021-09-23 13:13:51 +02:00
return { } ;
} ;
2024-08-14 14:06:03 +01:00
auto find_generic_font = [ & ] ( Keyword font_id ) - > RefPtr < Gfx : : FontCascadeList const > {
2022-09-08 11:55:12 +02:00
Platform : : GenericFont generic_font { } ;
2021-09-23 13:13:51 +02:00
switch ( font_id ) {
2024-08-14 14:06:03 +01:00
case Keyword : : Monospace :
case Keyword : : UiMonospace :
2022-09-08 11:55:12 +02:00
generic_font = Platform : : GenericFont : : Monospace ;
break ;
2024-08-14 14:06:03 +01:00
case Keyword : : Serif :
2022-09-08 11:55:12 +02:00
generic_font = Platform : : GenericFont : : Serif ;
break ;
2024-08-14 14:06:03 +01:00
case Keyword : : Fantasy :
2022-09-08 11:55:12 +02:00
generic_font = Platform : : GenericFont : : Fantasy ;
break ;
2024-08-14 14:06:03 +01:00
case Keyword : : SansSerif :
2022-09-08 11:55:12 +02:00
generic_font = Platform : : GenericFont : : SansSerif ;
break ;
2024-08-14 14:06:03 +01:00
case Keyword : : Cursive :
2022-09-08 11:55:12 +02:00
generic_font = Platform : : GenericFont : : Cursive ;
break ;
2024-08-14 14:06:03 +01:00
case Keyword : : UiSerif :
2022-09-08 11:55:12 +02:00
generic_font = Platform : : GenericFont : : UiSerif ;
break ;
2024-08-14 14:06:03 +01:00
case Keyword : : UiSansSerif :
2022-09-08 11:55:12 +02:00
generic_font = Platform : : GenericFont : : UiSansSerif ;
break ;
2024-08-14 14:06:03 +01:00
case Keyword : : UiRounded :
2022-09-08 11:55:12 +02:00
generic_font = Platform : : GenericFont : : UiRounded ;
break ;
2021-09-23 13:13:51 +02:00
default :
return { } ;
}
2023-09-06 07:45:47 +02:00
return find_font ( Platform : : FontPlugin : : the ( ) . generic_font_name ( generic_font ) ) ;
2021-09-23 13:13:51 +02:00
} ;
2023-12-09 23:42:02 +01:00
auto font_list = Gfx : : FontCascadeList : : create ( ) ;
2023-08-07 21:48:18 +02:00
if ( font_family . is_value_list ( ) ) {
auto const & family_list = static_cast < StyleValueList const & > ( font_family ) . values ( ) ;
2022-02-09 20:11:16 +01:00
for ( auto const & family : family_list ) {
2023-12-09 23:42:02 +01:00
RefPtr < Gfx : : FontCascadeList const > other_font_list ;
2024-08-14 11:46:56 +01:00
if ( family - > is_keyword ( ) ) {
2024-08-14 14:06:03 +01:00
other_font_list = find_generic_font ( family - > to_keyword ( ) ) ;
2023-03-06 14:33:11 +01:00
} else if ( family - > is_string ( ) ) {
2023-12-09 23:42:02 +01:00
other_font_list = find_font ( family - > as_string ( ) . string_value ( ) ) ;
2023-09-16 13:26:42 +02:00
} else if ( family - > is_custom_ident ( ) ) {
2023-12-09 23:42:02 +01:00
other_font_list = find_font ( family - > as_custom_ident ( ) . custom_ident ( ) ) ;
2021-09-23 13:13:51 +02:00
}
2023-12-09 23:42:02 +01:00
if ( other_font_list )
font_list - > extend ( * other_font_list ) ;
2021-09-23 13:13:51 +02:00
}
2024-08-14 11:46:56 +01:00
} else if ( font_family . is_keyword ( ) ) {
2024-08-14 14:06:03 +01:00
if ( auto other_font_list = find_generic_font ( font_family . to_keyword ( ) ) )
2023-12-09 23:42:02 +01:00
font_list - > extend ( * other_font_list ) ;
2023-08-07 21:48:18 +02:00
} else if ( font_family . is_string ( ) ) {
2023-12-09 23:42:02 +01:00
if ( auto other_font_list = find_font ( font_family . as_string ( ) . string_value ( ) ) )
font_list - > extend ( * other_font_list ) ;
2023-09-16 13:26:42 +02:00
} else if ( font_family . is_custom_ident ( ) ) {
2023-12-09 23:42:02 +01:00
if ( auto other_font_list = find_font ( font_family . as_custom_ident ( ) . custom_ident ( ) ) )
font_list - > extend ( * other_font_list ) ;
2021-09-23 13:13:51 +02:00
}
2025-01-02 04:48:23 +03:00
auto default_font = Platform : : FontPlugin : : the ( ) . default_font ( font_size_in_pt ) ;
if ( font_list - > is_empty ( ) ) {
// This is needed to make sure we check default font before reaching to emojis.
font_list - > add ( * default_font ) ;
}
2024-09-05 22:35:01 +02:00
if ( auto emoji_font = Platform : : FontPlugin : : the ( ) . default_emoji_font ( font_size_in_pt ) ; emoji_font ) {
font_list - > add ( * emoji_font ) ;
}
2025-01-02 04:48:23 +03:00
// The default font is already included in the font list, but we explicitly set it
// as the last-resort font. This ensures that if none of the specified fonts contain
// the requested code point, there is still a font available to provide a fallback glyph.
font_list - > set_last_resort_font ( * default_font ) ;
2021-09-23 13:13:51 +02:00
2023-12-09 23:42:02 +01:00
return font_list ;
2023-08-07 21:48:18 +02:00
}
2025-03-20 16:56:46 +00:00
void StyleComputer : : compute_font ( ComputedProperties & style , DOM : : Element const * element , Optional < CSS : : PseudoElement > pseudo_element ) const
2023-08-07 21:48:18 +02:00
{
// To compute the font, first ensure that we've defaulted the relevant CSS font properties.
// FIXME: This should be more sophisticated.
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontFamily , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontSize , pseudo_element ) ;
2024-09-27 14:04:59 +01:00
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontWidth , pseudo_element ) ;
2023-08-07 21:48:18 +02:00
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontStyle , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontWeight , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : LineHeight , pseudo_element ) ;
2024-12-05 01:19:03 +01:00
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontVariant , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontVariantAlternates , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontVariantCaps , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontVariantEmoji , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontVariantEastAsian , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontVariantLigatures , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontVariantNumeric , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : FontVariantPosition , pseudo_element ) ;
2023-08-07 21:48:18 +02:00
2024-11-03 13:20:04 +01:00
auto const & font_family = style . property ( CSS : : PropertyID : : FontFamily ) ;
auto const & font_size = style . property ( CSS : : PropertyID : : FontSize ) ;
auto const & font_style = style . property ( CSS : : PropertyID : : FontStyle ) ;
auto const & font_weight = style . property ( CSS : : PropertyID : : FontWeight ) ;
auto const & font_width = style . property ( CSS : : PropertyID : : FontWidth ) ;
2023-08-07 21:48:18 +02:00
2024-09-27 14:04:59 +01:00
auto font_list = compute_font_for_style_values ( element , pseudo_element , font_family , font_size , font_style , font_weight , font_width , style . math_depth ( ) ) ;
2023-12-09 23:42:02 +01:00
VERIFY ( font_list ) ;
VERIFY ( ! font_list - > is_empty ( ) ) ;
RefPtr < Gfx : : Font const > const found_font = font_list - > first ( ) ;
2023-08-07 21:48:18 +02:00
2024-12-23 17:51:10 +01:00
style . set_property (
CSS : : PropertyID : : FontSize ,
LengthStyleValue : : create ( CSS : : Length : : make_px ( CSSPixels : : nearest_value_for ( found_font - > pixel_size ( ) ) ) ) ,
style . is_property_inherited ( CSS : : PropertyID : : FontSize ) ? ComputedProperties : : Inherited : : Yes : ComputedProperties : : Inherited : : No ) ;
style . set_property (
CSS : : PropertyID : : FontWeight ,
NumberStyleValue : : create ( font_weight . to_font_weight ( ) ) ,
style . is_property_inherited ( CSS : : PropertyID : : FontWeight ) ? ComputedProperties : : Inherited : : Yes : ComputedProperties : : Inherited : : No ) ;
2022-02-21 16:24:12 +01:00
2023-12-09 23:42:02 +01:00
style . set_computed_font_list ( * font_list ) ;
2023-05-08 10:28:21 +02:00
if ( element & & is < HTML : : HTMLHtmlElement > ( * element ) ) {
const_cast < StyleComputer & > ( * this ) . m_root_element_font_metrics = calculate_root_element_font_metrics ( style ) ;
}
2021-09-23 13:13:51 +02:00
}
2025-07-03 14:31:26 +01:00
LogicalAliasMappingContext StyleComputer : : compute_logical_alias_mapping_context ( DOM : : Element & element , Optional < PseudoElement > pseudo_element , ComputeStyleMode mode ) const
2025-06-18 17:45:26 +12:00
{
auto normalize_value = [ & ] ( auto property_id , auto value ) {
if ( ! value | | value - > is_inherit ( ) | | value - > is_unset ( ) ) {
if ( auto const * inheritance_parent = element_to_inherit_style_from ( & element , pseudo_element ) ) {
value = inheritance_parent - > computed_properties ( ) - > property ( property_id ) ;
} else {
value = property_initial_value ( property_id ) ;
}
}
if ( value - > is_initial ( ) )
value = property_initial_value ( property_id ) ;
return value ;
} ;
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.
auto cascaded_properties = compute_cascaded_values ( element , pseudo_element , did_match_any_pseudo_element_rules , attempted_pseudo_class_matches , mode , { } ) ;
auto writing_mode = normalize_value ( PropertyID : : WritingMode , cascaded_properties - > property ( PropertyID : : WritingMode ) ) ;
auto direction = normalize_value ( PropertyID : : Direction , cascaded_properties - > property ( PropertyID : : Direction ) ) ;
return LogicalAliasMappingContext {
. writing_mode = keyword_to_writing_mode ( writing_mode - > to_keyword ( ) ) . release_value ( ) ,
. direction = keyword_to_direction ( direction - > to_keyword ( ) ) . release_value ( )
} ;
}
2022-03-11 12:53:32 +00:00
Gfx : : Font const & StyleComputer : : initial_font ( ) const
{
// FIXME: This is not correct.
2025-06-11 12:13:29 +02:00
static auto font = ComputedProperties : : font_fallback ( false , false , 12 ) ;
return font ;
2022-03-11 12:53:32 +00:00
}
2025-03-05 18:22:05 +01:00
void StyleComputer : : absolutize_values ( ComputedProperties & style , GC : : Ptr < DOM : : Element const > element ) const
2021-09-23 13:13:51 +02:00
{
2024-01-12 12:39:40 +01:00
Length : : FontMetrics font_metrics {
2025-03-05 18:22:05 +01:00
root_element_font_metrics_for_element ( element ) . font_size ,
2024-01-12 12:39:40 +01:00
style . first_available_computed_font ( ) . pixel_metrics ( )
} ;
2023-04-28 16:29:12 +01:00
2025-06-24 16:03:27 +10:00
// "A percentage value specifies an absolute font size relative to the parent element’ s computed font-size. Negative percentages are invalid."
auto & font_size_value_slot = style . m_property_values [ to_underlying ( CSS : : PropertyID : : FontSize ) ] ;
if ( font_size_value_slot & & font_size_value_slot - > is_percentage ( ) ) {
auto parent_font_size = get_inherit_value ( CSS : : PropertyID : : FontSize , element ) - > as_length ( ) . length ( ) . to_px ( viewport_rect ( ) , font_metrics , m_root_element_font_metrics ) ;
font_size_value_slot = LengthStyleValue : : create (
Length : : make_px ( CSSPixels : : nearest_value_for ( parent_font_size * font_size_value_slot - > as_percentage ( ) . percentage ( ) . as_fraction ( ) ) ) ) ;
}
auto font_size = font_size_value_slot - > as_length ( ) . length ( ) . to_px ( viewport_rect ( ) , font_metrics , m_root_element_font_metrics ) ;
2023-04-28 16:29:12 +01:00
font_metrics . font_size = font_size ;
2025-06-24 16:03:27 +10:00
style . set_font_size ( { } , font_size ) ;
2021-09-23 13:13:51 +02:00
2023-03-12 16:09:30 +01:00
// NOTE: Percentage line-height values are relative to the font-size of the element.
// We have to resolve them right away, so that the *computed* line-height is ready for inheritance.
// We can't simply absolutize *all* percentage values against the font size,
// because most percentages are relative to containing block metrics.
2024-12-20 16:35:12 +01:00
auto & line_height_value_slot = style . m_property_values [ to_underlying ( CSS : : PropertyID : : LineHeight ) ] ;
2024-03-18 12:02:29 +01:00
if ( line_height_value_slot & & line_height_value_slot - > is_percentage ( ) ) {
line_height_value_slot = LengthStyleValue : : create (
Length : : make_px ( CSSPixels : : nearest_value_for ( font_size * static_cast < double > ( line_height_value_slot - > as_percentage ( ) . percentage ( ) . as_fraction ( ) ) ) ) ) ;
2023-03-12 16:09:30 +01:00
}
2024-01-12 12:39:40 +01:00
auto line_height = style . compute_line_height ( viewport_rect ( ) , font_metrics , m_root_element_font_metrics ) ;
2023-04-28 16:29:12 +01:00
font_metrics . line_height = line_height ;
2023-03-17 23:08:45 +01:00
// NOTE: line-height might be using lh which should be resolved against the parent line height (like we did here already)
2024-03-18 12:02:29 +01:00
if ( line_height_value_slot & & line_height_value_slot - > is_length ( ) )
line_height_value_slot = LengthStyleValue : : create ( Length : : make_px ( line_height ) ) ;
2023-03-17 23:08:45 +01:00
2024-12-20 16:35:12 +01:00
for ( size_t i = 0 ; i < style . m_property_values . size ( ) ; + + i ) {
auto & value_slot = style . m_property_values [ i ] ;
2024-08-02 13:59:19 +02:00
if ( ! value_slot )
2022-02-18 20:21:49 +01:00
continue ;
2024-08-02 13:59:19 +02:00
value_slot = value_slot - > absolutized ( viewport_rect ( ) , font_metrics , m_root_element_font_metrics ) ;
2019-09-30 20:25:33 +02:00
}
2024-01-12 12:39:40 +01:00
style . set_line_height ( { } , line_height ) ;
2021-09-21 11:38:18 +02:00
}
2019-09-30 20:25:33 +02:00
2024-12-20 11:32:17 +01:00
void StyleComputer : : resolve_effective_overflow_values ( ComputedProperties & style ) const
2024-02-04 15:27:04 +01:00
{
// https://www.w3.org/TR/css-overflow-3/#overflow-control
// The visible/clip values of overflow compute to auto/hidden (respectively) if one of overflow-x or
// overflow-y is neither visible nor clip.
2024-11-03 13:20:04 +01:00
auto overflow_x = keyword_to_overflow ( style . property ( PropertyID : : OverflowX ) . to_keyword ( ) ) ;
auto overflow_y = keyword_to_overflow ( style . property ( PropertyID : : OverflowY ) . to_keyword ( ) ) ;
2024-02-04 15:27:04 +01:00
auto overflow_x_is_visible_or_clip = overflow_x = = Overflow : : Visible | | overflow_x = = Overflow : : Clip ;
auto overflow_y_is_visible_or_clip = overflow_y = = Overflow : : Visible | | overflow_y = = Overflow : : Clip ;
if ( ! overflow_x_is_visible_or_clip | | ! overflow_y_is_visible_or_clip ) {
if ( overflow_x = = CSS : : Overflow : : Visible )
2024-08-14 14:06:03 +01:00
style . set_property ( CSS : : PropertyID : : OverflowX , CSSKeywordValue : : create ( Keyword : : Auto ) ) ;
2024-02-04 15:27:04 +01:00
if ( overflow_x = = CSS : : Overflow : : Clip )
2024-08-14 14:06:03 +01:00
style . set_property ( CSS : : PropertyID : : OverflowX , CSSKeywordValue : : create ( Keyword : : Hidden ) ) ;
2024-02-04 15:27:04 +01:00
if ( overflow_y = = CSS : : Overflow : : Visible )
2024-08-14 14:06:03 +01:00
style . set_property ( CSS : : PropertyID : : OverflowY , CSSKeywordValue : : create ( Keyword : : Auto ) ) ;
2024-02-04 15:27:04 +01:00
if ( overflow_y = = CSS : : Overflow : : Clip )
2024-08-14 14:06:03 +01:00
style . set_property ( CSS : : PropertyID : : OverflowY , CSSKeywordValue : : create ( Keyword : : Hidden ) ) ;
2024-02-04 15:27:04 +01:00
}
}
2025-03-20 16:56:46 +00:00
static void compute_text_align ( ComputedProperties & style , DOM : : Element const & element , Optional < PseudoElement > pseudo_element )
2025-02-03 16:06:07 +00:00
{
// https://drafts.csswg.org/css-text-4/#valdef-text-align-match-parent
// This value behaves the same as inherit (computes to its parent’ s computed value) except that an inherited
// value of start or end is interpreted against the parent’ s direction value and results in a computed value of
// either left or right. Computes to start when specified on the root element.
if ( style . property ( PropertyID : : TextAlign ) . to_keyword ( ) = = Keyword : : MatchParent ) {
// If it's a pseudo-element, then the "parent" is the originating element instead.
auto const * parent = [ & ] ( ) - > DOM : : Element const * {
if ( pseudo_element . has_value ( ) )
return & element ;
return element . parent_element ( ) ;
} ( ) ;
if ( parent ) {
auto const & parent_text_align = parent - > computed_properties ( ) - > property ( PropertyID : : TextAlign ) ;
2025-02-05 19:48:56 +01:00
auto const & parent_direction = parent - > computed_properties ( ) - > direction ( ) ;
2025-02-03 16:06:07 +00:00
switch ( parent_text_align . to_keyword ( ) ) {
case Keyword : : Start :
if ( parent_direction = = Direction : : Ltr ) {
style . set_property ( PropertyID : : TextAlign , CSSKeywordValue : : create ( Keyword : : Left ) ) ;
} else {
style . set_property ( PropertyID : : TextAlign , CSSKeywordValue : : create ( Keyword : : Right ) ) ;
}
break ;
case Keyword : : End :
if ( parent_direction = = Direction : : Ltr ) {
style . set_property ( PropertyID : : TextAlign , CSSKeywordValue : : create ( Keyword : : Right ) ) ;
} else {
style . set_property ( PropertyID : : TextAlign , CSSKeywordValue : : create ( Keyword : : Left ) ) ;
}
break ;
default :
style . set_property ( PropertyID : : TextAlign , parent_text_align ) ;
}
} else {
style . set_property ( PropertyID : : TextAlign , CSSKeywordValue : : create ( Keyword : : Start ) ) ;
}
}
}
2022-02-28 11:27:57 +01:00
enum class BoxTypeTransformation {
None ,
Blockify ,
Inlinify ,
} ;
2025-03-20 16:56:46 +00:00
static BoxTypeTransformation required_box_type_transformation ( ComputedProperties const & style , DOM : : Element const & element , Optional < CSS : : PseudoElement > const & pseudo_element )
2022-02-28 11:27:57 +01:00
{
2023-09-01 09:55:56 +02:00
// NOTE: We never blockify <br> elements. They are always inline.
// There is currently no way to express in CSS how a <br> element really behaves.
// Spec issue: https://github.com/whatwg/html/issues/2291
if ( is < HTML : : HTMLBRElement > ( element ) )
return BoxTypeTransformation : : None ;
2022-02-28 11:27:57 +01:00
// Absolute positioning or floating an element blockifies the box’ s display type. [CSS2]
2023-10-27 15:17:36 +02:00
if ( style . position ( ) = = CSS : : Positioning : : Absolute | | style . position ( ) = = CSS : : Positioning : : Fixed | | style . float_ ( ) ! = CSS : : Float : : None )
2022-02-28 11:27:57 +01:00
return BoxTypeTransformation : : Blockify ;
// FIXME: Containment in a ruby container inlinifies the box’ s display type, as described in [CSS-RUBY-1].
2023-04-27 15:38:50 +02:00
// NOTE: If we're computing style for a pseudo-element, the effective parent will be the originating element itself, not its parent.
2025-04-18 14:19:19 +12:00
auto parent = pseudo_element . has_value ( ) ? GC : : Ptr < DOM : : Element const > { & element } : element . parent_element ( ) ;
2023-04-27 15:38:50 +02:00
2022-02-28 11:34:15 +01:00
// A parent with a grid or flex display value blockifies the box’ s display type. [CSS-GRID-1] [CSS-FLEXBOX-1]
2024-12-20 16:35:12 +01:00
if ( parent & & parent - > computed_properties ( ) ) {
auto const & parent_display = parent - > computed_properties ( ) - > display ( ) ;
2022-02-28 11:34:15 +01:00
if ( parent_display . is_grid_inside ( ) | | parent_display . is_flex_inside ( ) )
return BoxTypeTransformation : : Blockify ;
}
2022-02-28 11:27:57 +01:00
return BoxTypeTransformation : : None ;
}
2022-01-24 14:41:48 +01:00
// https://drafts.csswg.org/css-display/#transformations
2025-03-20 16:56:46 +00:00
void StyleComputer : : transform_box_type_if_needed ( ComputedProperties & style , DOM : : Element const & element , Optional < CSS : : PseudoElement > pseudo_element ) const
2022-01-24 14:41:48 +01:00
{
// 2.7. Automatic Box Type Transformations
// Some layout effects require blockification or inlinification of the box type,
// which sets the box’ s computed outer display type to block or inline (respectively).
// (This has no effect on display types that generate no box at all, such as none or contents.)
auto display = style . display ( ) ;
2024-09-29 10:54:46 +02:00
if ( display . is_none ( ) | | ( display . is_contents ( ) & & ! element . is_document_element ( ) ) )
return ;
// https://drafts.csswg.org/css-display/#root
// The root element’ s display type is always blockified, and its principal box always establishes an independent formatting context.
if ( element . is_document_element ( ) & & ! display . is_block_outside ( ) ) {
style . set_property ( CSS : : PropertyID : : Display , DisplayStyleValue : : create ( Display : : from_short ( CSS : : Display : : Short : : Block ) ) ) ;
2022-02-28 11:27:57 +01:00
return ;
2024-09-29 10:54:46 +02:00
}
2022-01-24 14:41:48 +01:00
2023-05-03 14:59:44 +02:00
auto new_display = display ;
2023-09-07 16:03:20 +01:00
if ( display . is_math_inside ( ) ) {
// https://w3c.github.io/mathml-core/#new-display-math-value
// For elements that are not MathML elements, if the specified value of display is inline math or block math
// then the computed value is block flow and inline flow respectively.
2023-11-04 18:42:04 +01:00
if ( element . namespace_uri ( ) ! = Namespace : : MathML )
2023-09-07 16:03:20 +01:00
new_display = CSS : : Display { display . outside ( ) , CSS : : DisplayInside : : Flow } ;
// For the mtable element the computed value is block table and inline table respectively.
else if ( element . tag_name ( ) . equals_ignoring_ascii_case ( " mtable " sv ) )
new_display = CSS : : Display { display . outside ( ) , CSS : : DisplayInside : : Table } ;
// For the mtr element, the computed value is table-row.
else if ( element . tag_name ( ) . equals_ignoring_ascii_case ( " mtr " sv ) )
new_display = CSS : : Display { CSS : : DisplayInternal : : TableRow } ;
// For the mtd element, the computed value is table-cell.
else if ( element . tag_name ( ) . equals_ignoring_ascii_case ( " mtd " sv ) )
new_display = CSS : : Display { CSS : : DisplayInternal : : TableCell } ;
}
2022-02-28 11:27:57 +01:00
switch ( required_box_type_transformation ( style , element , pseudo_element ) ) {
case BoxTypeTransformation : : None :
break ;
case BoxTypeTransformation : : Blockify :
2023-05-03 14:59:44 +02:00
if ( display . is_block_outside ( ) )
return ;
// If a layout-internal box is blockified, its inner display type converts to flow so that it becomes a block container.
if ( display . is_internal ( ) ) {
new_display = CSS : : Display : : from_short ( CSS : : Display : : Short : : Block ) ;
} else {
VERIFY ( display . is_outside_and_inside ( ) ) ;
// For legacy reasons, if an inline block box (inline flow-root) is blockified, it becomes a block box (losing its flow-root nature).
// For consistency, a run-in flow-root box also blockifies to a block box.
if ( display . is_inline_block ( ) ) {
2023-09-04 17:39:15 +01:00
new_display = CSS : : Display { CSS : : DisplayOutside : : Block , CSS : : DisplayInside : : Flow , display . list_item ( ) } ;
2023-05-03 14:59:44 +02:00
} else {
2023-09-04 17:39:15 +01:00
new_display = CSS : : Display { CSS : : DisplayOutside : : Block , display . inside ( ) , display . list_item ( ) } ;
2023-05-03 14:59:44 +02:00
}
2022-07-19 15:31:01 +02:00
}
2022-02-28 11:27:57 +01:00
break ;
case BoxTypeTransformation : : Inlinify :
2023-05-03 14:59:44 +02:00
if ( display . is_inline_outside ( ) ) {
// FIXME: If an inline box (inline flow) is inlinified, it recursively inlinifies all of its in-flow children,
// so that no block-level descendants break up the inline formatting context in which it participates.
if ( display . is_flow_inside ( ) ) {
dbgln ( " FIXME: Inlinify inline box children recursively " ) ;
}
break ;
}
if ( display . is_internal ( ) ) {
// Inlinification has no effect on layout-internal boxes. (However, placement in such an inline context will typically cause them
// to be wrapped in an appropriately-typed anonymous inline-level box.)
} else {
VERIFY ( display . is_outside_and_inside ( ) ) ;
// If a block box (block flow) is inlinified, its inner display type is set to flow-root so that it remains a block container.
if ( display . is_block_outside ( ) & & display . is_flow_inside ( ) ) {
2023-09-04 17:39:15 +01:00
new_display = CSS : : Display { CSS : : DisplayOutside : : Inline , CSS : : DisplayInside : : FlowRoot , display . list_item ( ) } ;
2023-05-03 14:59:44 +02:00
}
2023-09-04 17:39:15 +01:00
new_display = CSS : : Display { CSS : : DisplayOutside : : Inline , display . inside ( ) , display . list_item ( ) } ;
2023-05-03 14:59:44 +02:00
}
2022-02-28 11:27:57 +01:00
break ;
}
2023-05-03 14:59:44 +02:00
if ( new_display ! = display )
2024-08-02 11:58:56 +02:00
style . set_property ( CSS : : PropertyID : : Display , DisplayStyleValue : : create ( new_display ) ) ;
2022-01-24 14:41:48 +01:00
}
2024-12-20 16:35:12 +01:00
GC : : Ref < ComputedProperties > StyleComputer : : create_document_style ( ) const
2021-09-23 19:48:41 +02:00
{
2024-12-20 16:35:12 +01:00
auto style = document ( ) . heap ( ) . allocate < CSS : : ComputedProperties > ( ) ;
2023-09-07 15:29:54 +01:00
compute_math_depth ( style , nullptr , { } ) ;
2022-02-24 15:54:12 +00:00
compute_font ( style , nullptr , { } ) ;
compute_defaulted_values ( style , nullptr , { } ) ;
2025-03-05 18:22:05 +01:00
absolutize_values ( style , nullptr ) ;
2024-12-20 16:35:12 +01:00
style - > set_property ( CSS : : PropertyID : : Width , CSS : : LengthStyleValue : : create ( CSS : : Length : : make_px ( viewport_rect ( ) . width ( ) ) ) ) ;
style - > set_property ( CSS : : PropertyID : : Height , CSS : : LengthStyleValue : : create ( CSS : : Length : : make_px ( viewport_rect ( ) . height ( ) ) ) ) ;
style - > set_property ( CSS : : PropertyID : : Display , CSS : : DisplayStyleValue : : create ( CSS : : Display : : from_short ( CSS : : Display : : Short : : Block ) ) ) ;
2021-09-23 19:48:41 +02:00
return style ;
}
2025-03-20 16:56:46 +00:00
GC : : Ref < ComputedProperties > StyleComputer : : compute_style ( DOM : : Element & element , Optional < CSS : : PseudoElement > pseudo_element ) const
2023-03-14 16:36:20 +01:00
{
2024-12-20 16:35:12 +01:00
return * compute_style_impl ( element , move ( pseudo_element ) , ComputeStyleMode : : Normal ) ;
2023-03-14 16:36:20 +01:00
}
2025-03-20 16:56:46 +00:00
GC : : Ptr < ComputedProperties > StyleComputer : : compute_pseudo_element_style_if_needed ( DOM : : Element & element , Optional < CSS : : PseudoElement > pseudo_element ) const
2023-03-14 16:36:20 +01:00
{
return compute_style_impl ( element , move ( pseudo_element ) , ComputeStyleMode : : CreatePseudoElementStyleIfNeeded ) ;
}
2025-03-20 16:56:46 +00:00
GC : : Ptr < ComputedProperties > StyleComputer : : compute_style_impl ( DOM : : Element & element , Optional < CSS : : PseudoElement > pseudo_element , ComputeStyleMode mode ) const
2021-09-21 11:38:18 +02:00
{
2022-02-10 17:49:50 +01:00
build_rule_cache_if_needed ( ) ;
2023-12-17 21:03:28 +01:00
// Special path for elements that use pseudo element as style selector
if ( element . use_pseudo_element ( ) . has_value ( ) ) {
2025-01-21 09:12:05 -05:00
auto & parent_element = as < HTML : : HTMLElement > ( * element . root ( ) . parent_or_shadow_host ( ) ) ;
2024-03-05 18:10:02 -07:00
auto style = compute_style ( parent_element , * element . use_pseudo_element ( ) ) ;
2023-12-17 21:03:28 +01:00
// Merge back inline styles
2024-10-19 17:03:56 +02:00
if ( auto inline_style = element . inline_style ( ) ) {
2023-12-17 21:03:28 +01:00
for ( auto const & property : inline_style - > properties ( ) )
2024-12-20 16:35:12 +01:00
style - > set_property ( property . property_id , property . value ) ;
2023-12-17 21:03:28 +01:00
}
return style ;
}
2024-09-30 11:18:55 +01:00
ScopeGuard guard { [ & element ] ( ) { element . set_needs_style_update ( false ) ; } } ;
2021-09-23 13:13:51 +02:00
// 1. Perform the cascade. This produces the "specified style"
2023-03-14 16:36:20 +01:00
bool did_match_any_pseudo_element_rules = false ;
2025-04-17 13:39:30 +02:00
PseudoClassBitmap attempted_pseudo_class_matches ;
2025-06-18 17:45:26 +12:00
auto logical_alias_mapping_context = compute_logical_alias_mapping_context ( element , pseudo_element , 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 ) ;
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
element . set_cascaded_properties ( pseudo_element , cascaded_properties ) ;
2023-03-14 16:36:20 +01:00
2024-09-10 14:25:03 +02:00
if ( mode = = ComputeStyleMode : : CreatePseudoElementStyleIfNeeded ) {
// NOTE: If we're computing style for a pseudo-element, we look for a number of reasons to bail early.
// Bail if no pseudo-element rules matched.
if ( ! did_match_any_pseudo_element_rules )
2024-10-26 17:42:27 +02:00
return { } ;
2024-09-10 14:25:03 +02:00
// Bail if no pseudo-element would be generated due to...
// - content: none
// - content: normal (for ::before and ::after)
bool content_is_normal = false ;
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
if ( auto content_value = cascaded_properties - > property ( CSS : : PropertyID : : Content ) ) {
2024-09-10 14:25:03 +02:00
if ( content_value - > is_keyword ( ) ) {
auto content = content_value - > as_keyword ( ) . keyword ( ) ;
if ( content = = CSS : : Keyword : : None )
2024-10-26 17:42:27 +02:00
return { } ;
2024-09-10 14:25:03 +02:00
content_is_normal = content = = CSS : : Keyword : : Normal ;
} else {
content_is_normal = false ;
}
} else {
// NOTE: `normal` is the initial value, so the absence of a value is treated as `normal`.
content_is_normal = true ;
}
2025-03-20 16:56:46 +00:00
if ( content_is_normal & & first_is_one_of ( * pseudo_element , CSS : : PseudoElement : : Before , CSS : : PseudoElement : : After ) ) {
2024-10-26 17:42:27 +02:00
return { } ;
2024-09-10 14:25:03 +02:00
}
}
2021-09-23 13:13:51 +02:00
2025-01-03 20:39:25 +03:00
auto computed_properties = compute_properties ( element , pseudo_element , cascaded_properties ) ;
2025-04-17 13:39:30 +02:00
computed_properties - > set_attempted_pseudo_class_matches ( attempted_pseudo_class_matches ) ;
2025-01-03 20:39:25 +03:00
return computed_properties ;
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
}
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
static bool is_monospace ( CSSStyleValue const & value )
{
if ( value . to_keyword ( ) = = Keyword : : Monospace )
return true ;
if ( value . is_value_list ( ) ) {
auto const & values = value . as_value_list ( ) . values ( ) ;
if ( values . size ( ) = = 1 & & values [ 0 ] - > to_keyword ( ) = = Keyword : : Monospace )
return true ;
}
return false ;
}
// HACK: This function implements time-travelling inheritance for the font-size property
// in situations where the cascade ended up with `font-family: monospace`.
// In such cases, other browsers will magically change the meaning of keyword font sizes
// *even in earlier stages of the cascade!!* to be relative to the default monospace font size (13px)
// instead of the default font size (16px).
// See this blog post for a lot more details about this weirdness:
// https://manishearth.github.io/blog/2017/08/10/font-size-an-unexpectedly-complex-css-property/
2025-04-15 15:18:27 -06:00
RefPtr < CSSStyleValue const > StyleComputer : : recascade_font_size_if_needed (
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
DOM : : Element & element ,
2025-03-20 16:56:46 +00:00
Optional < CSS : : PseudoElement > pseudo_element ,
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
CascadedProperties & cascaded_properties ) const
{
// Check for `font-family: monospace`. Note that `font-family: monospace, AnythingElse` does not trigger this path.
// Some CSS frameworks use `font-family: monospace, monospace` to work around this behavior.
auto font_family_value = cascaded_properties . property ( CSS : : PropertyID : : FontFamily ) ;
if ( ! font_family_value | | ! is_monospace ( * font_family_value ) )
return nullptr ;
// FIXME: This should be configurable.
constexpr CSSPixels default_monospace_font_size_in_px = 13 ;
static auto monospace_font_family_name = Platform : : FontPlugin : : the ( ) . generic_font_name ( Platform : : GenericFont : : Monospace ) ;
static auto monospace_font = Gfx : : FontDatabase : : the ( ) . get ( monospace_font_family_name , default_monospace_font_size_in_px * 0.75f , 400 , Gfx : : FontWidth : : Normal , 0 ) ;
// Reconstruct the line of ancestor elements we need to inherit style from, and then do the cascade again
// but only for the font-size property.
Vector < DOM : : Element & > ancestors ;
if ( pseudo_element . has_value ( ) )
ancestors . append ( element ) ;
2025-04-18 14:19:19 +12:00
for ( auto ancestor = element . parent_element ( ) ; ancestor ; ancestor = ancestor - > parent_element ( ) )
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
ancestors . append ( * ancestor ) ;
2025-04-15 15:18:27 -06:00
NonnullRefPtr < CSSStyleValue const > new_font_size = CSS : : LengthStyleValue : : create ( CSS : : Length : : make_px ( default_monospace_font_size_in_px ) ) ;
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
CSSPixels current_size_in_px = default_monospace_font_size_in_px ;
for ( auto & ancestor : ancestors . in_reverse ( ) ) {
auto & ancestor_cascaded_properties = * ancestor . cascaded_properties ( { } ) ;
auto font_size_value = ancestor_cascaded_properties . property ( CSS : : PropertyID : : FontSize ) ;
if ( ! font_size_value )
continue ;
if ( font_size_value - > is_initial ( ) | | font_size_value - > is_unset ( ) ) {
current_size_in_px = default_monospace_font_size_in_px ;
continue ;
}
if ( font_size_value - > is_inherit ( ) ) {
// Do nothing.
continue ;
}
if ( font_size_value - > is_keyword ( ) ) {
current_size_in_px = default_monospace_font_size_in_px * absolute_size_mapping ( font_size_value - > to_keyword ( ) ) ;
continue ;
}
if ( font_size_value - > is_percentage ( ) ) {
current_size_in_px = CSSPixels : : nearest_value_for ( font_size_value - > as_percentage ( ) . percentage ( ) . as_fraction ( ) * current_size_in_px ) ;
continue ;
}
if ( font_size_value - > is_calculated ( ) ) {
dbgln ( " FIXME: Support calc() when time-traveling for monospace font-size " ) ;
continue ;
}
VERIFY ( font_size_value - > is_length ( ) ) ;
current_size_in_px = font_size_value - > as_length ( ) . length ( ) . to_px ( viewport_rect ( ) , Length : : FontMetrics { current_size_in_px , monospace_font - > with_size ( current_size_in_px * 0.75f ) - > pixel_metrics ( ) } , m_root_element_font_metrics ) ;
} ;
return CSS : : LengthStyleValue : : create ( CSS : : Length : : make_px ( current_size_in_px ) ) ;
}
2025-03-20 16:56:46 +00:00
GC : : Ref < ComputedProperties > StyleComputer : : compute_properties ( DOM : : Element & element , Optional < PseudoElement > pseudo_element , CascadedProperties & cascaded_properties ) 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
{
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
DOM : : AbstractElement abstract_element { element , pseudo_element } ;
2024-12-20 16:35:12 +01:00
auto computed_style = document ( ) . heap ( ) . allocate < CSS : : ComputedProperties > ( ) ;
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
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
auto new_font_size = recascade_font_size_if_needed ( element , pseudo_element , cascaded_properties ) ;
if ( new_font_size )
computed_style - > set_property ( PropertyID : : FontSize , * new_font_size , ComputedProperties : : Inherited : : No , Important : : No ) ;
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
for ( auto i = to_underlying ( first_longhand_property_id ) ; i < = to_underlying ( last_longhand_property_id ) ; + + i ) {
auto property_id = static_cast < CSS : : PropertyID > ( i ) ;
auto value = cascaded_properties . property ( property_id ) ;
2024-12-22 11:56:54 +01:00
auto inherited = ComputedProperties : : Inherited : : No ;
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
// NOTE: We've already handled font-size above.
if ( property_id = = PropertyID : : FontSize & & ! value & & new_font_size )
continue ;
2025-06-18 17:45:26 +12:00
// FIXME: Logical properties should inherit from their parent's equivalent unmapped logical property.
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
if ( ( ! value & & is_inherited_property ( property_id ) )
| | ( value & & value - > is_inherit ( ) ) ) {
if ( auto inheritance_parent = element_to_inherit_style_from ( & element , pseudo_element ) ) {
2024-12-20 16:35:12 +01:00
value = inheritance_parent - > computed_properties ( ) - > property ( property_id ) ;
2024-12-22 11:56:54 +01:00
inherited = ComputedProperties : : Inherited : : Yes ;
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
} else {
value = property_initial_value ( property_id ) ;
}
}
if ( ! value | | value - > is_initial ( ) )
value = property_initial_value ( property_id ) ;
if ( value - > is_unset ( ) ) {
if ( is_inherited_property ( property_id ) )
value = CSSKeywordValue : : create ( Keyword : : Inherit ) ;
else
value = CSSKeywordValue : : create ( Keyword : : Initial ) ;
}
2024-12-22 11:56:54 +01:00
computed_style - > set_property ( property_id , value . release_nonnull ( ) , inherited ) ;
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
if ( property_id = = PropertyID : : AnimationName ) {
2024-12-20 16:35:12 +01:00
computed_style - > set_animation_name_source ( cascaded_properties . property_source ( property_id ) ) ;
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
}
if ( property_id = = PropertyID : : TransitionProperty ) {
2024-12-20 16:35:12 +01:00
computed_style - > set_transition_property_source ( cascaded_properties . property_source ( property_id ) ) ;
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
}
}
// Animation declarations [css-animations-2]
auto animation_name = [ & ] ( ) - > Optional < String > {
2024-12-20 16:35:12 +01:00
auto const animation_name = computed_style - > maybe_null_property ( PropertyID : : AnimationName ) ;
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
if ( ! animation_name )
return OptionalNone { } ;
2025-05-27 17:09:39 +02:00
if ( animation_name - > is_keyword ( ) & & animation_name - > to_keyword ( ) = = Keyword : : None )
return OptionalNone { } ;
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
if ( animation_name - > is_string ( ) )
return animation_name - > as_string ( ) . string_value ( ) . to_string ( ) ;
2025-05-16 19:20:24 +01:00
return animation_name - > to_string ( SerializationMode : : Normal ) ;
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
} ( ) ;
if ( animation_name . has_value ( ) ) {
2024-12-20 16:35:12 +01:00
if ( auto source_declaration = computed_style - > animation_name_source ( ) ) {
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
auto & realm = element . realm ( ) ;
if ( source_declaration ! = element . cached_animation_name_source ( pseudo_element ) ) {
// This animation name is new, so we need to create a new animation for it.
if ( auto existing_animation = element . cached_animation_name_animation ( pseudo_element ) )
existing_animation - > cancel ( Animations : : Animation : : ShouldInvalidate : : No ) ;
element . set_cached_animation_name_source ( source_declaration , pseudo_element ) ;
auto effect = Animations : : KeyframeEffect : : create ( realm ) ;
auto animation = CSSAnimation : : create ( realm ) ;
animation - > set_id ( animation_name . release_value ( ) ) ;
animation - > set_timeline ( m_document - > timeline ( ) ) ;
animation - > set_owning_element ( element ) ;
animation - > set_effect ( effect ) ;
apply_animation_properties ( m_document , cascaded_properties , animation ) ;
if ( pseudo_element . has_value ( ) )
2025-03-20 16:56:46 +00:00
effect - > set_pseudo_element ( Selector : : PseudoElementSelector { pseudo_element . value ( ) } ) ;
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-01-27 18:28:48 +01:00
if ( auto * rule_cache = rule_cache_for_cascade_origin ( CascadeOrigin : : Author , { } , { } ) ) {
if ( auto keyframe_set = rule_cache - > rules_by_animation_keyframes . get ( animation - > id ( ) ) ; keyframe_set . has_value ( ) )
effect - > set_key_frame_set ( keyframe_set . value ( ) ) ;
}
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
effect - > set_target ( & element ) ;
element . set_cached_animation_name_animation ( animation , pseudo_element ) ;
} else {
// The animation hasn't changed, but some properties of the animation may have
2024-12-22 21:29:02 -05:00
if ( auto animation = element . cached_animation_name_animation ( pseudo_element ) ; animation )
apply_animation_properties ( m_document , cascaded_properties , * animation ) ;
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
}
}
} else {
// If the element had an existing animation, cancel it
if ( auto existing_animation = element . cached_animation_name_animation ( pseudo_element ) ) {
existing_animation - > cancel ( Animations : : Animation : : ShouldInvalidate : : No ) ;
element . set_cached_animation_name_animation ( { } , pseudo_element ) ;
element . set_cached_animation_name_source ( { } , pseudo_element ) ;
}
}
auto animations = element . get_animations_internal ( Animations : : GetAnimationsOptions { . subtree = false } ) ;
if ( animations . is_exception ( ) ) {
dbgln ( " Error getting animations for element {} " , element . debug_description ( ) ) ;
} else {
for ( auto & animation : animations . value ( ) ) {
if ( auto effect = animation - > effect ( ) ; effect & & effect - > is_keyframe_effect ( ) ) {
auto & keyframe_effect = * static_cast < Animations : : KeyframeEffect * > ( effect . ptr ( ) ) ;
if ( keyframe_effect . pseudo_element_type ( ) = = pseudo_element )
collect_animation_into ( element , pseudo_element , keyframe_effect , computed_style ) ;
}
}
}
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
// Compute the value of custom properties
compute_custom_properties ( computed_style , abstract_element ) ;
2023-09-07 15:29:54 +01:00
// 2. Compute the math-depth property, since that might affect the font-size
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
compute_math_depth ( computed_style , & element , pseudo_element ) ;
2023-09-07 15:29:54 +01:00
// 3. Compute the font, since that may be needed for font-relative CSS units
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
compute_font ( computed_style , & element , pseudo_element ) ;
2021-09-23 13:13:51 +02:00
2023-09-07 15:29:54 +01:00
// 4. Absolutize values, turning font/viewport relative lengths into absolute lengths
2025-03-05 18:22:05 +01:00
absolutize_values ( computed_style , element ) ;
2021-09-23 13:13:51 +02:00
2023-09-07 15:29:54 +01:00
// 5. Default the values, applying inheritance and 'initial' as needed
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
compute_defaulted_values ( computed_style , & element , pseudo_element ) ;
2021-09-23 13:13:51 +02:00
2023-09-07 15:29:54 +01:00
// 6. Run automatic box type transformations
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
transform_box_type_if_needed ( computed_style , element , pseudo_element ) ;
2022-01-24 14:41:48 +01:00
2025-02-03 16:06:07 +00:00
// 7. Apply any property-specific computed value logic
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
resolve_effective_overflow_values ( computed_style ) ;
2025-02-03 16:06:07 +00:00
compute_text_align ( computed_style , element , pseudo_element ) ;
2024-02-04 15:27:04 +01:00
2024-03-07 21:27:37 +01:00
// 8. Let the element adjust computed style
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
element . adjust_computed_style ( computed_style ) ;
2024-03-07 21:27:37 +01:00
2024-09-19 14:14:13 +01:00
// 9. Transition declarations [css-transitions-1]
// Theoretically this should be part of the cascade, but it works with computed values, which we don't have until now.
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
compute_transitioned_properties ( computed_style , element , pseudo_element ) ;
2024-12-20 16:35:12 +01:00
if ( auto previous_style = element . computed_properties ( ) ; previous_style ) {
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
start_needed_transitions ( * previous_style , computed_style , element , pseudo_element ) ;
2024-09-19 14:13:20 +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
return computed_style ;
2019-06-27 17:47:59 +02:00
}
2021-12-03 20:00:31 +00:00
2022-02-10 17:49:50 +01:00
void StyleComputer : : build_rule_cache_if_needed ( ) const
{
2025-01-24 10:29:51 +01:00
if ( has_valid_rule_cache ( ) )
2022-02-10 17:49:50 +01:00
return ;
const_cast < StyleComputer & > ( * this ) . build_rule_cache ( ) ;
}
2024-09-09 14:59:09 +02:00
struct SimplifiedSelectorForBucketing {
CSS : : Selector : : SimpleSelector : : Type type ;
FlyString name ;
} ;
static Optional < SimplifiedSelectorForBucketing > is_roundabout_selector_bucketable_as_something_simpler ( CSS : : Selector : : SimpleSelector const & simple_selector )
2024-09-09 10:44:54 +02:00
{
if ( simple_selector . type ! = CSS : : Selector : : SimpleSelector : : Type : : PseudoClass )
return { } ;
if ( simple_selector . pseudo_class ( ) . type ! = CSS : : PseudoClass : : Is
& & simple_selector . pseudo_class ( ) . type ! = CSS : : PseudoClass : : Where )
return { } ;
if ( simple_selector . pseudo_class ( ) . argument_selector_list . size ( ) ! = 1 )
return { } ;
auto const & argument_selector = * simple_selector . pseudo_class ( ) . argument_selector_list . first ( ) ;
auto const & compound_selector = argument_selector . compound_selectors ( ) . last ( ) ;
if ( compound_selector . simple_selectors . size ( ) ! = 1 )
return { } ;
auto const & inner_simple_selector = compound_selector . simple_selectors . first ( ) ;
2024-09-09 14:59:09 +02:00
if ( inner_simple_selector . type = = CSS : : Selector : : SimpleSelector : : Type : : Class
| | inner_simple_selector . type = = CSS : : Selector : : SimpleSelector : : Type : : Id ) {
return SimplifiedSelectorForBucketing { inner_simple_selector . type , inner_simple_selector . name ( ) } ;
}
if ( inner_simple_selector . type = = CSS : : Selector : : SimpleSelector : : Type : : TagName ) {
return SimplifiedSelectorForBucketing { inner_simple_selector . type , inner_simple_selector . qualified_name ( ) . name . lowercase_name } ;
}
2024-09-09 10:44:54 +02:00
2024-09-09 14:59:09 +02:00
return { } ;
2024-09-09 10:44:54 +02:00
}
2024-12-23 15:22:10 +01:00
void StyleComputer : : collect_selector_insights ( Selector const & selector , SelectorInsights & insights )
2024-10-27 12:13:05 +01:00
{
for ( auto const & compound_selector : selector . compound_selectors ( ) ) {
for ( auto const & simple_selector : compound_selector . simple_selectors ) {
2024-12-23 15:22:10 +01:00
if ( simple_selector . type = = Selector : : SimpleSelector : : Type : : PseudoClass ) {
if ( simple_selector . pseudo_class ( ) . type = = PseudoClass : : Has ) {
insights . has_has_selectors = true ;
}
for ( auto const & argument_selector : simple_selector . pseudo_class ( ) . argument_selector_list ) {
collect_selector_insights ( * argument_selector , insights ) ;
}
2024-10-27 12:13:05 +01:00
}
}
}
}
2025-02-20 21:53:31 +01:00
void StyleComputer : : make_rule_cache_for_cascade_origin ( CascadeOrigin cascade_origin , SelectorInsights & insights )
2022-02-10 17:49:50 +01:00
{
Vector < MatchingRule > matching_rules ;
size_t style_sheet_index = 0 ;
2024-11-15 04:01:23 +13:00
for_each_stylesheet ( cascade_origin , [ & ] ( auto & sheet , GC : : Ptr < DOM : : ShadowRoot > shadow_root ) {
2025-01-27 18:28:48 +01:00
auto & rule_caches = [ & ] - > RuleCaches & {
RuleCachesForDocumentAndShadowRoots * rule_caches_for_document_or_shadow_root = nullptr ;
switch ( cascade_origin ) {
case CascadeOrigin : : Author :
rule_caches_for_document_or_shadow_root = m_author_rule_cache ;
break ;
case CascadeOrigin : : User :
rule_caches_for_document_or_shadow_root = m_user_rule_cache ;
break ;
case CascadeOrigin : : UserAgent :
rule_caches_for_document_or_shadow_root = m_user_agent_rule_cache ;
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
if ( ! shadow_root )
return rule_caches_for_document_or_shadow_root - > for_document ;
return * rule_caches_for_document_or_shadow_root - > for_shadow_roots . ensure ( * shadow_root , [ ] { return make < RuleCaches > ( ) ; } ) ;
} ( ) ;
2022-02-10 17:49:50 +01:00
size_t rule_index = 0 ;
2024-10-17 13:48:00 +01:00
sheet . for_each_effective_style_producing_rule ( [ & ] ( auto const & rule ) {
SelectorList const & absolutized_selectors = [ & ] ( ) {
if ( rule . type ( ) = = CSSRule : : Type : : Style )
return static_cast < CSSStyleRule const & > ( rule ) . absolutized_selectors ( ) ;
if ( rule . type ( ) = = CSSRule : : Type : : NestedDeclarations )
2024-11-06 17:43:30 +00:00
return static_cast < CSSNestedDeclarations const & > ( rule ) . parent_style_rule ( ) . absolutized_selectors ( ) ;
2024-10-17 13:48:00 +01:00
VERIFY_NOT_REACHED ( ) ;
} ( ) ;
2025-01-12 18:38:05 +03:00
for ( auto const & selector : absolutized_selectors ) {
m_style_invalidation_data - > build_invalidation_sets_for_selector ( selector ) ;
}
2024-10-17 13:48:00 +01:00
for ( CSS : : Selector const & selector : absolutized_selectors ) {
2023-03-09 19:27:23 +01:00
MatchingRule matching_rule {
2023-03-19 17:01:26 +01:00
shadow_root ,
2023-03-09 19:27:23 +01:00
& rule ,
2023-07-29 11:51:15 -05:00
sheet ,
2025-01-26 13:58:28 +01:00
sheet . default_namespace ( ) ,
2025-01-25 19:57:11 +01:00
selector ,
2023-03-09 19:27:23 +01:00
style_sheet_index ,
rule_index ,
2024-03-18 16:01:47 +01:00
selector . specificity ( ) ,
cascade_origin ,
2024-03-19 10:36:08 +01:00
false ,
2023-03-09 19:27:23 +01:00
} ;
2022-02-10 17:49:50 +01:00
2025-01-24 10:27:52 +01:00
auto const & qualified_layer_name = matching_rule . qualified_layer_name ( ) ;
2025-01-27 18:28:48 +01:00
auto & rule_cache = qualified_layer_name . is_empty ( ) ? rule_caches . main : * rule_caches . by_layer . ensure ( qualified_layer_name , [ ] { return make < RuleCache > ( ) ; } ) ;
2025-01-24 10:27:52 +01:00
2024-09-09 13:52:07 +02:00
bool contains_root_pseudo_class = false ;
2025-03-20 16:56:46 +00:00
Optional < CSS : : PseudoElement > pseudo_element ;
2024-09-09 13:52:07 +02:00
2024-12-23 15:22:10 +01:00
collect_selector_insights ( selector , insights ) ;
2024-10-27 12:13:05 +01:00
2022-02-10 17:49:50 +01:00
for ( auto const & simple_selector : selector . compound_selectors ( ) . last ( ) . simple_selectors ) {
2024-03-13 11:47:25 +01:00
if ( ! matching_rule . contains_pseudo_element ) {
if ( simple_selector . type = = CSS : : Selector : : SimpleSelector : : Type : : PseudoElement ) {
matching_rule . contains_pseudo_element = true ;
2024-09-10 15:18:29 +02:00
pseudo_element = simple_selector . pseudo_element ( ) . type ( ) ;
2024-03-13 11:47:25 +01:00
}
}
2024-09-09 13:52:07 +02:00
if ( ! contains_root_pseudo_class ) {
2024-03-13 11:47:25 +01:00
if ( simple_selector . type = = CSS : : Selector : : SimpleSelector : : Type : : PseudoClass
& & simple_selector . pseudo_class ( ) . type = = CSS : : PseudoClass : : Root ) {
2024-09-09 13:52:07 +02:00
contains_root_pseudo_class = true ;
2024-03-13 11:47:25 +01:00
}
2022-02-10 18:50:58 +01:00
}
2022-02-25 17:50:06 +00:00
}
2023-03-09 19:27:23 +01:00
2025-04-17 13:39:30 +02:00
for ( size_t i = 0 ; i < to_underlying ( PseudoClass : : __Count ) ; + + i ) {
auto pseudo_class = static_cast < PseudoClass > ( i ) ;
// If we're not building a rule cache for this pseudo class, just ignore it.
if ( ! m_pseudo_class_rule_cache [ i ] )
continue ;
if ( selector . contains_pseudo_class ( pseudo_class ) ) {
// For pseudo class rule caches we intentionally pass no pseudo-element, because we don't want to bucket pseudo class rules by pseudo-element type.
m_pseudo_class_rule_cache [ i ] - > add_rule ( matching_rule , { } , contains_root_pseudo_class ) ;
}
2025-01-04 18:09:21 +03:00
}
2025-04-17 13:39:30 +02:00
2025-02-20 21:07:22 +01:00
rule_cache . add_rule ( matching_rule , pseudo_element , contains_root_pseudo_class ) ;
2022-02-10 17:49:50 +01:00
}
+ + rule_index ;
} ) ;
2023-05-26 23:30:54 +03:30
2024-02-22 13:56:15 +00:00
// Loosely based on https://drafts.csswg.org/css-animations-2/#keyframe-processing
2023-05-26 23:30:54 +03:30
sheet . for_each_effective_keyframes_at_rule ( [ & ] ( CSSKeyframesRule const & rule ) {
2024-02-22 13:56:15 +00:00
auto keyframe_set = adopt_ref ( * new Animations : : KeyframeEffect : : KeyFrameSet ) ;
HashTable < PropertyID > animated_properties ;
2023-05-26 23:30:54 +03:30
// Forwards pass, resolve all the user-specified keyframe properties.
2024-06-14 17:04:56 +02:00
for ( auto const & keyframe_rule : * rule . css_rules ( ) ) {
2025-01-21 09:12:05 -05:00
auto const & keyframe = as < CSSKeyframeRule > ( * keyframe_rule ) ;
2024-02-22 13:56:15 +00:00
Animations : : KeyframeEffect : : KeyFrameSet : : ResolvedKeyFrame resolved_keyframe ;
2023-05-26 23:30:54 +03:30
2024-06-14 17:04:56 +02:00
auto key = static_cast < u64 > ( keyframe . key ( ) . value ( ) * Animations : : KeyframeEffect : : AnimationKeyFrameKeyScaleFactor ) ;
LibWeb/CSS: Merge style declaration subclasses into CSSStyleProperties
We previously had PropertyOwningCSSStyleDeclaration and
ResolvedCSSStyleDeclaration, representing the current style properties
and resolved style respectively. Both of these were the
CSSStyleDeclaration type in the CSSOM. (We also had
ElementInlineCSSStyleDeclaration but I removed that in a previous
commit.)
In the meantime, the spec has changed so that these should now be a new
CSSStyleProperties type in the CSSOM. Also, we need to subclass
CSSStyleDeclaration for things like CSSFontFaceRule's list of
descriptors, which means it wouldn't hold style properties.
So, this commit does the fairly messy work of combining these two types
into a new CSSStyleProperties class. A lot of what previously was done
as separate methods in the two classes, now follows the spec steps of
"if the readonly flag is set, do X" instead, which is hopefully easier
to follow too.
There is still some functionality in CSSStyleDeclaration that belongs in
CSSStyleProperties, but I'll do that next. To avoid a huge diff for
"CSSStyleDeclaration-all-supported-properties-and-default-values.txt"
both here and in the following commit, we don't apply the (currently
empty) CSSStyleProperties prototype yet.
2025-03-17 17:50:49 +00:00
auto const & keyframe_style = * keyframe . style ( ) ;
2024-02-27 16:53:35 -07:00
for ( auto const & it : keyframe_style . properties ( ) ) {
2024-03-17 17:29:31 -07:00
// Unresolved properties will be resolved in collect_animation_into()
2025-05-07 13:45:55 +01:00
for_each_property_expanding_shorthands ( it . property_id , it . value , [ & ] ( PropertyID shorthand_id , CSSStyleValue const & shorthand_value ) {
2024-02-27 16:53:35 -07:00
animated_properties . set ( shorthand_id ) ;
2024-08-14 11:10:54 +01:00
resolved_keyframe . properties . set ( shorthand_id , NonnullRefPtr < CSSStyleValue const > { shorthand_value } ) ;
2024-02-27 16:53:35 -07:00
} ) ;
2023-05-26 23:30:54 +03:30
}
2024-02-22 13:56:15 +00:00
keyframe_set - > keyframes_by_key . insert ( key , resolved_keyframe ) ;
2023-05-26 23:30:54 +03:30
}
2024-02-22 13:56:15 +00:00
Animations : : KeyframeEffect : : generate_initial_and_final_frames ( keyframe_set , animated_properties ) ;
2023-05-26 23:30:54 +03:30
if constexpr ( LIBWEB_CSS_DEBUG ) {
dbgln ( " Resolved keyframe set '{}' into {} keyframes: " , rule . name ( ) , keyframe_set - > keyframes_by_key . size ( ) ) ;
2024-02-22 13:56:15 +00:00
for ( auto it = keyframe_set - > keyframes_by_key . begin ( ) ; it ! = keyframe_set - > keyframes_by_key . end ( ) ; + + it )
2024-03-17 17:22:12 -07:00
dbgln ( " - keyframe {}: {} properties " , it . key ( ) , it - > properties . size ( ) ) ;
2023-05-26 23:30:54 +03:30
}
2025-01-27 18:28:48 +01:00
rule_caches . main . rules_by_animation_keyframes . set ( rule . name ( ) , move ( keyframe_set ) ) ;
2023-05-26 23:30:54 +03:30
} ) ;
2022-02-10 17:49:50 +01:00
+ + style_sheet_index ;
} ) ;
2023-03-07 20:13:13 +01:00
}
2024-09-04 17:43:18 +01:00
struct LayerNode {
OrderedHashMap < FlyString , LayerNode > children { } ;
} ;
static void flatten_layer_names_tree ( Vector < FlyString > & layer_names , StringView const & parent_qualified_name , FlyString const & name , LayerNode const & node )
{
FlyString qualified_name = parent_qualified_name . is_empty ( ) ? name : MUST ( String : : formatted ( " {}.{} " , parent_qualified_name , name ) ) ;
for ( auto const & item : node . children )
flatten_layer_names_tree ( layer_names , qualified_name , item . key , item . value ) ;
layer_names . append ( qualified_name ) ;
}
void StyleComputer : : build_qualified_layer_names_cache ( )
{
LayerNode root ;
auto insert_layer_name = [ & ] ( FlyString const & internal_qualified_name ) {
auto * node = & root ;
internal_qualified_name . bytes_as_string_view ( )
. for_each_split_view ( ' . ' , SplitBehavior : : Nothing , [ & ] ( StringView part ) {
auto local_name = MUST ( FlyString : : from_utf8 ( part ) ) ;
node = & node - > children . ensure ( local_name ) ;
} ) ;
} ;
// Walk all style sheets, identifying when we first see a @layer name, and add its qualified name to the list.
// TODO: Separate the light and shadow-dom layers.
2024-11-15 04:01:23 +13:00
for_each_stylesheet ( CascadeOrigin : : Author , [ & ] ( auto & sheet , GC : : Ptr < DOM : : ShadowRoot > ) {
2024-09-04 17:43:18 +01:00
// NOTE: Postorder so that a @layer block is iterated after its children,
// because we want those children to occur before it in the list.
sheet . for_each_effective_rule ( TraversalOrder : : Postorder , [ & ] ( auto & rule ) {
switch ( rule . type ( ) ) {
case CSSRule : : Type : : Import :
// TODO: Handle `layer(foo)` in import rules once we implement that.
break ;
case CSSRule : : Type : : LayerBlock : {
auto & layer_block = static_cast < CSSLayerBlockRule const & > ( rule ) ;
insert_layer_name ( layer_block . internal_qualified_name ( { } ) ) ;
break ;
}
case CSSRule : : Type : : LayerStatement : {
auto & layer_statement = static_cast < CSSLayerStatementRule const & > ( rule ) ;
auto qualified_names = layer_statement . internal_qualified_name_list ( { } ) ;
for ( auto & name : qualified_names )
insert_layer_name ( name ) ;
break ;
}
// Ignore everything else
case CSSRule : : Type : : Style :
case CSSRule : : Type : : Media :
case CSSRule : : Type : : FontFace :
case CSSRule : : Type : : Keyframes :
case CSSRule : : Type : : Keyframe :
2025-05-15 11:48:56 +01:00
case CSSRule : : Type : : Margin :
2024-09-04 17:43:18 +01:00
case CSSRule : : Type : : Namespace :
2024-10-15 15:59:31 +01:00
case CSSRule : : Type : : NestedDeclarations :
2025-05-13 12:17:41 +01:00
case CSSRule : : Type : : Page :
2024-10-17 23:28:09 +01:00
case CSSRule : : Type : : Property :
2025-05-13 12:17:41 +01:00
case CSSRule : : Type : : Supports :
2024-09-04 17:43:18 +01:00
break ;
}
} ) ;
} ) ;
// Now, produce a flat list of qualified names to use later
m_qualified_layer_names_in_order . clear ( ) ;
flatten_layer_names_tree ( m_qualified_layer_names_in_order , " " sv , { } , root ) ;
}
2023-03-07 20:13:13 +01:00
void StyleComputer : : build_rule_cache ( )
{
2025-01-27 18:28:48 +01:00
m_author_rule_cache = make < RuleCachesForDocumentAndShadowRoots > ( ) ;
m_user_rule_cache = make < RuleCachesForDocumentAndShadowRoots > ( ) ;
m_user_agent_rule_cache = make < RuleCachesForDocumentAndShadowRoots > ( ) ;
2024-12-23 15:22:10 +01:00
m_selector_insights = make < SelectorInsights > ( ) ;
2025-01-12 18:38:05 +03:00
m_style_invalidation_data = make < StyleInvalidationData > ( ) ;
2024-12-23 15:22:10 +01:00
2023-12-15 15:41:28 +01:00
if ( auto user_style_source = document ( ) . page ( ) . user_style ( ) ; user_style_source . has_value ( ) ) {
2025-02-05 12:08:27 +00:00
m_user_style_sheet = GC : : make_root ( parse_css_stylesheet ( CSS : : Parser : : ParsingParams ( document ( ) ) , user_style_source . value ( ) ) ) ;
2023-08-21 15:50:01 +01:00
}
2024-09-04 17:43:18 +01:00
build_qualified_layer_names_cache ( ) ;
2025-04-17 13:39:30 +02:00
m_pseudo_class_rule_cache [ to_underlying ( PseudoClass : : Hover ) ] = make < RuleCache > ( ) ;
m_pseudo_class_rule_cache [ to_underlying ( PseudoClass : : Active ) ] = make < RuleCache > ( ) ;
m_pseudo_class_rule_cache [ to_underlying ( PseudoClass : : Focus ) ] = make < RuleCache > ( ) ;
m_pseudo_class_rule_cache [ to_underlying ( PseudoClass : : FocusWithin ) ] = make < RuleCache > ( ) ;
m_pseudo_class_rule_cache [ to_underlying ( PseudoClass : : FocusVisible ) ] = make < RuleCache > ( ) ;
m_pseudo_class_rule_cache [ to_underlying ( PseudoClass : : Target ) ] = make < RuleCache > ( ) ;
2025-02-20 21:53:31 +01:00
make_rule_cache_for_cascade_origin ( CascadeOrigin : : Author , * m_selector_insights ) ;
make_rule_cache_for_cascade_origin ( CascadeOrigin : : User , * m_selector_insights ) ;
make_rule_cache_for_cascade_origin ( CascadeOrigin : : UserAgent , * m_selector_insights ) ;
2022-02-10 17:49:50 +01:00
}
void StyleComputer : : invalidate_rule_cache ( )
{
2023-03-07 20:13:13 +01:00
m_author_rule_cache = nullptr ;
2023-08-21 15:50:01 +01:00
// NOTE: We could be smarter about keeping the user rule cache, and style sheet.
// Currently we are re-parsing the user style sheet every time we build the caches,
// as it may have changed.
m_user_rule_cache = nullptr ;
m_user_style_sheet = nullptr ;
2023-03-07 20:13:13 +01:00
// NOTE: It might not be necessary to throw away the UA rule cache.
// If we are sure that it's safe, we could keep it as an optimization.
m_user_agent_rule_cache = nullptr ;
2025-01-04 18:09:21 +03:00
2025-04-17 13:39:30 +02:00
m_pseudo_class_rule_cache = { } ;
2025-01-12 18:38:05 +03:00
m_style_invalidation_data = nullptr ;
2022-02-10 17:49:50 +01:00
}
2023-12-25 12:45:18 +01:00
void StyleComputer : : did_load_font ( FlyString const & )
2022-03-29 02:14:20 +02:00
{
2024-09-04 10:01:08 +02:00
document ( ) . invalidate_style ( DOM : : StyleInvalidationReason : : CSSFontLoaded ) ;
2022-03-29 02:14:20 +02:00
}
2025-05-01 16:16:57 +01:00
Optional < FontLoader & > StyleComputer : : load_font_face ( ParsedFontFace const & font_face , Function < void ( RefPtr < Gfx : : Typeface const > ) > on_load )
2022-03-29 02:14:20 +02:00
{
2024-05-15 14:58:24 -06:00
if ( font_face . sources ( ) . is_empty ( ) ) {
2025-05-01 16:16:57 +01:00
if ( on_load )
on_load ( { } ) ;
2024-05-15 14:58:24 -06:00
return { } ;
}
2022-03-29 02:14:20 +02:00
2024-05-15 14:58:24 -06:00
FontFaceKey key {
. family_name = font_face . font_family ( ) ,
. weight = font_face . weight ( ) . value_or ( 0 ) ,
. slope = font_face . slope ( ) . value_or ( 0 ) ,
} ;
2022-09-14 21:46:34 +02:00
2025-05-01 16:16:57 +01:00
// FIXME: Pass the sources directly, so the font loader can make use of the format information, or load local fonts.
2025-05-02 12:07:22 +01:00
Vector < URL > urls ;
2024-05-15 14:58:24 -06:00
for ( auto const & source : font_face . sources ( ) ) {
2025-05-02 12:07:22 +01:00
if ( source . local_or_url . has < URL > ( ) )
urls . append ( source . local_or_url . get < URL > ( ) ) ;
2024-05-15 14:58:24 -06:00
// FIXME: Handle local()
}
2022-09-14 21:46:34 +02:00
2024-05-15 14:58:24 -06:00
if ( urls . is_empty ( ) ) {
2025-05-01 16:16:57 +01:00
if ( on_load )
on_load ( { } ) ;
2024-05-15 14:58:24 -06:00
return { } ;
}
2025-05-02 12:07:22 +01:00
auto loader = make < FontLoader > ( * this , font_face . parent_style_sheet ( ) , font_face . font_family ( ) , font_face . unicode_ranges ( ) , move ( urls ) , move ( on_load ) ) ;
2024-05-15 14:58:24 -06:00
auto & loader_ref = * loader ;
2025-05-01 16:16:57 +01:00
auto maybe_font_loaders_list = m_loaded_fonts . get ( key ) ;
2024-05-15 14:58:24 -06:00
if ( maybe_font_loaders_list . has_value ( ) ) {
maybe_font_loaders_list - > append ( move ( loader ) ) ;
} else {
FontLoaderList loaders ;
loaders . append ( move ( loader ) ) ;
2025-05-01 16:16:57 +01:00
m_loaded_fonts . set ( OwnFontFaceKey ( key ) , move ( loaders ) ) ;
2024-05-15 14:58:24 -06:00
}
// Actual object owned by font loader list inside m_loaded_fonts, this isn't use-after-move/free
return loader_ref ;
}
2024-09-22 18:10:46 +02:00
void StyleComputer : : load_fonts_from_sheet ( CSSStyleSheet & sheet )
2024-05-15 14:58:24 -06:00
{
for ( auto const & rule : sheet . rules ( ) ) {
if ( ! is < CSSFontFaceRule > ( * rule ) )
continue ;
2025-04-03 12:15:11 +01:00
auto const & font_face_rule = static_cast < CSSFontFaceRule const & > ( * rule ) ;
if ( ! font_face_rule . is_valid ( ) )
continue ;
if ( auto font_loader = load_font_face ( font_face_rule . font_face ( ) ) ; font_loader . has_value ( ) ) {
2024-09-22 18:10:46 +02:00
sheet . add_associated_font_loader ( font_loader . value ( ) ) ;
}
}
}
void StyleComputer : : unload_fonts_from_sheet ( CSSStyleSheet & sheet )
{
for ( auto & [ _ , font_loader_list ] : m_loaded_fonts ) {
font_loader_list . remove_all_matching ( [ & ] ( auto & font_loader ) {
return sheet . has_associated_font_loader ( * font_loader ) ;
} ) ;
2022-04-08 21:27:35 +02:00
}
2022-03-29 02:14:20 +02:00
}
2022-04-08 21:27:35 +02:00
2025-06-19 17:03:26 +01:00
NonnullRefPtr < CSSStyleValue const > StyleComputer : : compute_value_of_custom_property ( DOM : : AbstractElement abstract_element , FlyString const & name , Optional < Parser : : GuardedSubstitutionContexts & > guarded_contexts )
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
{
// https://drafts.csswg.org/css-variables/#propdef-
// The computed value of a custom property is its specified value with any arbitrary-substitution functions replaced.
// FIXME: These should probably be part of ComputedProperties.
2025-07-17 14:51:22 +02:00
auto & document = abstract_element . document ( ) ;
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
auto value = abstract_element . get_custom_property ( name ) ;
2025-07-14 12:50:58 +01:00
if ( ! value | | value - > is_initial ( ) )
2025-07-17 14:51:22 +02:00
return document . custom_property_initial_value ( name ) ;
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
// Unset is the same as inherit for inherited properties, and by default all custom properties are inherited.
2025-07-14 12:50:58 +01:00
// FIXME: Support non-inherited registered custom properties.
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
if ( value - > is_inherit ( ) | | value - > is_unset ( ) ) {
2025-07-14 12:50:58 +01:00
if ( ! abstract_element . parent_element ( ) )
2025-07-17 14:51:22 +02:00
return document . custom_property_initial_value ( name ) ;
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
auto inherited_value = DOM : : AbstractElement { const_cast < DOM : : Element & > ( * abstract_element . parent_element ( ) ) } . get_custom_property ( name ) ;
if ( ! inherited_value )
2025-07-17 14:51:22 +02:00
return document . custom_property_initial_value ( name ) ;
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
return inherited_value . release_nonnull ( ) ;
}
if ( value - > is_revert ( ) ) {
// FIXME: Implement reverting custom properties.
}
if ( value - > is_revert_layer ( ) ) {
// FIXME: Implement reverting custom properties.
}
if ( ! value - > is_unresolved ( ) | | ! value - > as_unresolved ( ) . contains_arbitrary_substitution_function ( ) )
return value . release_nonnull ( ) ;
auto & unresolved = value - > as_unresolved ( ) ;
2025-06-19 17:03:26 +01:00
return Parser : : Parser : : resolve_unresolved_style_value ( Parser : : ParsingParams { } , abstract_element . element ( ) , abstract_element . pseudo_element ( ) , name , unresolved , guarded_contexts ) ;
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 StyleComputer : : compute_custom_properties ( ComputedProperties & , DOM : : AbstractElement abstract_element ) const
{
// https://drafts.csswg.org/css-variables/#propdef-
// The computed value of a custom property is its specified value with any arbitrary-substitution functions replaced.
// FIXME: These should probably be part of ComputedProperties.
auto custom_properties = abstract_element . custom_properties ( ) ;
decltype ( custom_properties ) resolved_custom_properties ;
for ( auto const & [ name , style_property ] : custom_properties ) {
resolved_custom_properties . set ( name ,
StyleProperty {
. important = style_property . important ,
. property_id = style_property . property_id ,
. value = compute_value_of_custom_property ( abstract_element , name ) ,
. custom_name = style_property . custom_name ,
} ) ;
}
abstract_element . set_custom_properties ( move ( resolved_custom_properties ) ) ;
}
2025-03-20 16:56:46 +00:00
void StyleComputer : : compute_math_depth ( ComputedProperties & style , DOM : : Element const * element , Optional < CSS : : PseudoElement > pseudo_element ) const
2023-09-07 15:29:54 +01:00
{
// https://w3c.github.io/mathml-core/#propdef-math-depth
// First, ensure that the relevant CSS properties have been defaulted.
// FIXME: This should be more sophisticated.
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : MathDepth , pseudo_element ) ;
compute_defaulted_property_value ( style , element , CSS : : PropertyID : : MathStyle , pseudo_element ) ;
auto inherited_math_depth = [ & ] ( ) {
if ( ! element | | ! element - > parent_element ( ) )
return InitialValues : : math_depth ( ) ;
2024-12-20 16:35:12 +01:00
return element - > parent_element ( ) - > computed_properties ( ) - > math_depth ( ) ;
2023-09-07 15:29:54 +01:00
} ;
2024-11-03 13:20:04 +01:00
auto const & value = style . property ( CSS : : PropertyID : : MathDepth ) ;
if ( ! value . is_math_depth ( ) ) {
2023-09-07 15:29:54 +01:00
style . set_math_depth ( inherited_math_depth ( ) ) ;
return ;
}
2024-11-03 13:20:04 +01:00
auto const & math_depth = value . as_math_depth ( ) ;
2023-09-07 15:29:54 +01:00
2024-08-14 11:10:54 +01:00
auto resolve_integer = [ & ] ( CSSStyleValue const & integer_value ) {
2023-09-07 15:29:54 +01:00
if ( integer_value . is_integer ( ) )
return integer_value . as_integer ( ) . integer ( ) ;
2024-12-11 15:05:56 +00:00
if ( integer_value . is_calculated ( ) )
2025-07-02 19:12:33 +12:00
return integer_value . as_calculated ( ) . resolve_integer_deprecated ( { } ) . value ( ) ;
2023-09-07 15:29:54 +01:00
VERIFY_NOT_REACHED ( ) ;
} ;
// The computed value of the math-depth value is determined as follows:
// - If the specified value of math-depth is auto-add and the inherited value of math-style is compact
// then the computed value of math-depth of the element is its inherited value plus one.
2024-11-03 13:20:04 +01:00
if ( math_depth . is_auto_add ( ) & & style . property ( CSS : : PropertyID : : MathStyle ) . to_keyword ( ) = = Keyword : : Compact ) {
2023-09-07 15:29:54 +01:00
style . set_math_depth ( inherited_math_depth ( ) + 1 ) ;
return ;
}
// - If the specified value of math-depth is of the form add(<integer>) then the computed value of
// math-depth of the element is its inherited value plus the specified integer.
if ( math_depth . is_add ( ) ) {
style . set_math_depth ( inherited_math_depth ( ) + resolve_integer ( * math_depth . integer_value ( ) ) ) ;
return ;
}
// - If the specified value of math-depth is of the form <integer> then the computed value of math-depth
// of the element is the specified integer.
if ( math_depth . is_integer ( ) ) {
style . set_math_depth ( resolve_integer ( * math_depth . integer_value ( ) ) ) ;
return ;
}
// - Otherwise, the computed value of math-depth of the element is the inherited one.
style . set_math_depth ( inherited_math_depth ( ) ) ;
}
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
static void for_each_element_hash ( DOM : : Element const & element , auto callback )
{
2025-05-18 07:27:01 +03:00
callback ( element . local_name ( ) . ascii_case_insensitive_hash ( ) ) ;
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
if ( element . id ( ) . has_value ( ) )
callback ( element . id ( ) . value ( ) . hash ( ) ) ;
for ( auto const & class_ : element . class_names ( ) )
callback ( class_ . hash ( ) ) ;
element . for_each_attribute ( [ & ] ( auto & attribute ) {
2025-05-18 07:27:01 +03:00
callback ( attribute . lowercase_name ( ) . hash ( ) ) ;
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 StyleComputer : : reset_ancestor_filter ( )
{
m_ancestor_filter . clear ( ) ;
}
void StyleComputer : : push_ancestor ( DOM : : Element const & element )
{
for_each_element_hash ( element , [ & ] ( u32 hash ) {
m_ancestor_filter . increment ( hash ) ;
} ) ;
}
void StyleComputer : : pop_ancestor ( DOM : : Element const & element )
{
for_each_element_hash ( element , [ & ] ( u32 hash ) {
m_ancestor_filter . decrement ( hash ) ;
} ) ;
}
2024-09-29 23:38:49 +02:00
size_t StyleComputer : : number_of_css_font_faces_with_loading_in_progress ( ) const
{
size_t count = 0 ;
for ( auto const & [ _ , loaders ] : m_loaded_fonts ) {
for ( auto const & loader : loaders ) {
if ( loader - > is_loading ( ) )
+ + count ;
}
}
return count ;
}
2025-01-24 10:29:51 +01:00
bool StyleComputer : : may_have_has_selectors ( ) const
2024-12-23 15:22:10 +01:00
{
2025-01-24 10:29:51 +01:00
if ( ! has_valid_rule_cache ( ) )
return true ;
2025-01-02 21:07:47 +03:00
2024-12-23 15:22:10 +01:00
build_rule_cache_if_needed ( ) ;
return m_selector_insights - > has_has_selectors ;
}
2025-02-10 19:03:09 +01:00
bool StyleComputer : : have_has_selectors ( ) const
{
build_rule_cache_if_needed ( ) ;
return m_selector_insights - > has_has_selectors ;
}
2025-03-20 16:56:46 +00:00
void RuleCache : : add_rule ( MatchingRule const & matching_rule , Optional < PseudoElement > pseudo_element , bool contains_root_pseudo_class )
2025-02-20 21:07:22 +01:00
{
// NOTE: We traverse the simple selectors in reverse order to make sure that class/ID buckets are preferred over tag buckets
// in the common case of div.foo or div#foo selectors.
auto add_to_id_bucket = [ & ] ( FlyString const & name ) {
rules_by_id . ensure ( name ) . append ( matching_rule ) ;
} ;
auto add_to_class_bucket = [ & ] ( FlyString const & name ) {
rules_by_class . ensure ( name ) . append ( matching_rule ) ;
} ;
auto add_to_tag_name_bucket = [ & ] ( FlyString const & name ) {
rules_by_tag_name . ensure ( name ) . append ( matching_rule ) ;
} ;
for ( auto const & simple_selector : matching_rule . selector . compound_selectors ( ) . last ( ) . simple_selectors . in_reverse ( ) ) {
if ( simple_selector . type = = Selector : : SimpleSelector : : Type : : Id ) {
add_to_id_bucket ( simple_selector . name ( ) ) ;
return ;
}
if ( simple_selector . type = = Selector : : SimpleSelector : : Type : : Class ) {
add_to_class_bucket ( simple_selector . name ( ) ) ;
return ;
}
if ( simple_selector . type = = Selector : : SimpleSelector : : Type : : TagName ) {
add_to_tag_name_bucket ( simple_selector . qualified_name ( ) . name . lowercase_name ) ;
return ;
}
// NOTE: Selectors like `:is/where(.foo)` and `:is/where(.foo .bar)` are bucketed as class selectors for `foo` and `bar` respectively.
if ( auto simplified = is_roundabout_selector_bucketable_as_something_simpler ( simple_selector ) ; simplified . has_value ( ) ) {
if ( simplified - > type = = Selector : : SimpleSelector : : Type : : TagName ) {
add_to_tag_name_bucket ( simplified - > name ) ;
return ;
}
if ( simplified - > type = = Selector : : SimpleSelector : : Type : : Class ) {
add_to_class_bucket ( simplified - > name ) ;
return ;
}
if ( simplified - > type = = Selector : : SimpleSelector : : Type : : Id ) {
add_to_id_bucket ( simplified - > name ) ;
return ;
}
}
}
2025-02-20 21:53:31 +01:00
if ( matching_rule . contains_pseudo_element & & pseudo_element . has_value ( ) ) {
2025-03-20 16:56:46 +00:00
if ( Selector : : PseudoElementSelector : : is_known_pseudo_element_type ( pseudo_element . value ( ) ) ) {
2025-02-20 21:07:22 +01:00
rules_by_pseudo_element [ to_underlying ( pseudo_element . value ( ) ) ] . append ( matching_rule ) ;
} else {
// NOTE: We don't cache rules for unknown pseudo-elements. They can't match anything anyway.
}
} else if ( contains_root_pseudo_class ) {
root_rules . append ( matching_rule ) ;
} else {
for ( auto const & simple_selector : matching_rule . selector . compound_selectors ( ) . last ( ) . simple_selectors ) {
if ( simple_selector . type = = Selector : : SimpleSelector : : Type : : Attribute ) {
rules_by_attribute_name . ensure ( simple_selector . attribute ( ) . qualified_name . name . lowercase_name ) . append ( matching_rule ) ;
return ;
}
}
other_rules . append ( matching_rule ) ;
}
}
2025-03-20 16:56:46 +00:00
void RuleCache : : for_each_matching_rules ( DOM : : Element const & element , Optional < PseudoElement > pseudo_element , Function < IterationDecision ( Vector < MatchingRule > const & ) > callback ) const
2025-02-20 21:07:22 +01:00
{
for ( auto const & class_name : element . class_names ( ) ) {
if ( auto it = rules_by_class . find ( class_name ) ; it ! = rules_by_class . end ( ) ) {
if ( callback ( it - > value ) = = IterationDecision : : Break )
return ;
}
}
if ( auto id = element . id ( ) ; id . has_value ( ) ) {
if ( auto it = rules_by_id . find ( id . value ( ) ) ; it ! = rules_by_id . end ( ) ) {
if ( callback ( it - > value ) = = IterationDecision : : Break )
return ;
}
}
2025-07-07 15:18:22 +02:00
if ( auto it = rules_by_tag_name . find ( element . lowercased_local_name ( ) ) ; it ! = rules_by_tag_name . end ( ) ) {
2025-02-20 21:07:22 +01:00
if ( callback ( it - > value ) = = IterationDecision : : Break )
return ;
}
if ( pseudo_element . has_value ( ) ) {
2025-03-20 16:56:46 +00:00
if ( Selector : : PseudoElementSelector : : is_known_pseudo_element_type ( pseudo_element . value ( ) ) ) {
2025-02-20 21:07:22 +01:00
if ( callback ( rules_by_pseudo_element . at ( to_underlying ( pseudo_element . value ( ) ) ) ) = = IterationDecision : : Break )
return ;
} else {
// NOTE: We don't cache rules for unknown pseudo-elements. They can't match anything anyway.
}
}
if ( element . is_document_element ( ) ) {
if ( callback ( root_rules ) = = IterationDecision : : Break )
return ;
}
IterationDecision decision = IterationDecision : : Continue ;
element . for_each_attribute ( [ & ] ( auto & name , auto & ) {
if ( auto it = rules_by_attribute_name . find ( name ) ; it ! = rules_by_attribute_name . end ( ) ) {
decision = callback ( it - > value ) ;
}
} ) ;
if ( decision = = IterationDecision : : Break )
return ;
( void ) callback ( other_rules ) ;
}
2025-03-05 18:22:05 +01:00
Length : : FontMetrics const & StyleComputer : : root_element_font_metrics_for_element ( GC : : Ptr < DOM : : Element const > element ) const
{
if ( element & & element - > document ( ) . document_element ( ) = = element )
return m_default_font_metrics ;
return m_root_element_font_metrics ;
}
2020-03-07 10:27:02 +01:00
}