2023-05-30 23:23:52 +03:00
/*
* Copyright ( c ) 2023 , Preston Taylor < 95388976 + PrestonLTaylor @ users . noreply . github . com >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibWeb/Bindings/Intrinsics.h>
# include <LibWeb/DOM/Document.h>
# include <LibWeb/DOM/ElementFactory.h>
# include <LibWeb/DOM/Event.h>
# include <LibWeb/DOM/ShadowRoot.h>
2023-08-19 20:42:31 +03:00
# include <LibWeb/Layout/Box.h>
2024-03-10 16:41:00 +03:00
# include <LibWeb/Layout/SVGGraphicsBox.h>
2023-05-30 23:23:52 +03:00
# include <LibWeb/Namespace.h>
# include <LibWeb/SVG/AttributeNames.h>
# include <LibWeb/SVG/SVGSVGElement.h>
# include <LibWeb/SVG/SVGUseElement.h>
namespace Web : : SVG {
2023-11-19 21:47:52 +03:00
JS_DEFINE_ALLOCATOR ( SVGUseElement ) ;
2023-05-30 23:23:52 +03:00
SVGUseElement : : SVGUseElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
: SVGGraphicsElement ( document , qualified_name )
{
}
2023-08-07 09:41:28 +03:00
void SVGUseElement : : initialize ( JS : : Realm & realm )
2023-05-30 23:23:52 +03:00
{
2023-08-07 09:41:28 +03:00
Base : : initialize ( realm ) ;
2024-03-16 15:13:08 +03:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( SVGUseElement ) ;
2023-05-30 23:23:52 +03:00
// The shadow tree is open (inspectable by script), but read-only.
2023-08-13 14:05:26 +03:00
auto shadow_root = heap ( ) . allocate < DOM : : ShadowRoot > ( realm , document ( ) , * this , Bindings : : ShadowRootMode : : Open ) ;
2023-05-30 23:23:52 +03:00
// The user agent must create a use-element shadow tree whose host is the ‘ use’ element itself
set_shadow_root ( shadow_root ) ;
2023-08-13 14:05:26 +03:00
m_document_observer = realm . heap ( ) . allocate < DOM : : DocumentObserver > ( realm , realm , document ( ) ) ;
2023-09-27 18:23:48 +03:00
m_document_observer - > set_document_completely_loaded ( [ this ] ( ) {
2023-05-30 23:23:52 +03:00
clone_element_tree_as_our_shadow_tree ( referenced_element ( ) ) ;
2023-09-27 18:23:48 +03:00
} ) ;
2023-05-30 23:23:52 +03:00
}
void SVGUseElement : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2023-11-14 04:03:19 +03:00
SVGURIReferenceMixin : : visit_edges ( visitor ) ;
2023-05-30 23:23:52 +03:00
visitor . visit ( m_document_observer ) ;
}
2023-11-19 08:10:36 +03:00
void SVGUseElement : : attribute_changed ( FlyString const & name , Optional < String > const & value )
2023-05-30 23:23:52 +03:00
{
2023-07-03 18:08:37 +03:00
Base : : attribute_changed ( name , value ) ;
2023-05-30 23:23:52 +03:00
// https://svgwg.org/svg2-draft/struct.html#UseLayout
if ( name = = SVG : : AttributeNames : : x ) {
2023-11-19 08:10:36 +03:00
m_x = AttributeParser : : parse_coordinate ( value . value_or ( String { } ) ) ;
2023-05-30 23:23:52 +03:00
} else if ( name = = SVG : : AttributeNames : : y ) {
2023-11-19 08:10:36 +03:00
m_y = AttributeParser : : parse_coordinate ( value . value_or ( String { } ) ) ;
2023-05-30 23:23:52 +03:00
} else if ( name = = SVG : : AttributeNames : : href ) {
// FIXME: Support the xlink:href attribute as a fallback
2023-12-24 05:39:06 +03:00
m_referenced_id = parse_id_from_href ( value . value_or ( String { } ) ) ;
2023-08-22 04:59:48 +03:00
clone_element_tree_as_our_shadow_tree ( referenced_element ( ) ) ;
2023-05-30 23:23:52 +03:00
}
}
2023-12-24 05:39:06 +03:00
Optional < FlyString > SVGUseElement : : parse_id_from_href ( StringView href )
2023-05-30 23:23:52 +03:00
{
auto id_seperator = href . find ( ' # ' ) ;
if ( ! id_seperator . has_value ( ) ) {
return { } ;
}
2023-10-11 17:13:17 +03:00
auto id = href . substring_view ( id_seperator . value ( ) + 1 ) ;
return MUST ( FlyString : : from_utf8 ( id ) ) ;
2023-05-30 23:23:52 +03:00
}
2023-08-31 10:58:41 +03:00
Gfx : : AffineTransform SVGUseElement : : element_transform ( ) const
2023-05-30 23:23:52 +03:00
{
// The x and y properties define an additional transformation (translate(x,y), where x and y represent the computed value of the corresponding property)
// to be applied to the ‘ use’ element, after any transformations specified with other properties
2023-08-31 10:58:41 +03:00
return Base : : element_transform ( ) . translate ( m_x . value_or ( 0 ) , m_y . value_or ( 0 ) ) ;
}
void SVGUseElement : : inserted ( )
{
Base : : inserted ( ) ;
2023-05-30 23:23:52 +03:00
}
void SVGUseElement : : svg_element_changed ( SVGElement & svg_element )
{
auto to_clone = referenced_element ( ) ;
if ( ! to_clone ) {
return ;
}
2023-07-03 18:08:37 +03:00
// NOTE: We need to check the ancestor because attribute_changed of a child doesn't call children_changed on the parent(s)
2023-05-30 23:23:52 +03:00
if ( to_clone = = & svg_element | | to_clone - > is_ancestor_of ( svg_element ) ) {
clone_element_tree_as_our_shadow_tree ( to_clone ) ;
}
}
void SVGUseElement : : svg_element_removed ( SVGElement & svg_element )
{
if ( ! m_referenced_id . has_value ( ) ) {
return ;
}
2024-01-16 21:04:45 +03:00
if ( AK : : StringUtils : : matches ( svg_element . get_attribute_value ( " id " _fly_string ) , m_referenced_id . value ( ) ) ) {
2023-05-30 23:23:52 +03:00
shadow_root ( ) - > remove_all_children ( ) ;
}
}
JS : : GCPtr < DOM : : Element > SVGUseElement : : referenced_element ( )
{
if ( ! m_referenced_id . has_value ( ) ) {
return nullptr ;
}
// FIXME: Support loading of external svg documents
2023-10-11 17:13:17 +03:00
return document ( ) . get_element_by_id ( m_referenced_id . value ( ) ) ;
2023-05-30 23:23:52 +03:00
}
// https://svgwg.org/svg2-draft/struct.html#UseShadowTree
void SVGUseElement : : clone_element_tree_as_our_shadow_tree ( Element * to_clone ) const
{
shadow_root ( ) - > remove_all_children ( ) ;
if ( to_clone & & is_valid_reference_element ( to_clone ) ) {
// The ‘ use’ element references another element, a copy of which is rendered in place of the ‘ use’ in the document.
auto cloned_reference_node = to_clone - > clone_node ( nullptr , true ) ;
shadow_root ( ) - > append_child ( cloned_reference_node ) . release_value_but_fixme_should_propagate_errors ( ) ;
}
}
bool SVGUseElement : : is_valid_reference_element ( Element * reference_element ) const
{
// If the referenced element that results from resolving the URL is not an SVG element, then the reference is invalid and the ‘ use’ element is in error.
// If the referenced element is a (shadow-including) ancestor of the ‘ use’ element, then this is an invalid circular reference and the ‘ use’ element is in error.
return reference_element - > is_svg_element ( ) & & ! reference_element - > is_ancestor_of ( * this ) ;
}
// https://www.w3.org/TR/SVG11/shapes.html#RectElementXAttribute
JS : : NonnullGCPtr < SVGAnimatedLength > SVGUseElement : : x ( ) const
{
// FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported.
2023-08-13 14:05:26 +03:00
auto base_length = SVGLength : : create ( realm ( ) , 0 , m_x . value_or ( 0 ) ) ;
auto anim_length = SVGLength : : create ( realm ( ) , 0 , m_x . value_or ( 0 ) ) ;
return SVGAnimatedLength : : create ( realm ( ) , move ( base_length ) , move ( anim_length ) ) ;
2023-05-30 23:23:52 +03:00
}
// https://www.w3.org/TR/SVG11/shapes.html#RectElementYAttribute
JS : : NonnullGCPtr < SVGAnimatedLength > SVGUseElement : : y ( ) const
{
// FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported.
2023-08-13 14:05:26 +03:00
auto base_length = SVGLength : : create ( realm ( ) , 0 , m_y . value_or ( 0 ) ) ;
auto anim_length = SVGLength : : create ( realm ( ) , 0 , m_y . value_or ( 0 ) ) ;
return SVGAnimatedLength : : create ( realm ( ) , move ( base_length ) , move ( anim_length ) ) ;
2023-05-30 23:23:52 +03:00
}
JS : : NonnullGCPtr < SVGAnimatedLength > SVGUseElement : : width ( ) const
{
2024-03-11 17:34:24 +03:00
// FIXME: Implement this properly.
return SVGAnimatedLength : : create ( realm ( ) , SVGLength : : create ( realm ( ) , 0 , 0 ) , SVGLength : : create ( realm ( ) , 0 , 0 ) ) ;
2023-05-30 23:23:52 +03:00
}
JS : : NonnullGCPtr < SVGAnimatedLength > SVGUseElement : : height ( ) const
{
2024-03-11 17:34:24 +03:00
// FIXME: Implement this properly.
return SVGAnimatedLength : : create ( realm ( ) , SVGLength : : create ( realm ( ) , 0 , 0 ) , SVGLength : : create ( realm ( ) , 0 , 0 ) ) ;
2023-05-30 23:23:52 +03:00
}
// https://svgwg.org/svg2-draft/struct.html#TermInstanceRoot
JS : : GCPtr < SVGElement > SVGUseElement : : instance_root ( ) const
{
return shadow_root ( ) - > first_child_of_type < SVGElement > ( ) ;
}
JS : : GCPtr < SVGElement > SVGUseElement : : animated_instance_root ( ) const
{
return instance_root ( ) ;
}
2023-08-19 20:42:31 +03:00
JS : : GCPtr < Layout : : Node > SVGUseElement : : create_layout_node ( NonnullRefPtr < CSS : : StyleProperties > style )
{
2024-03-10 16:41:00 +03:00
return heap ( ) . allocate_without_realm < Layout : : SVGGraphicsBox > ( document ( ) , * this , move ( style ) ) ;
2023-08-19 20:42:31 +03:00
}
2023-05-30 23:23:52 +03:00
}