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