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