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.
This commit is contained in:
Andreas Kling 2024-12-12 10:06:29 +01:00 committed by Andreas Kling
commit ed7f4664c2
Notes: github-actions[bot] 2024-12-22 09:14:00 +00:00
60 changed files with 663 additions and 385 deletions

View file

@ -29,22 +29,22 @@ void SVGCircleElement::initialize(JS::Realm& realm)
WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGCircleElement);
}
void SVGCircleElement::apply_presentational_hints(CSS::StyleProperties& style) const
void SVGCircleElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{
Base::apply_presentational_hints(style);
Base::apply_presentational_hints(cascaded_properties);
auto parsing_context = CSS::Parser::ParsingContext { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
auto cx_attribute = attribute(SVG::AttributeNames::cx);
if (auto cx_value = parse_css_value(parsing_context, cx_attribute.value_or(String {}), CSS::PropertyID::Cx))
style.set_property(CSS::PropertyID::Cx, cx_value.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Cx, cx_value.release_nonnull());
auto cy_attribute = attribute(SVG::AttributeNames::cy);
if (auto cy_value = parse_css_value(parsing_context, cy_attribute.value_or(String {}), CSS::PropertyID::Cy))
style.set_property(CSS::PropertyID::Cy, cy_value.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Cy, cy_value.release_nonnull());
auto r_attribute = attribute(SVG::AttributeNames::r);
if (auto r_value = parse_css_value(parsing_context, r_attribute.value_or(String {}), CSS::PropertyID::R))
style.set_property(CSS::PropertyID::R, r_value.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::R, r_value.release_nonnull());
}
Gfx::Path SVGCircleElement::get_path(CSSPixelSize viewport_size)

View file

@ -18,7 +18,7 @@ class SVGCircleElement final : public SVGGeometryElement {
public:
virtual ~SVGCircleElement() override = default;
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual Gfx::Path get_path(CSSPixelSize viewport_size) override;

View file

@ -52,15 +52,15 @@ GC::Ptr<Layout::Node> SVGForeignObjectElement::create_layout_node(CSS::StyleProp
return heap().allocate<Layout::SVGForeignObjectBox>(document(), *this, move(style));
}
void SVGForeignObjectElement::apply_presentational_hints(CSS::StyleProperties& style) const
void SVGForeignObjectElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{
Base::apply_presentational_hints(style);
Base::apply_presentational_hints(cascaded_properties);
auto parsing_context = CSS::Parser::ParsingContext { document() };
if (auto width_value = parse_css_value(parsing_context, get_attribute_value(Web::HTML::AttributeNames::width), CSS::PropertyID::Width))
style.set_property(CSS::PropertyID::Width, width_value.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Width, width_value.release_nonnull());
if (auto height_value = parse_css_value(parsing_context, get_attribute_value(Web::HTML::AttributeNames::height), CSS::PropertyID::Height))
style.set_property(CSS::PropertyID::Height, height_value.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Height, height_value.release_nonnull());
}
GC::Ref<SVG::SVGAnimatedLength> SVGForeignObjectElement::x()

View file

@ -31,7 +31,7 @@ private:
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
GC::Ptr<SVG::SVGAnimatedLength> m_x;
GC::Ptr<SVG::SVGAnimatedLength> m_y;

View file

@ -143,7 +143,7 @@ struct NamedPropertyID {
StringView name;
};
void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style) const
void SVGGraphicsElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{
static Array const attribute_style_properties {
// FIXME: The `fill` attribute and CSS `fill` property are not the same! But our support is limited enough that they are equivalent for now.
@ -175,7 +175,7 @@ void SVGGraphicsElement::apply_presentational_hints(CSS::StyleProperties& style)
if (!name.equals_ignoring_ascii_case(property.name))
continue;
if (auto style_value = parse_css_value(parsing_context, value, property.id))
style.set_property(property.id, style_value.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(property.id, style_value.release_nonnull());
break;
}
});

View file

@ -29,7 +29,7 @@ class SVGGraphicsElement : public SVGElement {
WEB_PLATFORM_OBJECT(SVGGraphicsElement, SVGElement);
public:
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;

View file

@ -79,26 +79,26 @@ RefPtr<CSS::CSSStyleValue> SVGSVGElement::height_style_value_from_attribute() co
return nullptr;
}
void SVGSVGElement::apply_presentational_hints(CSS::StyleProperties& style) const
void SVGSVGElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{
Base::apply_presentational_hints(style);
Base::apply_presentational_hints(cascaded_properties);
auto parsing_context = CSS::Parser::ParsingContext { document(), CSS::Parser::ParsingContext::Mode::SVGPresentationAttribute };
auto x_attribute = attribute(SVG::AttributeNames::x);
if (auto x_value = parse_css_value(parsing_context, x_attribute.value_or(String {}), CSS::PropertyID::X)) {
style.set_property(CSS::PropertyID::X, x_value.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::X, x_value.release_nonnull());
}
auto y_attribute = attribute(SVG::AttributeNames::y);
if (auto y_value = parse_css_value(parsing_context, y_attribute.value_or(String {}), CSS::PropertyID::Y)) {
style.set_property(CSS::PropertyID::Y, y_value.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Y, y_value.release_nonnull());
}
if (auto width = width_style_value_from_attribute())
style.set_property(CSS::PropertyID::Width, width.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Width, width.release_nonnull());
if (auto height = height_style_value_from_attribute())
style.set_property(CSS::PropertyID::Height, height.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Height, height.release_nonnull());
}
void SVGSVGElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)

