LibWeb: Make getBBox() throw error for non-rendered elements

Per SVG2 spec (§ Geometry Properties: getBBox), getBBox() must throw
InvalidStateError if the element is not rendered and its geometry cannot
be computed. Previously we would crash on null paintables; now we throw
with a clear error instead.
This commit is contained in:
Pavel Shliak 2025-09-12 12:30:45 +04:00 committed by Andreas Kling
parent 46c6176235
commit 88500580e6
6 changed files with 61 additions and 8 deletions

View File

@ -23,6 +23,8 @@
#include <LibWeb/SVG/SVGMaskElement.h>
#include <LibWeb/SVG/SVGSVGElement.h>
#include <LibWeb/SVG/SVGSymbolElement.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::SVG {
@ -308,7 +310,7 @@ Optional<float> SVGGraphicsElement::stroke_width() const
}
// https://svgwg.org/svg2-draft/types.html#__svg__SVGGraphicsElement__getBBox
GC::Ref<Geometry::DOMRect> SVGGraphicsElement::get_b_box(Optional<SVGBoundingBoxOptions>)
WebIDL::ExceptionOr<GC::Ref<Geometry::DOMRect>> SVGGraphicsElement::get_b_box(Optional<SVGBoundingBoxOptions>)
{
// FIXME: It should be possible to compute this without layout updates. The bounding box is within the
// SVG coordinate space (before any viewbox or other transformations), so it should be possible to
@ -321,12 +323,25 @@ GC::Ref<Geometry::DOMRect> SVGGraphicsElement::get_b_box(Optional<SVGBoundingBox
auto owner_svg_element = this->owner_svg_element();
if (!owner_svg_element)
return Geometry::DOMRect::create(realm());
auto svg_element_rect = owner_svg_element->paintable_box()->absolute_rect();
auto inverse_transform = static_cast<Painting::SVGGraphicsPaintable&>(*paintable_box()).computed_transforms().svg_to_css_pixels_transform().inverse();
auto translated_rect = paintable_box()->absolute_rect().to_type<float>().translated(-svg_element_rect.location().to_type<float>());
if (inverse_transform.has_value())
translated_rect = inverse_transform->map(translated_rect);
return Geometry::DOMRect::create(realm(), translated_rect);
auto owner_paintable = owner_svg_element->paintable_box();
auto self_paintable = paintable_box();
if (!owner_paintable || !self_paintable) {
// Throw only for non-rendered *graphics* elements where geometry isn't computable
// (e.g. elements inside <marker>, <pattern>, etc.).
if (is<SVGSVGElement>(*this))
return Geometry::DOMRect::create(realm());
return WebIDL::InvalidStateError::create(
realm(),
"Element is not rendered and geometry is not computable"_utf16);
}
auto svg_rect = owner_paintable->absolute_rect();
auto inv = static_cast<Painting::SVGGraphicsPaintable&>(*self_paintable).computed_transforms().svg_to_css_pixels_transform().inverse();
auto rect = self_paintable->absolute_rect().to_type<float>().translated(-svg_rect.location().to_type<float>());
if (inv.has_value())
rect = inv->map(rect);
return Geometry::DOMRect::create(realm(), rect);
}
GC::Ref<SVGAnimatedTransformList> SVGGraphicsElement::transform() const

View File

@ -17,6 +17,8 @@
#include <LibWeb/SVG/SVGFitToViewBox.h>
#include <LibWeb/SVG/SVGGradientElement.h>
#include <LibWeb/SVG/TagNames.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::SVG {
@ -69,7 +71,7 @@ public:
GC::Ptr<SVG::SVGMaskElement const> mask() const;
GC::Ptr<SVG::SVGClipPathElement const> clip_path() const;
GC::Ref<Geometry::DOMRect> get_b_box(Optional<SVGBoundingBoxOptions>);
WebIDL::ExceptionOr<GC::Ref<Geometry::DOMRect>> get_b_box(Optional<SVGBoundingBoxOptions>);
GC::Ref<SVGAnimatedTransformList> transform() const;
GC::Ptr<Geometry::DOMMatrix> get_ctm();

View File

@ -0,0 +1 @@
Threw InvalidStateError

View File

@ -0,0 +1 @@
Threw InvalidStateError

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<svg width="1" height="1">
<marker id="mk" markerWidth="1" markerHeight="1">
<circle id="t" cx="0" cy="0" r="0"></circle>
</marker>
</svg>
<script>
test(() => {
try {
document.getElementById("t").getBBox();
println("Did not throw");
} catch (e) {
println(`Threw ${e.name}`);
}
});
</script>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<svg width="1" height="1">
<pattern id="pat" width="1" height="1" patternUnits="userSpaceOnUse">
<rect id="tp" width="1" height="1"></rect>
</pattern>
</svg>
<script>
test(() => {
try {
document.getElementById("tp").getBBox();
println("Did not throw");
} catch (e) {
println(`Threw ${e.name}`);
}
});
</script>