SVGUseElement.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /*
  2. * Copyright (c) 2023, Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/Bindings/Intrinsics.h>
  7. #include <LibWeb/Bindings/SVGUseElementPrototype.h>
  8. #include <LibWeb/DOM/Document.h>
  9. #include <LibWeb/DOM/ElementFactory.h>
  10. #include <LibWeb/DOM/Event.h>
  11. #include <LibWeb/DOM/ShadowRoot.h>
  12. #include <LibWeb/Layout/Box.h>
  13. #include <LibWeb/Layout/SVGGraphicsBox.h>
  14. #include <LibWeb/Namespace.h>
  15. #include <LibWeb/SVG/AttributeNames.h>
  16. #include <LibWeb/SVG/SVGSVGElement.h>
  17. #include <LibWeb/SVG/SVGUseElement.h>
  18. namespace Web::SVG {
  19. JS_DEFINE_ALLOCATOR(SVGUseElement);
  20. SVGUseElement::SVGUseElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  21. : SVGGraphicsElement(document, qualified_name)
  22. {
  23. }
  24. void SVGUseElement::initialize(JS::Realm& realm)
  25. {
  26. Base::initialize(realm);
  27. WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGUseElement);
  28. // The shadow tree is open (inspectable by script), but read-only.
  29. auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm, document(), *this, Bindings::ShadowRootMode::Open);
  30. // The user agent must create a use-element shadow tree whose host is the ‘use’ element itself
  31. set_shadow_root(shadow_root);
  32. m_document_observer = realm.heap().allocate<DOM::DocumentObserver>(realm, realm, document());
  33. m_document_observer->set_document_completely_loaded([this]() {
  34. clone_element_tree_as_our_shadow_tree(referenced_element());
  35. });
  36. }
  37. void SVGUseElement::visit_edges(Cell::Visitor& visitor)
  38. {
  39. Base::visit_edges(visitor);
  40. SVGURIReferenceMixin::visit_edges(visitor);
  41. visitor.visit(m_document_observer);
  42. }
  43. void SVGUseElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value)
  44. {
  45. Base::attribute_changed(name, old_value, value);
  46. // https://svgwg.org/svg2-draft/struct.html#UseLayout
  47. if (name == SVG::AttributeNames::x) {
  48. m_x = AttributeParser::parse_coordinate(value.value_or(String {}));
  49. } else if (name == SVG::AttributeNames::y) {
  50. m_y = AttributeParser::parse_coordinate(value.value_or(String {}));
  51. } else if (name == SVG::AttributeNames::href || name == "xlink:href"_fly_string) {
  52. // 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.
  53. process_the_url(value);
  54. }
  55. }
  56. // https://www.w3.org/TR/SVG2/linking.html#processingURL
  57. void SVGUseElement::process_the_url(Optional<String> const& href)
  58. {
  59. m_referenced_id = parse_id_from_href(href.value_or(String {}));
  60. clone_element_tree_as_our_shadow_tree(referenced_element());
  61. }
  62. Optional<FlyString> SVGUseElement::parse_id_from_href(StringView href)
  63. {
  64. auto id_seperator = href.find('#');
  65. if (!id_seperator.has_value()) {
  66. return {};
  67. }
  68. auto id = href.substring_view(id_seperator.value() + 1);
  69. return MUST(FlyString::from_utf8(id));
  70. }
  71. Gfx::AffineTransform SVGUseElement::element_transform() const
  72. {
  73. // The x and y properties define an additional transformation (translate(x,y), where x and y represent the computed value of the corresponding property)
  74. // to be applied to the ‘use’ element, after any transformations specified with other properties
  75. return Base::element_transform().translate(m_x.value_or(0), m_y.value_or(0));
  76. }
  77. void SVGUseElement::inserted()
  78. {
  79. Base::inserted();
  80. }
  81. void SVGUseElement::svg_element_changed(SVGElement& svg_element)
  82. {
  83. auto to_clone = referenced_element();
  84. if (!to_clone) {
  85. return;
  86. }
  87. // NOTE: We need to check the ancestor because attribute_changed of a child doesn't call children_changed on the parent(s)
  88. if (to_clone == &svg_element || to_clone->is_ancestor_of(svg_element)) {
  89. clone_element_tree_as_our_shadow_tree(to_clone);
  90. }
  91. }
  92. void SVGUseElement::svg_element_removed(SVGElement& svg_element)
  93. {
  94. if (!m_referenced_id.has_value()) {
  95. return;
  96. }
  97. if (AK::StringUtils::matches(svg_element.get_attribute_value("id"_fly_string), m_referenced_id.value())) {
  98. shadow_root()->remove_all_children();
  99. }
  100. }
  101. JS::GCPtr<DOM::Element> SVGUseElement::referenced_element()
  102. {
  103. if (!m_referenced_id.has_value()) {
  104. return nullptr;
  105. }
  106. // FIXME: Support loading of external svg documents
  107. return document().get_element_by_id(m_referenced_id.value());
  108. }
  109. // https://svgwg.org/svg2-draft/struct.html#UseShadowTree
  110. void SVGUseElement::clone_element_tree_as_our_shadow_tree(Element* to_clone)
  111. {
  112. shadow_root()->remove_all_children();
  113. if (to_clone && is_valid_reference_element(*to_clone)) {
  114. // The ‘use’ element references another element, a copy of which is rendered in place of the ‘use’ in the document.
  115. auto cloned_reference_node = MUST(to_clone->clone_node(nullptr, true));
  116. shadow_root()->append_child(cloned_reference_node).release_value_but_fixme_should_propagate_errors();
  117. }
  118. }
  119. bool SVGUseElement::is_valid_reference_element(Element const& reference_element) const
  120. {
  121. // 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.
  122. // 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.
  123. return reference_element.is_svg_element() && !reference_element.is_ancestor_of(*this);
  124. }
  125. // https://www.w3.org/TR/SVG11/shapes.html#RectElementXAttribute
  126. JS::NonnullGCPtr<SVGAnimatedLength> SVGUseElement::x() const
  127. {
  128. // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
  129. // FIXME: Create a proper animated value when animations are supported.
  130. auto base_length = SVGLength::create(realm(), 0, m_x.value_or(0));
  131. auto anim_length = SVGLength::create(realm(), 0, m_x.value_or(0));
  132. return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length));
  133. }
  134. // https://www.w3.org/TR/SVG11/shapes.html#RectElementYAttribute
  135. JS::NonnullGCPtr<SVGAnimatedLength> SVGUseElement::y() const
  136. {
  137. // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
  138. // FIXME: Create a proper animated value when animations are supported.
  139. auto base_length = SVGLength::create(realm(), 0, m_y.value_or(0));
  140. auto anim_length = SVGLength::create(realm(), 0, m_y.value_or(0));
  141. return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length));
  142. }
  143. JS::NonnullGCPtr<SVGAnimatedLength> SVGUseElement::width() const
  144. {
  145. // FIXME: Implement this properly.
  146. return SVGAnimatedLength::create(realm(), SVGLength::create(realm(), 0, 0), SVGLength::create(realm(), 0, 0));
  147. }
  148. JS::NonnullGCPtr<SVGAnimatedLength> SVGUseElement::height() const
  149. {
  150. // FIXME: Implement this properly.
  151. return SVGAnimatedLength::create(realm(), SVGLength::create(realm(), 0, 0), SVGLength::create(realm(), 0, 0));
  152. }
  153. // https://svgwg.org/svg2-draft/struct.html#TermInstanceRoot
  154. JS::GCPtr<SVGElement> SVGUseElement::instance_root() const
  155. {
  156. return const_cast<DOM::ShadowRoot&>(*shadow_root()).first_child_of_type<SVGElement>();
  157. }
  158. JS::GCPtr<SVGElement> SVGUseElement::animated_instance_root() const
  159. {
  160. return instance_root();
  161. }
  162. JS::GCPtr<Layout::Node> SVGUseElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
  163. {
  164. return heap().allocate_without_realm<Layout::SVGGraphicsBox>(document(), *this, move(style));
  165. }
  166. }