View file

@ -31,7 +31,7 @@ class SVGSVGElement final : public SVGGraphicsElement
public:
virtual GC::Ptr<Layout::Node> create_layout_node(CSS::StyleProperties) override;
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual bool requires_svg_container() const override { return false; }
virtual bool is_svg_container() const override { return true; }

View file

@ -30,18 +30,18 @@ void SVGStopElement::attribute_changed(FlyString const& name, Optional<String> c
}
}
void SVGStopElement::apply_presentational_hints(CSS::StyleProperties& style) const
void SVGStopElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{
CSS::Parser::ParsingContext parsing_context { document() };
for_each_attribute([&](auto& name, auto& value) {
CSS::Parser::ParsingContext parsing_context { document() };
if (name.equals_ignoring_ascii_case("stop-color"sv)) {
if (auto stop_color = parse_css_value(parsing_context, value, CSS::PropertyID::StopColor)) {
style.set_property(CSS::PropertyID::StopColor, stop_color.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::StopColor, stop_color.release_nonnull());
}
} else if (name.equals_ignoring_ascii_case("stop-opacity"sv)) {
if (auto stop_opacity = parse_css_value(parsing_context, value, CSS::PropertyID::StopOpacity)) {
style.set_property(CSS::PropertyID::StopOpacity, stop_opacity.release_nonnull());
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::StopOpacity, stop_opacity.release_nonnull());
}
}
});

View file

@ -25,7 +25,7 @@ public:
GC::Ref<SVGAnimatedNumber> offset() const;
virtual void apply_presentational_hints(CSS::StyleProperties&) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
NumberPercentage stop_offset() const { return m_offset.value_or(NumberPercentage::create_number(0)); }
Gfx::Color stop_color() const;

View file

@ -40,13 +40,13 @@ void SVGSymbolElement::visit_edges(Cell::Visitor& visitor)
}
// https://svgwg.org/svg2-draft/struct.html#SymbolNotes
void SVGSymbolElement::apply_presentational_hints(CSS::StyleProperties& style) const
void SVGSymbolElement::apply_presentational_hints(GC::Ref<CSS::CascadedProperties> cascaded_properties) const
{
Base::apply_presentational_hints(style);
Base::apply_presentational_hints(cascaded_properties);
if (is_direct_child_of_use_shadow_tree()) {
// The generated instance of a symbol that is the direct referenced element of a use element must always have a computed value of inline for the display property.
style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::Inline)));
cascaded_properties->set_property_from_presentational_hint(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::Inline)));
}
}

View file

@ -19,7 +19,7 @@ class SVGSymbolElement final : public SVGGraphicsElement
public:
virtual ~SVGSymbolElement() override = default;
void apply_presentational_hints(CSS::StyleProperties& style) const override;
virtual void apply_presentational_hints(GC::Ref<CSS::CascadedProperties>) const override;
virtual Optional<ViewBox> view_box() const override { return m_view_box; }
virtual Optional<PreserveAspectRatio> preserve_aspect_ratio() const override