Skip to content

Commit

Permalink
LibWeb: Handle reference cycles in SVG gradient linking
Browse files Browse the repository at this point in the history
Since SVG gradients can reference each other, we have to keep track of
visited gradients when traversing the link chain, or we will recurse
infinitely when there's a reference cycle.
  • Loading branch information
awesomekling committed Mar 11, 2024
1 parent 1b8d8c7 commit 2e0297d
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PASS (didn't hang or crash)
13 changes: 13 additions & 0 deletions Tests/LibWeb/Text/input/SVG/gradient-with-reference-cycle.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<script src="../include.js"></script>
<svg>
<linearGradient id="lol" href="#lmao"/>
<linearGradient id="lmao" href="#even"/>
<linearGradient id="even" href="#lol"/>
<rect fill="url(#lol)" />
</svg>
<script>
test(() => {
println("PASS (didn't hang or crash)");
});
</script>
36 changes: 29 additions & 7 deletions Userland/Libraries/LibWeb/SVG/SVGGradientElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,47 @@ void SVGGradientElement::attribute_changed(FlyString const& name, Optional<Strin
}

GradientUnits SVGGradientElement::gradient_units() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return gradient_units_impl(seen_gradients);
}

GradientUnits SVGGradientElement::gradient_units_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_gradient_units.has_value())
return *m_gradient_units;
if (auto gradient = linked_gradient())
return gradient->gradient_units();
if (auto gradient = linked_gradient(seen_gradients))
return gradient->gradient_units_impl(seen_gradients);
return GradientUnits::ObjectBoundingBox;
}

SpreadMethod SVGGradientElement::spread_method() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return spread_method_impl(seen_gradients);
}

SpreadMethod SVGGradientElement::spread_method_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_spread_method.has_value())
return *m_spread_method;
if (auto gradient = linked_gradient())
return gradient->spread_method();
if (auto gradient = linked_gradient(seen_gradients))
return gradient->spread_method_impl(seen_gradients);
return SpreadMethod::Pad;
}

Optional<Gfx::AffineTransform> SVGGradientElement::gradient_transform() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return gradient_transform_impl(seen_gradients);
}

Optional<Gfx::AffineTransform> SVGGradientElement::gradient_transform_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_gradient_transform.has_value())
return m_gradient_transform;
if (auto gradient = linked_gradient())
return gradient->gradient_transform();
if (auto gradient = linked_gradient(seen_gradients))
return gradient->gradient_transform_impl(seen_gradients);
return {};
}

Expand Down Expand Up @@ -89,7 +107,7 @@ void SVGGradientElement::add_color_stops(Gfx::SVGGradientPaintStyle& paint_style
});
}

JS::GCPtr<SVGGradientElement const> SVGGradientElement::linked_gradient() const
JS::GCPtr<SVGGradientElement const> SVGGradientElement::linked_gradient(HashTable<SVGGradientElement const*>& seen_gradients) const
{
// FIXME: This entire function is an ad-hoc hack!
// It can only resolve #<ids> in the same document.
Expand All @@ -103,8 +121,12 @@ JS::GCPtr<SVGGradientElement const> SVGGradientElement::linked_gradient() const
auto element = document().get_element_by_id(id.value());
if (!element)
return {};
if (element == this)
return {};
if (!is<SVGGradientElement>(*element))
return {};
if (seen_gradients.set(&verify_cast<SVGGradientElement>(*element)) != AK::HashSetResult::InsertedNewEntry)
return {};
return &verify_cast<SVGGradientElement>(*element);
}
return {};
Expand Down
21 changes: 16 additions & 5 deletions Userland/Libraries/LibWeb/SVG/SVGGradientElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,27 +55,38 @@ class SVGGradientElement : public SVGElement {

virtual void initialize(JS::Realm&) override;

JS::GCPtr<SVGGradientElement const> linked_gradient() const;
JS::GCPtr<SVGGradientElement const> linked_gradient(HashTable<SVGGradientElement const*>& seen_gradients) const;

Gfx::AffineTransform gradient_paint_transform(SVGPaintContext const&) const;

template<VoidFunction<SVGStopElement> Callback>
void for_each_color_stop(Callback const& callback) const
{
HashTable<SVGGradientElement const*> seen_gradients;
return for_each_color_stop_impl(callback, seen_gradients);
}

void add_color_stops(Gfx::SVGGradientPaintStyle&) const;

private:
template<VoidFunction<SVGStopElement> Callback>
void for_each_color_stop_impl(Callback const& callback, HashTable<SVGGradientElement const*>& seen_gradients) const
{
bool color_stops_found = false;
for_each_child_of_type<SVG::SVGStopElement>([&](auto& stop) {
color_stops_found = true;
callback(stop);
});
if (!color_stops_found) {
if (auto gradient = linked_gradient())
gradient->for_each_color_stop(callback);
if (auto gradient = linked_gradient(seen_gradients))
gradient->for_each_color_stop_impl(callback, seen_gradients);
}
}

void add_color_stops(Gfx::SVGGradientPaintStyle&) const;
GradientUnits gradient_units_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
SpreadMethod spread_method_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
Optional<Gfx::AffineTransform> gradient_transform_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;

private:
Optional<GradientUnits> m_gradient_units = {};
Optional<SpreadMethod> m_spread_method = {};
Optional<Gfx::AffineTransform> m_gradient_transform = {};
Expand Down
40 changes: 32 additions & 8 deletions Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,44 +48,68 @@ void SVGLinearGradientElement::attribute_changed(FlyString const& name, Optional

// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementX1Attribute
NumberPercentage SVGLinearGradientElement::start_x() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return start_x_impl(seen_gradients);
}

NumberPercentage SVGLinearGradientElement::start_x_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_x1.has_value())
return *m_x1;
if (auto gradient = linked_linear_gradient())
return gradient->start_x();
if (auto gradient = linked_linear_gradient(seen_gradients))
return gradient->start_x_impl(seen_gradients);
// If the attribute is not specified, the effect is as if a value of '0%' were specified.
return NumberPercentage::create_percentage(0);
}

// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementY1Attribute
NumberPercentage SVGLinearGradientElement::start_y() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return start_y_impl(seen_gradients);
}

NumberPercentage SVGLinearGradientElement::start_y_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_y1.has_value())
return *m_y1;
if (auto gradient = linked_linear_gradient())
return gradient->start_x();
if (auto gradient = linked_linear_gradient(seen_gradients))
return gradient->start_y_impl(seen_gradients);
// If the attribute is not specified, the effect is as if a value of '0%' were specified.
return NumberPercentage::create_percentage(0);
}

// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementX2Attribute
NumberPercentage SVGLinearGradientElement::end_x() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return end_x_impl(seen_gradients);
}

NumberPercentage SVGLinearGradientElement::end_x_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_x2.has_value())
return *m_x2;
if (auto gradient = linked_linear_gradient())
return gradient->start_x();
if (auto gradient = linked_linear_gradient(seen_gradients))
return gradient->end_x_impl(seen_gradients);
// If the attribute is not specified, the effect is as if a value of '100%' were specified.
return NumberPercentage::create_percentage(100);
}

// https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementY2Attribute
NumberPercentage SVGLinearGradientElement::end_y() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return end_y_impl(seen_gradients);
}

NumberPercentage SVGLinearGradientElement::end_y_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_y2.has_value())
return *m_y2;
if (auto gradient = linked_linear_gradient())
return gradient->start_x();
if (auto gradient = linked_linear_gradient(seen_gradients))
return gradient->end_y_impl(seen_gradients);
// If the attribute is not specified, the effect is as if a value of '0%' were specified.
return NumberPercentage::create_percentage(0);
}
Expand Down
9 changes: 7 additions & 2 deletions Userland/Libraries/LibWeb/SVG/SVGLinearGradientElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ class SVGLinearGradientElement : public SVGGradientElement {
virtual void initialize(JS::Realm&) override;

private:
JS::GCPtr<SVGLinearGradientElement const> linked_linear_gradient() const
JS::GCPtr<SVGLinearGradientElement const> linked_linear_gradient(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (auto gradient = linked_gradient(); gradient && is<SVGLinearGradientElement>(*gradient))
if (auto gradient = linked_gradient(seen_gradients); gradient && is<SVGLinearGradientElement>(*gradient))
return &verify_cast<SVGLinearGradientElement>(*gradient);
return {};
}
Expand All @@ -46,6 +46,11 @@ class SVGLinearGradientElement : public SVGGradientElement {
NumberPercentage end_x() const;
NumberPercentage end_y() const;

NumberPercentage start_x_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
NumberPercentage start_y_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
NumberPercentage end_x_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
NumberPercentage end_y_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;

Optional<NumberPercentage> m_x1;
Optional<NumberPercentage> m_y1;
Optional<NumberPercentage> m_x2;
Expand Down
60 changes: 48 additions & 12 deletions Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,74 +52,110 @@ void SVGRadialGradientElement::attribute_changed(FlyString const& name, Optional

// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementFXAttribute
NumberPercentage SVGRadialGradientElement::start_circle_x() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return start_circle_x_impl(seen_gradients);
}

NumberPercentage SVGRadialGradientElement::start_circle_x_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_fx.has_value())
return *m_fx;
// If the element references an element that specifies a value for 'fx', then the value of 'fx' is
// inherited from the referenced element.
if (auto gradient = linked_radial_gradient())
return gradient->start_circle_x();
if (auto gradient = linked_radial_gradient(seen_gradients))
return gradient->start_circle_x_impl(seen_gradients);
// If attribute ‘fx’ is not specified, ‘fx’ will coincide with the presentational value of ‘cx’ for
// the element whether the value for 'cx' was inherited or not.
return end_circle_x();
}

// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementFYAttribute
NumberPercentage SVGRadialGradientElement::start_circle_y() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return start_circle_y_impl(seen_gradients);
}

NumberPercentage SVGRadialGradientElement::start_circle_y_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_fy.has_value())
return *m_fy;
// If the element references an element that specifies a value for 'fy', then the value of 'fy' is
// inherited from the referenced element.
if (auto gradient = linked_radial_gradient())
return gradient->start_circle_y();
if (auto gradient = linked_radial_gradient(seen_gradients))
return gradient->start_circle_y_impl(seen_gradients);
// If attribute ‘fy’ is not specified, ‘fy’ will coincide with the presentational value of ‘cy’ for
// the element whether the value for 'cy' was inherited or not.
return end_circle_y();
}

// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementFRAttribute
NumberPercentage SVGRadialGradientElement::start_circle_radius() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return start_circle_radius_impl(seen_gradients);
}

NumberPercentage SVGRadialGradientElement::start_circle_radius_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
// Note: A negative value is an error.
if (m_fr.has_value() && m_fr->value() >= 0)
return *m_fr;
// if the element references an element that specifies a value for 'fr', then the value of
// 'fr' is inherited from the referenced element.
if (auto gradient = linked_radial_gradient())
return gradient->start_circle_radius();
if (auto gradient = linked_radial_gradient(seen_gradients))
return gradient->start_circle_radius_impl(seen_gradients);
// If the attribute is not specified, the effect is as if a value of '0%' were specified.
return NumberPercentage::create_percentage(0);
}

// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementCXAttribute
NumberPercentage SVGRadialGradientElement::end_circle_x() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return end_circle_x_impl(seen_gradients);
}

NumberPercentage SVGRadialGradientElement::end_circle_x_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_cx.has_value())
return *m_cx;
if (auto gradient = linked_radial_gradient())
return gradient->end_circle_x();
if (auto gradient = linked_radial_gradient(seen_gradients))
return gradient->end_circle_x_impl(seen_gradients);
return NumberPercentage::create_percentage(50);
}

// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementCYAttribute
NumberPercentage SVGRadialGradientElement::end_circle_y() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return end_circle_y_impl(seen_gradients);
}

NumberPercentage SVGRadialGradientElement::end_circle_y_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (m_cy.has_value())
return *m_cy;
if (auto gradient = linked_radial_gradient())
return gradient->end_circle_y();
if (auto gradient = linked_radial_gradient(seen_gradients))
return gradient->end_circle_y_impl(seen_gradients);
return NumberPercentage::create_percentage(50);
}

// https://svgwg.org/svg2-draft/pservers.html#RadialGradientElementRAttribute
NumberPercentage SVGRadialGradientElement::end_circle_radius() const
{
HashTable<SVGGradientElement const*> seen_gradients;
return end_circle_radius_impl(seen_gradients);
}

NumberPercentage SVGRadialGradientElement::end_circle_radius_impl(HashTable<SVGGradientElement const*>& seen_gradients) const
{
// Note: A negative value is an error.
if (m_r.has_value() && m_r->value() >= 0)
return *m_r;
if (auto gradient = linked_radial_gradient())
return gradient->end_circle_radius();
if (auto gradient = linked_radial_gradient(seen_gradients))
return gradient->end_circle_radius_impl(seen_gradients);
return NumberPercentage::create_percentage(50);
}

Expand Down
11 changes: 9 additions & 2 deletions Userland/Libraries/LibWeb/SVG/SVGRadialGradientElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ class SVGRadialGradientElement : public SVGGradientElement {
virtual void initialize(JS::Realm&) override;

private:
JS::GCPtr<SVGRadialGradientElement const> linked_radial_gradient() const
JS::GCPtr<SVGRadialGradientElement const> linked_radial_gradient(HashTable<SVGGradientElement const*>& seen_gradients) const
{
if (auto gradient = linked_gradient(); gradient && is<SVGRadialGradientElement>(*gradient))
if (auto gradient = linked_gradient(seen_gradients); gradient && is<SVGRadialGradientElement>(*gradient))
return &verify_cast<SVGRadialGradientElement>(*gradient);
return {};
}
Expand All @@ -50,6 +50,13 @@ class SVGRadialGradientElement : public SVGGradientElement {
NumberPercentage end_circle_y() const;
NumberPercentage end_circle_radius() const;

NumberPercentage start_circle_x_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
NumberPercentage start_circle_y_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
NumberPercentage start_circle_radius_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
NumberPercentage end_circle_x_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
NumberPercentage end_circle_y_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;
NumberPercentage end_circle_radius_impl(HashTable<SVGGradientElement const*>& seen_gradients) const;

Optional<NumberPercentage> m_cx;
Optional<NumberPercentage> m_cy;
Optional<NumberPercentage> m_fx;
Expand Down

0 comments on commit 2e0297d

Please sign in to comment.