SVGUseElement.cpp 6.5 KB

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