SVGUseElement.cpp 6.7 KB

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