LibWeb: Handle reference cycles in SVG gradient linking
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.
This commit is contained in:
parent
1b8d8c7bbc
commit
2e0297d703
Notes:
sideshowbarker
2024-07-17 02:14:39 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/2e0297d703 Pull-request: https://github.com/SerenityOS/serenity/pull/23549
8 changed files with 155 additions and 36 deletions
|
@ -0,0 +1 @@
|
|||
PASS (didn't hang or crash)
|
|
@ -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>
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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 {};
|
||||
|
|
|
@ -55,12 +55,22 @@ protected:
|
|||
|
||||
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) {
|
||||
|
@ -68,14 +78,15 @@ protected:
|
|||
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 = {};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -34,9 +34,9 @@ protected:
|
|||
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 {};
|
||||
}
|
||||
|
@ -46,6 +46,11 @@ private:
|
|||
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;
|
||||
|
|
|
@ -52,13 +52,19 @@ 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();
|
||||
|
@ -66,13 +72,19 @@ NumberPercentage SVGRadialGradientElement::start_circle_x() const
|
|||
|
||||
// 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();
|
||||
|
@ -80,46 +92,70 @@ NumberPercentage SVGRadialGradientElement::start_circle_y() const
|
|||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@ protected:
|
|||
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 {};
|
||||
}
|
||||
|
@ -50,6 +50,13 @@ private:
|
|||
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;
|
||||
|
|
Loading…
Add table
Reference in a new issue