diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index b8d2b5a0fb14c..b8750fee2027f 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -152,6 +152,7 @@ enum class InvalidateLayoutTreeReason { X(ProcessScreenshot) \ X(SVGGraphicsElementGetBBox) \ X(SVGLengthValue) \ + X(SVGPathLength) \ X(SourceSetNormalizeSourceDensities) \ X(ViewTransitionCapture) \ X(WindowScroll) diff --git a/Libraries/LibWeb/SVG/SVGCircleElement.cpp b/Libraries/LibWeb/SVG/SVGCircleElement.cpp index 5d5afe54d2d62..5d7e79b5fa442 100644 --- a/Libraries/LibWeb/SVG/SVGCircleElement.cpp +++ b/Libraries/LibWeb/SVG/SVGCircleElement.cpp @@ -38,18 +38,13 @@ static CSSPixels normalized_diagonal_length(CSSPixelSize viewport_size) Gfx::Path SVGCircleElement::get_path(CSSPixelSize viewport_size) { - // NB: Called during SVG layout. - auto node = unsafe_layout_node(); - if (!node) { - dbgln("FIXME: Null layout node in SVGCircleElement::get_path"); - return {}; - } + auto computed_values = this->computed_values(); - auto cx = float(node->computed_values().cx().to_px(viewport_size.width())); - auto cy = float(node->computed_values().cy().to_px(viewport_size.height())); + auto cx = float(computed_values->cx().to_px(viewport_size.width())); + auto cy = float(computed_values->cy().to_px(viewport_size.height())); // Percentages refer to the normalized diagonal of the current SVG viewport // (see Units: https://svgwg.org/svg2-draft/coords.html#Units) - auto r = float(node->computed_values().r().to_px(normalized_diagonal_length(viewport_size))); + auto r = float(computed_values->r().to_px(normalized_diagonal_length(viewport_size))); // A zero radius disables rendering. if (r == 0) diff --git a/Libraries/LibWeb/SVG/SVGElement.cpp b/Libraries/LibWeb/SVG/SVGElement.cpp index c1d1326871644..527f4bd37ca28 100644 --- a/Libraries/LibWeb/SVG/SVGElement.cpp +++ b/Libraries/LibWeb/SVG/SVGElement.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -393,6 +394,44 @@ GC::Ptr SVGElement::viewport_element() return nullptr; } +Gfx::Size SVGElement::viewport_size_for_percentage_resolution() +{ + auto viewport_size_from_layout = [&](SVGSVGElement const& viewport_element) -> Gfx::Size { + // NB: A disconnected element may have stale layout objects from before it was removed. + if (!viewport_element.is_connected()) + return {}; + + if (auto const* svg_paintable = as_if(viewport_element.paintable().ptr())) + return svg_paintable->svg_viewport_size().to_type(); + + return {}; + }; + + // NB: Percentages for SVGSVGElements are resolved irrespective of it's own viewBox (which only affects the internal + // coordinate system) + if (auto* svg_element = as_if(*this)) { + auto const* parent = svg_element->parent_or_shadow_host_element(); + + // https://w3c.github.io/svgwg/svg2-draft/coords.html#InitialViewport + if (!parent || !is(*parent)) + return viewport_size_from_layout(*svg_element); + + // https://w3c.github.io/svgwg/svg2-draft/coords.html#EstablishingANewSVGViewport + if (is(*parent)) + return viewport_size_from_layout(*svg_element); + } + + auto const& viewport_element = const_cast(this)->owner_svg_element().ptr(); + + if (!viewport_element) + return {}; + + if (auto view_box = viewport_element->active_view_box(); view_box.has_value() && view_box->width > 0 && view_box->height > 0) + return { view_box->width, view_box->height }; + + return viewport_size_from_layout(*viewport_element); +} + GC::Ref SVGElement::svg_animated_length_for_attribute(Utf16FlyString const& attribute_name, SVGLength::Directionality directionality, NonnullRefPtr&& default_value) { if (auto cached = m_reflected_attribute_cache.get(attribute_name); cached.has_value()) diff --git a/Libraries/LibWeb/SVG/SVGElement.h b/Libraries/LibWeb/SVG/SVGElement.h index 375fff90039f3..1cd4ce1cc9c34 100644 --- a/Libraries/LibWeb/SVG/SVGElement.h +++ b/Libraries/LibWeb/SVG/SVGElement.h @@ -39,6 +39,8 @@ class WEB_API SVGElement bool should_include_in_accessibility_tree() const; virtual Optional default_role() const override; + Gfx::Size viewport_size_for_percentage_resolution(); + GC::Ref svg_animated_length_for_attribute(Utf16FlyString const&, SVGLength::Directionality, NonnullRefPtr&& default_value); virtual bool is_presentational_hint(Utf16FlyString const&) const final override; diff --git a/Libraries/LibWeb/SVG/SVGGeometryElement.cpp b/Libraries/LibWeb/SVG/SVGGeometryElement.cpp index be3084cf7a9d4..248f1684b6475 100644 --- a/Libraries/LibWeb/SVG/SVGGeometryElement.cpp +++ b/Libraries/LibWeb/SVG/SVGGeometryElement.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -33,9 +34,25 @@ RefPtr SVGGeometryElement::create_layout_node(NonnullRefPtr(document(), *this, style); } -float SVGGeometryElement::get_total_length() +// https://w3c.github.io/svgwg/svg2-draft/types.html#__svg__SVGGeometryElement__getTotalLength +WebIDL::ExceptionOr SVGGeometryElement::get_total_length() { - return 0; + // When getTotalLength() is called, the user agent's computed value for the total length of the path, in user units, + // is returned. + + // NB: Update layout so that the viewport size is resolved correctly + document().update_layout(DOM::UpdateLayoutReason::SVGPathLength); + + auto viewport_size = viewport_size_for_percentage_resolution(); + + // NB: Update style for the element so that the correct computed values are used to generate the path - this is done + // separately from the layout update above since it may have been skipped if the element was display: none or + // disconnected. + document().update_style_for_element(*this); + + VERIFY(computed_values()); + + return get_path({ viewport_size.width(), viewport_size.height() }).length(); } GC::Ref SVGGeometryElement::get_point_at_length(float distance) diff --git a/Libraries/LibWeb/SVG/SVGGeometryElement.h b/Libraries/LibWeb/SVG/SVGGeometryElement.h index e25ee37d42e45..bf03a026fb50b 100644 --- a/Libraries/LibWeb/SVG/SVGGeometryElement.h +++ b/Libraries/LibWeb/SVG/SVGGeometryElement.h @@ -20,7 +20,7 @@ class SVGGeometryElement : public SVGGraphicsElement { virtual Gfx::Path get_path(CSSPixelSize viewport_size) = 0; - float get_total_length(); + WebIDL::ExceptionOr get_total_length(); GC::Ref get_point_at_length(float distance); GC::Ref path_length(); diff --git a/Libraries/LibWeb/SVG/SVGLength.cpp b/Libraries/LibWeb/SVG/SVGLength.cpp index 9d2f38ac65b17..4bb862837515d 100644 --- a/Libraries/LibWeb/SVG/SVGLength.cpp +++ b/Libraries/LibWeb/SVG/SVGLength.cpp @@ -55,44 +55,6 @@ void SVGLength::visit_edges(Cell::Visitor& visitor) SVGLength::~SVGLength() = default; -static Gfx::Size svg_viewport_size(SVGElement& element) -{ - auto viewport_size_from_layout = [](SVGSVGElement& viewport_element) -> Gfx::Size { - // NB: A disconnected element may have stale layout objects from before it was removed. - if (!viewport_element.is_connected()) - return {}; - - if (auto const* svg_paintable = as_if(viewport_element.paintable().ptr())) - return svg_paintable->svg_viewport_size().to_type(); - - return {}; - }; - - // NB: Presentational attributes on SVGSVGElements are resolved irrespective of it's own viewBox (which only affects - // the internal coordinate system) - if (auto* svg_element = as_if(element)) { - auto const* parent = svg_element->parent_or_shadow_host_element(); - - // https://w3c.github.io/svgwg/svg2-draft/coords.html#InitialViewport - if (!parent || !is(*parent)) - return viewport_size_from_layout(*svg_element); - - // https://w3c.github.io/svgwg/svg2-draft/coords.html#EstablishingANewSVGViewport - if (is(*parent)) - return viewport_size_from_layout(*svg_element); - } - - auto viewport_element = element.owner_svg_element(); - - if (!viewport_element) - return {}; - - if (auto view_box = viewport_element->active_view_box(); view_box.has_value() && view_box->width > 0 && view_box->height > 0) - return { view_box->width, view_box->height }; - - return viewport_size_from_layout(*viewport_element); -} - static double percentage_resolution_basis_for_attribute_reflecting_length(GC::Ptr element, SVGLength::Directionality directionality) { // AD-HOC: This is defined in the algorithms of both SVGLength::value and SVGLength::convertToSpecifiedUnits so we @@ -107,7 +69,7 @@ static double percentage_resolution_basis_for_attribute_reflecting_length(GC::Pt // NB: Make sure the SVG layout is up to date so the viewport size is up to date. element->document().update_layout(DOM::UpdateLayoutReason::SVGLengthValue); - auto viewport = svg_viewport_size(*element); + auto viewport = element->viewport_size_for_percentage_resolution(); switch (directionality) { case SVGLength::Directionality::Horizontal: diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-01.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-01.txt new file mode 100644 index 0000000000000..158ffd59b0982 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-01.txt @@ -0,0 +1,12 @@ +Harness status: OK + +Found 6 tests + +5 Pass +1 Fail +Pass SVGGeometryElement.prototype.getTotalLength(), getTotalLength - path with pathLength +Pass SVGGeometryElement.prototype.getTotalLength(), getTotalLength - rect +Pass SVGGeometryElement.prototype.getTotalLength(), getTotalLength - rect in document +Pass SVGGeometryElement.prototype.getTotalLength(), getTotalLength - rect in document with percent units +Pass SVGGeometryElement.prototype.getTotalLength(), getTotalLength - rect in document with display none +Fail SVGGeometryElement.prototype.getTotalLength(), getTotalLength - path modified with setPathData \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-02.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-02.txt new file mode 100644 index 0000000000000..d1e0ee019a959 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-02.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass SVGGeometryElement.getTotalLength: 'display:none' \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGPathElement.getTotalLength-01.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGPathElement.getTotalLength-01.txt new file mode 100644 index 0000000000000..20b2b9abb7548 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/types/scripted/SVGPathElement.getTotalLength-01.txt @@ -0,0 +1,8 @@ +Harness status: OK + +Found 3 tests + +3 Pass +Pass SVGPathElement.getTotalLength: 'display:none', path with d attribute specified on element +Pass SVGPathElement.getTotalLength: 'display:none', path with d attribute specified as styles +Pass SVGPathElement.getTotalLength: 'display:none', path with d attribute specified on element and as styles \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-01.svg b/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-01.svg new file mode 100644 index 0000000000000..0e8965e250179 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-01.svg @@ -0,0 +1,94 @@ + + SVGGeometryElement.prototype.getTotalLength() + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-02.html b/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-02.html new file mode 100644 index 0000000000000..1dbd826d82af6 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGGeometryElement.getTotalLength-02.html @@ -0,0 +1,18 @@ + +SVGGeometryElement.getTotalLength: 'display:none' + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGPathElement.getTotalLength-01.html b/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGPathElement.getTotalLength-01.html new file mode 100644 index 0000000000000..03705b1e7a378 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/svg/types/scripted/SVGPathElement.getTotalLength-01.html @@ -0,0 +1,26 @@ + +SVGPathElement.getTotalLength: 'display:none' + + + + + + + + + +