SVGUseElement.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /*
  2. * Copyright (c) 2023, Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com>
  3. * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibWeb/Bindings/Intrinsics.h>
  8. #include <LibWeb/Bindings/SVGUseElementPrototype.h>
  9. #include <LibWeb/DOM/Document.h>
  10. #include <LibWeb/DOM/DocumentLoadEventDelayer.h>
  11. #include <LibWeb/DOM/ElementFactory.h>
  12. #include <LibWeb/DOM/Event.h>
  13. #include <LibWeb/DOM/ShadowRoot.h>
  14. #include <LibWeb/HTML/PotentialCORSRequest.h>
  15. #include <LibWeb/Layout/Box.h>
  16. #include <LibWeb/Layout/SVGGraphicsBox.h>
  17. #include <LibWeb/Namespace.h>
  18. #include <LibWeb/SVG/AttributeNames.h>
  19. #include <LibWeb/SVG/SVGDecodedImageData.h>
  20. #include <LibWeb/SVG/SVGSVGElement.h>
  21. #include <LibWeb/SVG/SVGUseElement.h>
  22. namespace Web::SVG {
  23. GC_DEFINE_ALLOCATOR(SVGUseElement);
  24. SVGUseElement::SVGUseElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  25. : SVGGraphicsElement(document, qualified_name)
  26. {
  27. }
  28. void SVGUseElement::initialize(JS::Realm& realm)
  29. {
  30. Base::initialize(realm);
  31. WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGUseElement);
  32. // The shadow tree is open (inspectable by script), but read-only.
  33. auto shadow_root = realm.create<DOM::ShadowRoot>(document(), *this, Bindings::ShadowRootMode::Open);
  34. // The user agent must create a use-element shadow tree whose host is the ‘use’ element itself
  35. set_shadow_root(shadow_root);
  36. m_document_observer = realm.create<DOM::DocumentObserver>(realm, document());
  37. m_document_observer->set_document_completely_loaded([this]() {
  38. clone_element_tree_as_our_shadow_tree(referenced_element());
  39. });
  40. }
  41. void SVGUseElement::visit_edges(Cell::Visitor& visitor)
  42. {
  43. Base::visit_edges(visitor);
  44. SVGURIReferenceMixin::visit_edges(visitor);
  45. visitor.visit(m_document_observer);
  46. visitor.visit(m_resource_request);
  47. }
  48. void SVGUseElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_)
  49. {
  50. Base::attribute_changed(name, old_value, value, namespace_);
  51. // https://svgwg.org/svg2-draft/struct.html#UseLayout
  52. if (name == SVG::AttributeNames::x) {
  53. m_x = AttributeParser::parse_coordinate(value.value_or(String {}));
  54. } else if (name == SVG::AttributeNames::y) {
  55. m_y = AttributeParser::parse_coordinate(value.value_or(String {}));
  56. } else if (name == SVG::AttributeNames::href || name == "xlink:href"_fly_string) {
  57. // 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.
  58. process_the_url(value);
  59. }
  60. }
  61. // https://www.w3.org/TR/SVG2/linking.html#processingURL
  62. void SVGUseElement::process_the_url(Optional<String> const& href)
  63. {
  64. // In all other cases, the URL is for a resource to be used in this SVG document. The user agent
  65. // must parse the URL to separate out the target fragment from the rest of the URL, and compare
  66. // it with the document base URL. If all parts other than the target fragment are equal, this is
  67. // a same-document URL reference, and processing the URL must continue as indicated in Identifying
  68. // the target element with the current document as the referenced document.
  69. m_href = document().url().complete_url(href.value_or(String {}));
  70. if (!m_href.is_valid())
  71. return;
  72. if (is_referrenced_element_same_document()) {
  73. clone_element_tree_as_our_shadow_tree(referenced_element());
  74. } else {
  75. fetch_the_document(m_href);
  76. }
  77. }
  78. bool SVGUseElement::is_referrenced_element_same_document() const
  79. {
  80. return m_href.equals(document().url(), URL::ExcludeFragment::Yes);
  81. }
  82. Gfx::AffineTransform SVGUseElement::element_transform() const
  83. {
  84. // The x and y properties define an additional transformation (translate(x,y), where x and y represent the computed value of the corresponding property)
  85. // to be applied to the ‘use’ element, after any transformations specified with other properties
  86. return Base::element_transform().translate(m_x.value_or(0), m_y.value_or(0));
  87. }
  88. void SVGUseElement::inserted()
  89. {
  90. Base::inserted();
  91. }
  92. void SVGUseElement::svg_element_changed(SVGElement& svg_element)
  93. {
  94. auto to_clone = referenced_element();
  95. if (!to_clone) {
  96. return;
  97. }
  98. // NOTE: We need to check the ancestor because attribute_changed of a child doesn't call children_changed on the parent(s)
  99. if (to_clone == &svg_element || to_clone->is_ancestor_of(svg_element)) {
  100. clone_element_tree_as_our_shadow_tree(to_clone);
  101. }
  102. }
  103. void SVGUseElement::svg_element_removed(SVGElement& svg_element)
  104. {
  105. if (!m_href.fragment().has_value() || !is_referrenced_element_same_document()) {
  106. return;
  107. }
  108. if (AK::StringUtils::matches(svg_element.get_attribute_value("id"_fly_string), m_href.fragment().value())) {
  109. shadow_root()->remove_all_children();
  110. }
  111. }
  112. // https://svgwg.org/svg2-draft/linking.html#processingURL-target
  113. GC::Ptr<DOM::Element> SVGUseElement::referenced_element()
  114. {
  115. if (!m_href.is_valid())
  116. return nullptr;
  117. if (!m_href.fragment().has_value())
  118. return nullptr;
  119. if (is_referrenced_element_same_document())
  120. return document().get_element_by_id(*m_href.fragment());
  121. if (!m_resource_request)
  122. return nullptr;
  123. auto data = m_resource_request->image_data();
  124. if (!data || !is<SVG::SVGDecodedImageData>(*data))
  125. return nullptr;
  126. return verify_cast<SVG::SVGDecodedImageData>(*data).svg_document().get_element_by_id(*m_href.fragment());
  127. }
  128. // https://svgwg.org/svg2-draft/linking.html#processingURL-fetch
  129. void SVGUseElement::fetch_the_document(URL::URL const& url)
  130. {
  131. m_load_event_delayer.emplace(document());
  132. m_resource_request = HTML::SharedResourceRequest::get_or_create(realm(), document().page(), url);
  133. m_resource_request->add_callbacks(
  134. [this] {
  135. clone_element_tree_as_our_shadow_tree(referenced_element());
  136. m_load_event_delayer.clear();
  137. },
  138. [this] {
  139. m_load_event_delayer.clear();
  140. });
  141. if (m_resource_request->needs_fetching()) {
  142. auto request = HTML::create_potential_CORS_request(vm(), url, Fetch::Infrastructure::Request::Destination::Image, HTML::CORSSettingAttribute::NoCORS);
  143. request->set_client(&document().relevant_settings_object());
  144. m_resource_request->fetch_resource(realm(), request);
  145. }
  146. }
  147. // https://svgwg.org/svg2-draft/struct.html#UseShadowTree
  148. void SVGUseElement::clone_element_tree_as_our_shadow_tree(Element* to_clone)
  149. {
  150. shadow_root()->remove_all_children();
  151. if (to_clone && is_valid_reference_element(*to_clone)) {
  152. // The ‘use’ element references another element, a copy of which is rendered in place of the ‘use’ in the document.
  153. auto cloned_reference_node = MUST(to_clone->clone_node(nullptr, true));
  154. shadow_root()->append_child(cloned_reference_node).release_value_but_fixme_should_propagate_errors();
  155. }
  156. }
  157. bool SVGUseElement::is_valid_reference_element(Element const& reference_element) const
  158. {
  159. // 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.
  160. // 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.
  161. return reference_element.is_svg_element() && !reference_element.is_ancestor_of(*this);
  162. }
  163. // https://www.w3.org/TR/SVG11/shapes.html#RectElementXAttribute
  164. GC::Ref<SVGAnimatedLength> SVGUseElement::x() const
  165. {
  166. // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
  167. // FIXME: Create a proper animated value when animations are supported.
  168. auto base_length = SVGLength::create(realm(), 0, m_x.value_or(0));
  169. auto anim_length = SVGLength::create(realm(), 0, m_x.value_or(0));
  170. return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length));
  171. }
  172. // https://www.w3.org/TR/SVG11/shapes.html#RectElementYAttribute
  173. GC::Ref<SVGAnimatedLength> SVGUseElement::y() const
  174. {
  175. // FIXME: Populate the unit type when it is parsed (0 here is "unknown").
  176. // FIXME: Create a proper animated value when animations are supported.
  177. auto base_length = SVGLength::create(realm(), 0, m_y.value_or(0));
  178. auto anim_length = SVGLength::create(realm(), 0, m_y.value_or(0));
  179. return SVGAnimatedLength::create(realm(), move(base_length), move(anim_length));
  180. }
  181. GC::Ref<SVGAnimatedLength> SVGUseElement::width() const
  182. {
  183. // FIXME: Implement this properly.
  184. return SVGAnimatedLength::create(realm(), SVGLength::create(realm(), 0, 0), SVGLength::create(realm(), 0, 0));
  185. }
  186. GC::Ref<SVGAnimatedLength> SVGUseElement::height() const
  187. {
  188. // FIXME: Implement this properly.
  189. return SVGAnimatedLength::create(realm(), SVGLength::create(realm(), 0, 0), SVGLength::create(realm(), 0, 0));
  190. }
  191. // https://svgwg.org/svg2-draft/struct.html#TermInstanceRoot
  192. GC::Ptr<SVGElement> SVGUseElement::instance_root() const
  193. {
  194. return const_cast<DOM::ShadowRoot&>(*shadow_root()).first_child_of_type<SVGElement>();
  195. }
  196. GC::Ptr<SVGElement> SVGUseElement::animated_instance_root() const
  197. {
  198. return instance_root();
  199. }
  200. GC::Ptr<Layout::Node> SVGUseElement::create_layout_node(CSS::StyleProperties style)
  201. {
  202. return heap().allocate<Layout::SVGGraphicsBox>(document(), *this, move(style));
  203. }
  204. }