diff --git a/Tests/LibWeb/Text/expected/SVG/svg-viewBox-attribute.txt b/Tests/LibWeb/Text/expected/SVG/svg-viewBox-attribute.txt new file mode 100644 index 0000000000..e25d6798aa --- /dev/null +++ b/Tests/LibWeb/Text/expected/SVG/svg-viewBox-attribute.txt @@ -0,0 +1,26 @@ + [object SVGSVGElement] +[object SVGAnimatedRect] +null +null +[object DOMRect] +1 +2 +3 +4 +[object DOMRect] +1 +2 +3 +4 +[object DOMRect] +5 +6 +7 +8 +[object DOMRect] +5 +6 +7 +8 +null +null diff --git a/Tests/LibWeb/Text/input/SVG/svg-viewBox-attribute.html b/Tests/LibWeb/Text/input/SVG/svg-viewBox-attribute.html new file mode 100644 index 0000000000..d5b2424d02 --- /dev/null +++ b/Tests/LibWeb/Text/input/SVG/svg-viewBox-attribute.html @@ -0,0 +1,39 @@ + + + diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 2164bb6699..039941e81d 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -559,6 +559,7 @@ set(SOURCES SVG/AttributeParser.cpp SVG/SVGAnimatedLength.cpp SVG/SVGAnimatedNumber.cpp + SVG/SVGAnimatedRect.cpp SVG/SVGAnimatedString.cpp SVG/SVGClipPathElement.cpp SVG/SVGDecodedImageData.cpp diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 7612fb1862..1ebaf204fd 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -622,6 +622,7 @@ struct UnderlyingSource; namespace Web::SVG { class SVGAnimatedLength; +class SVGAnimatedRect; class SVGCircleElement; class SVGClipPathElement; class SVGDefsElement; diff --git a/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.cpp b/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.cpp new file mode 100644 index 0000000000..1735c0b2a0 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::SVG { + +JS_DEFINE_ALLOCATOR(SVGAnimatedRect); + +SVGAnimatedRect::SVGAnimatedRect(JS::Realm& realm) + : Bindings::PlatformObject(realm) +{ +} + +SVGAnimatedRect::~SVGAnimatedRect() = default; + +void SVGAnimatedRect::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + set_prototype(&Bindings::ensure_web_prototype(realm, "SVGAnimatedRect"_fly_string)); + m_base_val = Geometry::DOMRect::create(realm, { 0, 0, 0, 0 }); + m_anim_val = Geometry::DOMRect::create(realm, { 0, 0, 0, 0 }); +} + +void SVGAnimatedRect::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_base_val); + visitor.visit(m_anim_val); +} + +JS::GCPtr SVGAnimatedRect::base_val() const +{ + if (m_nulled) + return nullptr; + return m_base_val; +} + +JS::GCPtr SVGAnimatedRect::anim_val() const +{ + if (m_nulled) + return nullptr; + return m_anim_val; +} + +void SVGAnimatedRect::set_nulled(bool nulled) +{ + m_nulled = nulled; +} + +void SVGAnimatedRect::set_base_val(Gfx::DoubleRect const& rect) +{ + m_base_val->set_x(rect.x()); + m_base_val->set_y(rect.y()); + m_base_val->set_width(rect.width()); + m_base_val->set_height(rect.height()); +} + +void SVGAnimatedRect::set_anim_val(Gfx::DoubleRect const& rect) +{ + m_anim_val->set_x(rect.x()); + m_anim_val->set_y(rect.y()); + m_anim_val->set_width(rect.width()); + m_anim_val->set_height(rect.height()); +} + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.h b/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.h new file mode 100644 index 0000000000..d5d9bbad06 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::SVG { + +class SVGAnimatedRect final : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(SVGAnimatedRect, Bindings::PlatformObject); + JS_DECLARE_ALLOCATOR(SVGAnimatedRect); + +public: + virtual ~SVGAnimatedRect(); + + JS::GCPtr base_val() const; + JS::GCPtr anim_val() const; + + void set_base_val(Gfx::DoubleRect const&); + void set_anim_val(Gfx::DoubleRect const&); + + void set_nulled(bool); + +private: + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Visitor&) override; + + explicit SVGAnimatedRect(JS::Realm&); + + JS::GCPtr m_base_val; + JS::GCPtr m_anim_val; + + bool m_nulled { true }; +}; + +} diff --git a/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.idl b/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.idl new file mode 100644 index 0000000000..cc2df28358 --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGAnimatedRect.idl @@ -0,0 +1,11 @@ +#import + +// https://svgwg.org/svg2-draft/types.html#InterfaceSVGAnimatedRect +[Exposed=Window] +interface SVGAnimatedRect { + // NOTE: The spec says that baseVal and animVal are not nullable, but they are nullable in some other engines. + [SameObject] readonly attribute DOMRect? baseVal; + + // NOTE: animVal is a DOMRectReadOnly in the spec, but other engines expose a DOMRect (sometimes aliased as SVGRect). + [SameObject] readonly attribute DOMRect? animVal; +}; diff --git a/Userland/Libraries/LibWeb/SVG/SVGFitToViewBox.idl b/Userland/Libraries/LibWeb/SVG/SVGFitToViewBox.idl new file mode 100644 index 0000000000..c92b537d4a --- /dev/null +++ b/Userland/Libraries/LibWeb/SVG/SVGFitToViewBox.idl @@ -0,0 +1,7 @@ +#import + +// https://svgwg.org/svg2-draft/types.html#InterfaceSVGFitToViewBox +interface mixin SVGFitToViewBox { + [SameObject, ImplementedAs=view_box_for_bindings] readonly attribute SVGAnimatedRect viewBox; + // FIXME: [SameObject] readonly attribute SVGAnimatedPreserveAspectRatio preserveAspectRatio; +}; diff --git a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp index aedc46e6a2..71989e382f 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace Web::SVG { @@ -29,6 +30,13 @@ void SVGSVGElement::initialize(JS::Realm& realm) { Base::initialize(realm); set_prototype(&Bindings::ensure_web_prototype(realm, "SVGSVGElement"_fly_string)); + m_view_box_for_bindings = heap().allocate(realm, realm); +} + +void SVGSVGElement::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_view_box_for_bindings); } JS::GCPtr SVGSVGElement::create_layout_node(NonnullRefPtr style) @@ -67,8 +75,18 @@ void SVGSVGElement::attribute_changed(FlyString const& name, Optional co { SVGGraphicsElement::attribute_changed(name, value); - if (name.equals_ignoring_ascii_case(SVG::AttributeNames::viewBox)) - m_view_box = try_parse_view_box(value.value_or(String {})); + if (name.equals_ignoring_ascii_case(SVG::AttributeNames::viewBox)) { + if (!value.has_value()) { + m_view_box_for_bindings->set_nulled(true); + } else { + m_view_box = try_parse_view_box(value.value_or(String {})); + m_view_box_for_bindings->set_nulled(!m_view_box.has_value()); + if (m_view_box.has_value()) { + m_view_box_for_bindings->set_base_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height }); + m_view_box_for_bindings->set_anim_val(Gfx::DoubleRect { m_view_box->min_x, m_view_box->min_y, m_view_box->width, m_view_box->height }); + } + } + } if (name.equals_ignoring_ascii_case(SVG::AttributeNames::preserveAspectRatio)) m_preserve_aspect_ratio = AttributeParser::parse_preserve_aspect_ratio(value.value_or(String {})); if (name.equals_ignoring_ascii_case(SVG::AttributeNames::width) || name.equals_ignoring_ascii_case(SVG::AttributeNames::height)) diff --git a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.h b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.h index 084ad02622..f21fa1dd58 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.h +++ b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.h @@ -30,10 +30,13 @@ public: void set_fallback_view_box_for_svg_as_image(Optional); + JS::NonnullGCPtr view_box_for_bindings() { return *m_view_box_for_bindings; } + private: SVGSVGElement(DOM::Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Visitor&) override; virtual bool is_svg_svg_element() const override { return true; } @@ -45,6 +48,8 @@ private: Optional m_preserve_aspect_ratio; Optional m_fallback_view_box_for_svg_as_image; + + JS::GCPtr m_view_box_for_bindings; }; } diff --git a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.idl b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.idl index a060320b28..e09f07a995 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGSVGElement.idl +++ b/Userland/Libraries/LibWeb/SVG/SVGSVGElement.idl @@ -1,7 +1,10 @@ #import +#import // https://svgwg.org/svg2-draft/struct.html#InterfaceSVGSVGElement [Exposed=Window] interface SVGSVGElement : SVGGraphicsElement { }; + +SVGSVGElement includes SVGFitToViewBox; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 7f96c54c3b..ac2308145f 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -229,6 +229,7 @@ libweb_js_bindings(Streams/WritableStreamDefaultController) libweb_js_bindings(Streams/WritableStreamDefaultWriter) libweb_js_bindings(SVG/SVGAnimatedLength) libweb_js_bindings(SVG/SVGAnimatedNumber) +libweb_js_bindings(SVG/SVGAnimatedRect) libweb_js_bindings(SVG/SVGAnimatedString) libweb_js_bindings(SVG/SVGClipPathElement) libweb_js_bindings(SVG/SVGDefsElement)