2023-05-30 21:23:52 +01:00
/*
* Copyright ( c ) 2023 , Preston Taylor < 95388976 + PrestonLTaylor @ users . noreply . github . com >
2024-07-28 02:34:05 +12:00
* Copyright ( c ) 2024 , Shannon Booth < shannon @ serenityos . org >
2023-05-30 21:23:52 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <LibWeb/Bindings/Intrinsics.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/SVGUseElementPrototype.h>
2023-05-30 21:23:52 +01:00
# include <LibWeb/DOM/Document.h>
2024-07-28 02:34:05 +12:00
# include <LibWeb/DOM/DocumentLoadEventDelayer.h>
2023-05-30 21:23:52 +01:00
# include <LibWeb/DOM/ElementFactory.h>
# include <LibWeb/DOM/Event.h>
# include <LibWeb/DOM/ShadowRoot.h>
2024-07-28 02:34:05 +12:00
# include <LibWeb/HTML/PotentialCORSRequest.h>
2025-04-10 15:52:33 +01:00
# include <LibWeb/HTML/SharedResourceRequest.h>
2023-08-19 19:42:31 +02:00
# include <LibWeb/Layout/Box.h>
2024-03-10 14:41:00 +01:00
# include <LibWeb/Layout/SVGGraphicsBox.h>
2023-05-30 21:23:52 +01:00
# include <LibWeb/Namespace.h>
# include <LibWeb/SVG/AttributeNames.h>
2024-07-28 02:34:05 +12:00
# include <LibWeb/SVG/SVGDecodedImageData.h>
2023-05-30 21:23:52 +01:00
# include <LibWeb/SVG/SVGSVGElement.h>
2025-08-26 13:44:50 +01:00
# include <LibWeb/SVG/SVGSymbolElement.h>
2023-05-30 21:23:52 +01:00
# include <LibWeb/SVG/SVGUseElement.h>
namespace Web : : SVG {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( SVGUseElement ) ;
2023-11-19 19:47:52 +01:00
2023-05-30 21:23:52 +01:00
SVGUseElement : : SVGUseElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
: SVGGraphicsElement ( document , qualified_name )
{
}
2023-08-07 08:41:28 +02:00
void SVGUseElement : : initialize ( JS : : Realm & realm )
2023-05-30 21:23:52 +01:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( SVGUseElement ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-05-30 21:23:52 +01:00
2025-08-07 16:32:28 +02:00
// NOTE: The spec says "The shadow tree is open (inspectable by script), but read-only."
// This doesn't actually match other browsers, and there's a spec issue to change it.
// Spec bug: https://github.com/w3c/svgwg/issues/875
auto shadow_root = realm . create < DOM : : ShadowRoot > ( document ( ) , * this , Bindings : : ShadowRootMode : : Closed ) ;
2023-05-30 21:23:52 +01:00
// The user agent must create a use-element shadow tree whose host is the ‘ use’ element itself
set_shadow_root ( shadow_root ) ;
2024-11-14 05:50:17 +13:00
m_document_observer = realm . create < DOM : : DocumentObserver > ( realm , document ( ) ) ;
2023-09-27 17:23:48 +02:00
m_document_observer - > set_document_completely_loaded ( [ this ] ( ) {
2023-05-30 21:23:52 +01:00
clone_element_tree_as_our_shadow_tree ( referenced_element ( ) ) ;
2023-09-27 17:23:48 +02:00
} ) ;
2023-05-30 21:23:52 +01:00
}
void SVGUseElement : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2023-11-14 01:03:19 +00:00
SVGURIReferenceMixin : : visit_edges ( visitor ) ;
2023-05-30 21:23:52 +01:00
visitor . visit ( m_document_observer ) ;
2024-08-03 15:27:08 +12:00
visitor . visit ( m_resource_request ) ;
2023-05-30 21:23:52 +01:00
}
2024-11-14 08:14:16 -05:00
void SVGUseElement : : attribute_changed ( FlyString const & name , Optional < String > const & old_value , Optional < String > const & value , Optional < FlyString > const & namespace_ )
2023-05-30 21:23:52 +01:00
{
2024-11-14 08:14:16 -05:00
Base : : attribute_changed ( name , old_value , value , namespace_ ) ;
2023-05-30 21:23:52 +01:00
// https://svgwg.org/svg2-draft/struct.html#UseLayout
if ( name = = SVG : : AttributeNames : : x ) {
2023-11-19 18:10:36 +13:00
m_x = AttributeParser : : parse_coordinate ( value . value_or ( String { } ) ) ;
2023-05-30 21:23:52 +01:00
} else if ( name = = SVG : : AttributeNames : : y ) {
2023-11-19 18:10:36 +13:00
m_y = AttributeParser : : parse_coordinate ( value . value_or ( String { } ) ) ;
2024-03-27 00:08:14 +00:00
} else if ( name = = SVG : : AttributeNames : : href | | name = = " xlink:href " _fly_string ) {
2024-07-27 15:43:56 +12:00
// When the ‘ href’ attribute is set (or, in the absence of an ‘ href’ attribute, an ‘ xlink:href’ attribute), the user agent must process the URL.
process_the_url ( value ) ;
2023-05-30 21:23:52 +01:00
}
}
2024-07-27 15:43:56 +12:00
// https://www.w3.org/TR/SVG2/linking.html#processingURL
void SVGUseElement : : process_the_url ( Optional < String > const & href )
{
2024-07-28 02:34:05 +12:00
// In all other cases, the URL is for a resource to be used in this SVG document. The user agent
// must parse the URL to separate out the target fragment from the rest of the URL, and compare
// it with the document base URL. If all parts other than the target fragment are equal, this is
// a same-document URL reference, and processing the URL must continue as indicated in Identifying
// the target element with the current document as the referenced document.
m_href = document ( ) . url ( ) . complete_url ( href . value_or ( String { } ) ) ;
2025-02-15 22:55:46 +13:00
if ( ! m_href . has_value ( ) )
2024-07-28 02:34:05 +12:00
return ;
2024-07-27 15:43:56 +12:00
2025-04-07 10:46:22 +00:00
if ( is_referenced_element_same_document ( ) ) {
2024-07-28 02:34:05 +12:00
clone_element_tree_as_our_shadow_tree ( referenced_element ( ) ) ;
} else {
2025-02-15 22:55:46 +13:00
fetch_the_document ( * m_href ) ;
2024-07-28 02:34:05 +12:00
}
2024-07-27 15:43:56 +12:00
}
2025-04-07 10:46:22 +00:00
bool SVGUseElement : : is_referenced_element_same_document ( ) const
2023-05-30 21:23:52 +01:00
{
2025-02-15 22:55:46 +13:00
return m_href - > equals ( document ( ) . url ( ) , URL : : ExcludeFragment : : Yes ) ;
2023-05-30 21:23:52 +01:00
}
2023-08-31 08:58:41 +01:00
Gfx : : AffineTransform SVGUseElement : : element_transform ( ) const
2023-05-30 21:23:52 +01: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 08:58:41 +01:00
return Base : : element_transform ( ) . translate ( m_x . value_or ( 0 ) , m_y . value_or ( 0 ) ) ;
}
void SVGUseElement : : inserted ( )
{
Base : : inserted ( ) ;
2023-05-30 21:23:52 +01:00
}
void SVGUseElement : : svg_element_changed ( SVGElement & svg_element )
{
auto to_clone = referenced_element ( ) ;
if ( ! to_clone ) {
return ;
}
2023-07-03 17:08:37 +02: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 21:23:52 +01: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 )
{
2025-04-07 10:46:22 +00:00
if ( ! m_href . has_value ( ) | | ! m_href - > fragment ( ) . has_value ( ) | | ! is_referenced_element_same_document ( ) ) {
2023-05-30 21:23:52 +01:00
return ;
}
2025-02-15 22:55:46 +13:00
if ( AK : : StringUtils : : matches ( svg_element . get_attribute_value ( " id " _fly_string ) , m_href - > fragment ( ) . value ( ) ) ) {
2023-05-30 21:23:52 +01:00
shadow_root ( ) - > remove_all_children ( ) ;
}
}
2024-07-28 02:34:05 +12:00
// https://svgwg.org/svg2-draft/linking.html#processingURL-target
2024-11-15 04:01:23 +13:00
GC : : Ptr < DOM : : Element > SVGUseElement : : referenced_element ( )
2023-05-30 21:23:52 +01:00
{
2025-02-15 22:55:46 +13:00
if ( ! m_href . has_value ( ) )
2023-05-30 21:23:52 +01:00
return nullptr ;
2025-02-15 22:55:46 +13:00
if ( ! m_href - > fragment ( ) . has_value ( ) )
2024-07-28 02:34:05 +12:00
return nullptr ;
2025-04-07 10:46:22 +00:00
if ( is_referenced_element_same_document ( ) )
2025-02-15 22:55:46 +13:00
return document ( ) . get_element_by_id ( * m_href - > fragment ( ) ) ;
2024-07-28 02:34:05 +12:00
2024-08-03 15:27:08 +12:00
if ( ! m_resource_request )
2024-07-28 02:34:05 +12:00
return nullptr ;
2024-08-03 15:27:08 +12:00
auto data = m_resource_request - > image_data ( ) ;
2024-07-28 02:34:05 +12:00
if ( ! data | | ! is < SVG : : SVGDecodedImageData > ( * data ) )
return nullptr ;
2025-02-15 22:55:46 +13:00
return as < SVG : : SVGDecodedImageData > ( * data ) . svg_document ( ) . get_element_by_id ( * m_href - > fragment ( ) ) ;
2024-07-28 02:34:05 +12:00
}
// https://svgwg.org/svg2-draft/linking.html#processingURL-fetch
void SVGUseElement : : fetch_the_document ( URL : : URL const & url )
{
m_load_event_delayer . emplace ( document ( ) ) ;
2024-08-03 15:27:08 +12:00
m_resource_request = HTML : : SharedResourceRequest : : get_or_create ( realm ( ) , document ( ) . page ( ) , url ) ;
m_resource_request - > add_callbacks (
2024-07-28 02:34:05 +12:00
[ this ] {
clone_element_tree_as_our_shadow_tree ( referenced_element ( ) ) ;
m_load_event_delayer . clear ( ) ;
} ,
[ this ] {
m_load_event_delayer . clear ( ) ;
} ) ;
2024-08-03 15:27:08 +12:00
if ( m_resource_request - > needs_fetching ( ) ) {
2024-07-28 02:34:05 +12:00
auto request = HTML : : create_potential_CORS_request ( vm ( ) , url , Fetch : : Infrastructure : : Request : : Destination : : Image , HTML : : CORSSettingAttribute : : NoCORS ) ;
request - > set_client ( & document ( ) . relevant_settings_object ( ) ) ;
2024-08-03 15:27:08 +12:00
m_resource_request - > fetch_resource ( realm ( ) , request ) ;
2024-07-28 02:34:05 +12:00
}
2023-05-30 21:23:52 +01:00
}
// https://svgwg.org/svg2-draft/struct.html#UseShadowTree
2024-07-28 00:53:52 +12:00
void SVGUseElement : : clone_element_tree_as_our_shadow_tree ( Element * to_clone )
2023-05-30 21:23:52 +01:00
{
2024-07-28 00:53:52 +12:00
shadow_root ( ) - > remove_all_children ( ) ;
2023-05-30 21:23:52 +01:00
2024-07-28 00:48:59 +12:00
if ( to_clone & & is_valid_reference_element ( * to_clone ) ) {
2023-05-30 21:23:52 +01:00
// The ‘ use’ element references another element, a copy of which is rendered in place of the ‘ use’ in the document.
2024-06-25 11:06:41 +02:00
auto cloned_reference_node = MUST ( to_clone - > clone_node ( nullptr , true ) ) ;
2025-08-26 13:44:50 +01:00
if ( is < SVGSVGElement > ( cloned_reference_node . ptr ( ) ) | | is < SVGSymbolElement > ( cloned_reference_node . ptr ( ) ) ) {
auto & cloned_element = as < SVGElement > ( * cloned_reference_node ) ;
// The width and height properties on the ‘ use’ element override the values for the corresponding
// properties on a referenced ‘ svg’ or ‘ symbol’ element when determining the used value for that property
// on the instance root element. However, if the computed value for the property on the ‘ use’ element is
// auto, then the property is computed as normal for the element instance.
if ( has_attribute ( AttributeNames : : width ) ) {
MUST ( cloned_element . set_attribute ( AttributeNames : : width , get_attribute_value ( AttributeNames : : width ) ) ) ;
}
if ( has_attribute ( AttributeNames : : height ) ) {
MUST ( cloned_element . set_attribute ( AttributeNames : : height , get_attribute_value ( AttributeNames : : height ) ) ) ;
}
}
2024-07-28 00:53:52 +12:00
shadow_root ( ) - > append_child ( cloned_reference_node ) . release_value_but_fixme_should_propagate_errors ( ) ;
2023-05-30 21:23:52 +01:00
}
}
2024-07-28 00:48:59 +12:00
bool SVGUseElement : : is_valid_reference_element ( Element const & reference_element ) const
2023-05-30 21:23:52 +01:00
{
// 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.
2024-07-28 00:48:59 +12:00
return reference_element . is_svg_element ( ) & & ! reference_element . is_ancestor_of ( * this ) ;
2023-05-30 21:23:52 +01:00
}
// https://www.w3.org/TR/SVG11/shapes.html#RectElementXAttribute
2024-11-15 04:01:23 +13:00
GC : : Ref < SVGAnimatedLength > SVGUseElement : : x ( ) const
2023-05-30 21:23:52 +01:00
{
// FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported.
2025-08-26 16:51:46 +02:00
auto base_length = SVGLength : : create ( realm ( ) , 0 , m_x . value_or ( 0 ) , SVGLength : : ReadOnly : : No ) ;
auto anim_length = SVGLength : : create ( realm ( ) , 0 , m_x . value_or ( 0 ) , SVGLength : : ReadOnly : : Yes ) ;
return SVGAnimatedLength : : create ( realm ( ) , base_length , anim_length ) ;
2023-05-30 21:23:52 +01:00
}
// https://www.w3.org/TR/SVG11/shapes.html#RectElementYAttribute
2024-11-15 04:01:23 +13:00
GC : : Ref < SVGAnimatedLength > SVGUseElement : : y ( ) const
2023-05-30 21:23:52 +01:00
{
// FIXME: Populate the unit type when it is parsed (0 here is "unknown").
// FIXME: Create a proper animated value when animations are supported.
2025-08-26 16:51:46 +02:00
auto base_length = SVGLength : : create ( realm ( ) , 0 , m_y . value_or ( 0 ) , SVGLength : : ReadOnly : : No ) ;
auto anim_length = SVGLength : : create ( realm ( ) , 0 , m_y . value_or ( 0 ) , SVGLength : : ReadOnly : : Yes ) ;
return SVGAnimatedLength : : create ( realm ( ) , base_length , anim_length ) ;
2023-05-30 21:23:52 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ref < SVGAnimatedLength > SVGUseElement : : width ( ) const
2023-05-30 21:23:52 +01:00
{
2025-08-26 16:51:46 +02:00
return fake_animated_length_fixme ( ) ;
2023-05-30 21:23:52 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ref < SVGAnimatedLength > SVGUseElement : : height ( ) const
2023-05-30 21:23:52 +01:00
{
2025-08-26 16:51:46 +02:00
return fake_animated_length_fixme ( ) ;
2023-05-30 21:23:52 +01:00
}
// https://svgwg.org/svg2-draft/struct.html#TermInstanceRoot
2024-11-15 04:01:23 +13:00
GC : : Ptr < SVGElement > SVGUseElement : : instance_root ( ) const
2023-05-30 21:23:52 +01:00
{
2024-06-25 11:28:58 +02:00
return const_cast < DOM : : ShadowRoot & > ( * shadow_root ( ) ) . first_child_of_type < SVGElement > ( ) ;
2023-05-30 21:23:52 +01:00
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < SVGElement > SVGUseElement : : animated_instance_root ( ) const
2023-05-30 21:23:52 +01:00
{
return instance_root ( ) ;
}
2024-12-20 16:35:12 +01:00
GC : : Ptr < Layout : : Node > SVGUseElement : : create_layout_node ( GC : : Ref < CSS : : ComputedProperties > style )
2023-08-19 19:42:31 +02:00
{
2024-11-14 06:13:46 +13:00
return heap ( ) . allocate < Layout : : SVGGraphicsBox > ( document ( ) , * this , move ( style ) ) ;
2023-08-19 19:42:31 +02:00
}
2023-05-30 21:23:52 +01:00
}