/src/serenity/Userland/Libraries/LibWeb/SVG/SVGElement.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> |
3 | | * Copyright (c) 2023, Preston Taylor <95388976+PrestonLTaylor@users.noreply.github.com> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <LibWeb/Bindings/ExceptionOrUtils.h> |
9 | | #include <LibWeb/Bindings/Intrinsics.h> |
10 | | #include <LibWeb/Bindings/SVGElementPrototype.h> |
11 | | #include <LibWeb/CSS/StyleProperties.h> |
12 | | #include <LibWeb/DOM/Document.h> |
13 | | #include <LibWeb/DOM/ShadowRoot.h> |
14 | | #include <LibWeb/HTML/DOMStringMap.h> |
15 | | #include <LibWeb/SVG/SVGElement.h> |
16 | | #include <LibWeb/SVG/SVGSVGElement.h> |
17 | | #include <LibWeb/SVG/SVGUseElement.h> |
18 | | |
19 | | namespace Web::SVG { |
20 | | |
21 | | SVGElement::SVGElement(DOM::Document& document, DOM::QualifiedName qualified_name) |
22 | 0 | : Element(document, move(qualified_name)) |
23 | 0 | { |
24 | 0 | } |
25 | | |
26 | | void SVGElement::initialize(JS::Realm& realm) |
27 | 0 | { |
28 | 0 | Base::initialize(realm); |
29 | 0 | WEB_SET_PROTOTYPE_FOR_INTERFACE(SVGElement); |
30 | 0 | } |
31 | | |
32 | | void SVGElement::visit_edges(Cell::Visitor& visitor) |
33 | 0 | { |
34 | 0 | Base::visit_edges(visitor); |
35 | 0 | visitor.visit(m_dataset); |
36 | 0 | visitor.visit(m_class_name_animated_string); |
37 | 0 | } |
38 | | |
39 | | JS::NonnullGCPtr<HTML::DOMStringMap> SVGElement::dataset() |
40 | 0 | { |
41 | 0 | if (!m_dataset) |
42 | 0 | m_dataset = HTML::DOMStringMap::create(*this); |
43 | 0 | return *m_dataset; |
44 | 0 | } |
45 | | |
46 | | void SVGElement::attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value) |
47 | 0 | { |
48 | 0 | Base::attribute_changed(name, old_value, value); |
49 | |
|
50 | 0 | update_use_elements_that_reference_this(); |
51 | 0 | } |
52 | | |
53 | | void SVGElement::inserted() |
54 | 0 | { |
55 | 0 | Base::inserted(); |
56 | |
|
57 | 0 | update_use_elements_that_reference_this(); |
58 | 0 | } |
59 | | |
60 | | void SVGElement::children_changed() |
61 | 0 | { |
62 | 0 | Base::children_changed(); |
63 | |
|
64 | 0 | update_use_elements_that_reference_this(); |
65 | 0 | } |
66 | | |
67 | | void SVGElement::update_use_elements_that_reference_this() |
68 | 0 | { |
69 | 0 | if (is<SVGUseElement>(this) |
70 | | // If this element is in a shadow root, it already represents a clone and is not itself referenced. |
71 | 0 | || is<DOM::ShadowRoot>(this->root()) |
72 | | // If this does not have an id it cannot be referenced, no point in searching the entire DOM tree. |
73 | 0 | || !id().has_value() |
74 | | // An unconnected node cannot have valid references. |
75 | | // This also prevents searches for elements that are in the process of being constructed - as clones. |
76 | 0 | || !this->is_connected() |
77 | | // Each use element already listens for the completely_loaded event and then clones its referece, |
78 | | // we do not have to also clone it in the process of initial DOM building. |
79 | 0 | || !document().is_completely_loaded()) { |
80 | |
|
81 | 0 | return; |
82 | 0 | } |
83 | | |
84 | 0 | document().for_each_in_subtree_of_type<SVGUseElement>([this](SVGUseElement& use_element) { |
85 | 0 | use_element.svg_element_changed(*this); |
86 | 0 | return TraversalDecision::Continue; |
87 | 0 | }); |
88 | 0 | } |
89 | | |
90 | | void SVGElement::removed_from(Node* parent) |
91 | 0 | { |
92 | 0 | Base::removed_from(parent); |
93 | |
|
94 | 0 | remove_from_use_element_that_reference_this(); |
95 | 0 | } |
96 | | |
97 | | void SVGElement::remove_from_use_element_that_reference_this() |
98 | 0 | { |
99 | 0 | if (is<SVGUseElement>(this) || !id().has_value()) { |
100 | 0 | return; |
101 | 0 | } |
102 | | |
103 | 0 | document().for_each_in_subtree_of_type<SVGUseElement>([this](SVGUseElement& use_element) { |
104 | 0 | use_element.svg_element_removed(*this); |
105 | 0 | return TraversalDecision::Continue; |
106 | 0 | }); |
107 | 0 | } |
108 | | |
109 | | void SVGElement::focus() |
110 | 0 | { |
111 | 0 | dbgln("(STUBBED) SVGElement::focus()"); |
112 | 0 | } |
113 | | |
114 | | void SVGElement::blur() |
115 | 0 | { |
116 | 0 | dbgln("(STUBBED) SVGElement::blur()"); |
117 | 0 | } |
118 | | |
119 | | // https://svgwg.org/svg2-draft/types.html#__svg__SVGElement__classNames |
120 | | JS::NonnullGCPtr<SVGAnimatedString> SVGElement::class_name() |
121 | 0 | { |
122 | | // The className IDL attribute reflects the ‘class’ attribute. |
123 | 0 | if (!m_class_name_animated_string) |
124 | 0 | m_class_name_animated_string = SVGAnimatedString::create(realm(), *this, AttributeNames::class_); |
125 | |
|
126 | 0 | return *m_class_name_animated_string; |
127 | 0 | } |
128 | | |
129 | | // https://svgwg.org/svg2-draft/types.html#__svg__SVGElement__ownerSVGElement |
130 | | JS::GCPtr<SVGSVGElement> SVGElement::owner_svg_element() |
131 | 0 | { |
132 | | // The ownerSVGElement IDL attribute represents the nearest ancestor ‘svg’ element. |
133 | | // On getting ownerSVGElement, the nearest ancestor ‘svg’ element is returned; |
134 | | // if the current element is the outermost svg element, then null is returned. |
135 | 0 | return shadow_including_first_ancestor_of_type<SVGSVGElement>(); |
136 | 0 | } |
137 | | |
138 | | JS::NonnullGCPtr<SVGAnimatedLength> SVGElement::svg_animated_length_for_property(CSS::PropertyID property) const |
139 | 0 | { |
140 | | // FIXME: Create a proper animated value when animations are supported. |
141 | 0 | auto make_length = [&] { |
142 | 0 | if (auto const* style = computed_css_values(); style) { |
143 | 0 | if (auto length = style->length_percentage(property); length.has_value()) |
144 | 0 | return SVGLength::from_length_percentage(realm(), *length); |
145 | 0 | } |
146 | 0 | return SVGLength::create(realm(), 0, 0.0f); |
147 | 0 | }; |
148 | 0 | return SVGAnimatedLength::create(realm(), make_length(), make_length()); |
149 | 0 | } |
150 | | |
151 | | } |