1use base64::Engine as _;
6use cssparser::{Parser, ParserInput};
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, local_name, ns};
9use js::context::JSContext;
10use js::rust::HandleObject;
11use layout_api::SVGElementData;
12use servo_url::ServoUrl;
13use style::attr::AttrValue;
14use style::parser::ParserContext;
15use style::stylesheets::Origin;
16use style::values::specified::LengthPercentage;
17use style_traits::ParsingMode;
18use uuid::Uuid;
19use xml5ever::serialize::TraversalScope;
20
21use crate::dom::attr::Attr;
22use crate::dom::bindings::cell::DomRefCell;
23use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
24use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::root::{DomRoot, LayoutDom};
27use crate::dom::bindings::str::DOMString;
28use crate::dom::document::Document;
29use crate::dom::element::{AttributeMutation, Element};
30use crate::dom::node::{
31 ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding,
32 UnbindContext,
33};
34use crate::dom::svg::svggraphicselement::SVGGraphicsElement;
35use crate::dom::virtualmethods::VirtualMethods;
36
37#[dom_struct]
38pub(crate) struct SVGSVGElement {
39 svggraphicselement: SVGGraphicsElement,
40 uuid: String,
41 #[no_trace]
45 cached_serialized_data_url: DomRefCell<Option<Result<ServoUrl, ()>>>,
46}
47
48impl SVGSVGElement {
49 fn new_inherited(
50 local_name: LocalName,
51 prefix: Option<Prefix>,
52 document: &Document,
53 ) -> SVGSVGElement {
54 SVGSVGElement {
55 svggraphicselement: SVGGraphicsElement::new_inherited(local_name, prefix, document),
56 uuid: Uuid::new_v4().to_string(),
57 cached_serialized_data_url: Default::default(),
58 }
59 }
60
61 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
62 pub(crate) fn new(
63 cx: &mut js::context::JSContext,
64 local_name: LocalName,
65 prefix: Option<Prefix>,
66 document: &Document,
67 proto: Option<HandleObject>,
68 ) -> DomRoot<SVGSVGElement> {
69 Node::reflect_node_with_proto(
70 cx,
71 Box::new(SVGSVGElement::new_inherited(local_name, prefix, document)),
72 document,
73 proto,
74 )
75 }
76
77 #[expect(unsafe_code)]
78 pub(crate) fn serialize_and_cache_subtree(&self) {
79 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
81 let cx = &mut cx;
82 let cloned_nodes = self.process_use_elements(cx);
83
84 let serialize_result = self
85 .upcast::<Node>()
86 .xml_serialize(TraversalScope::IncludeNode);
87
88 self.cleanup_cloned_nodes(cx, &cloned_nodes);
89
90 let Ok(xml_source) = serialize_result else {
91 *self.cached_serialized_data_url.borrow_mut() = Some(Err(()));
92 return;
93 };
94
95 let xml_source: String = xml_source.into();
96 let base64_encoded_source = base64::engine::general_purpose::STANDARD.encode(xml_source);
97 let data_url = format!("data:image/svg+xml;base64,{}", base64_encoded_source);
98 match ServoUrl::parse(&data_url) {
99 Ok(url) => *self.cached_serialized_data_url.borrow_mut() = Some(Ok(url)),
100 Err(error) => error!("Unable to parse serialized SVG data url: {error}"),
101 };
102 }
103
104 fn process_use_elements(&self, cx: &mut JSContext) -> Vec<DomRoot<Node>> {
105 let mut cloned_nodes = Vec::new();
106 let root_node = self.upcast::<Node>();
107
108 for node in root_node.traverse_preorder(ShadowIncluding::No) {
109 if let Some(element) = node.downcast::<Element>() {
110 if element.local_name() == &local_name!("use") {
111 if let Some(cloned) = self.process_single_use_element(cx, element) {
112 cloned_nodes.push(cloned);
113 }
114 }
115 }
116 }
117
118 cloned_nodes
119 }
120
121 fn process_single_use_element(
122 &self,
123 cx: &mut JSContext,
124 use_element: &Element,
125 ) -> Option<DomRoot<Node>> {
126 let href = use_element.get_string_attribute(&local_name!("href"));
127 let href_view = href.str();
128 let id_str = href_view.strip_prefix("#")?;
129 let id = DOMString::from(id_str);
130 let document = self.upcast::<Node>().owner_doc();
131 let referenced_element = document.GetElementById(id)?;
132 let referenced_node = referenced_element.upcast::<Node>();
133 let has_svg_ancestor = referenced_node
134 .inclusive_ancestors(ShadowIncluding::No)
135 .any(|ancestor| ancestor.is::<SVGSVGElement>());
136 if !has_svg_ancestor {
137 return None;
138 }
139 let cloned_node = Node::clone(
140 cx,
141 referenced_node,
142 None,
143 CloneChildrenFlag::CloneChildren,
144 None,
145 );
146 let root_node = self.upcast::<Node>();
147 let _ = root_node.AppendChild(cx, &cloned_node);
148
149 Some(cloned_node)
150 }
151
152 fn cleanup_cloned_nodes(&self, cx: &mut JSContext, cloned_nodes: &[DomRoot<Node>]) {
153 if cloned_nodes.is_empty() {
154 return;
155 }
156 let root_node = self.upcast::<Node>();
157
158 for cloned_node in cloned_nodes {
159 let _ = root_node.RemoveChild(cx, cloned_node);
160 }
161 }
162
163 fn invalidate_cached_serialized_subtree(&self) {
164 *self.cached_serialized_data_url.borrow_mut() = None;
165 self.upcast::<Node>().dirty(NodeDamage::Other);
166 }
167}
168
169impl<'dom> LayoutDom<'dom, SVGSVGElement> {
170 #[expect(unsafe_code)]
171 pub(crate) fn data(self) -> SVGElementData<'dom> {
172 let svg_id = self.unsafe_get().uuid.clone();
173 let element = self.upcast::<Element>();
174 let width = element.get_attr_for_layout(&ns!(), &local_name!("width"));
175 let height = element.get_attr_for_layout(&ns!(), &local_name!("height"));
176 let view_box = element.get_attr_for_layout(&ns!(), &local_name!("viewBox"));
177 SVGElementData {
178 source: unsafe {
179 self.unsafe_get()
180 .cached_serialized_data_url
181 .borrow_for_layout()
182 .clone()
183 },
184 width,
185 height,
186 view_box,
187 svg_id,
188 }
189 }
190}
191
192impl VirtualMethods for SVGSVGElement {
193 fn super_type(&self) -> Option<&dyn VirtualMethods> {
194 Some(self.upcast::<SVGGraphicsElement>() as &dyn VirtualMethods)
195 }
196
197 fn attribute_mutated(
198 &self,
199 cx: &mut js::context::JSContext,
200 attr: &Attr,
201 mutation: AttributeMutation,
202 ) {
203 self.super_type()
204 .unwrap()
205 .attribute_mutated(cx, attr, mutation);
206
207 self.invalidate_cached_serialized_subtree();
208 }
209
210 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
211 match attr.local_name() {
212 &local_name!("width") | &local_name!("height") => true,
213 _ => self
214 .super_type()
215 .unwrap()
216 .attribute_affects_presentational_hints(attr),
217 }
218 }
219
220 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
221 match *name {
222 local_name!("width") | local_name!("height") => {
223 let value = &value.str();
224 let parser_input = &mut ParserInput::new(value);
225 let parser = &mut Parser::new(parser_input);
226 let doc = self.owner_document();
227 let url = doc.url().into_url().into();
228 let context = ParserContext::new(
229 Origin::Author,
230 &url,
231 None,
232 ParsingMode::ALLOW_UNITLESS_LENGTH,
233 doc.quirks_mode(),
234 Default::default(),
235 None,
236 None,
237 );
238 let val = LengthPercentage::parse_quirky(
239 &context,
240 parser,
241 style::values::specified::AllowQuirks::Always,
242 );
243 AttrValue::LengthPercentage(value.to_string(), val.ok())
244 },
245 _ => self
246 .super_type()
247 .unwrap()
248 .parse_plain_attribute(name, value),
249 }
250 }
251
252 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
253 if let Some(super_type) = self.super_type() {
254 super_type.children_changed(cx, mutation);
255 }
256
257 self.invalidate_cached_serialized_subtree();
258 }
259
260 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext<'_>) {
261 if let Some(s) = self.super_type() {
262 s.unbind_from_tree(cx, context);
263 }
264 let owner_window = self.owner_window();
265 self.owner_window()
266 .image_cache()
267 .evict_rasterized_image(&self.uuid);
268 let data_url = self.cached_serialized_data_url.borrow().clone();
269 if let Some(Ok(url)) = data_url {
270 owner_window.layout_mut().remove_cached_image(&url);
271 owner_window.image_cache().evict_completed_image(
272 &url,
273 owner_window.origin().immutable(),
274 &None,
275 );
276 }
277 self.invalidate_cached_serialized_subtree();
278 }
279}