1use atomic_refcell::AtomicRefCell;
6use base64::Engine as _;
7use cssparser::{Parser, ParserInput};
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name, ns};
10use js::context::{JSContext, NoGC};
11use js::rust::HandleObject;
12use layout_api::SVGElementData;
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: AtomicRefCell<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, no_gc: &NoGC) {
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(no_gc, NodeDamage::Other);
182 }
183}
184
185impl<'dom> LayoutDom<'dom, SVGSVGElement> {
186 pub(crate) fn data(self) -> SVGElementData<'dom> {
187 let svg_id = self.unsafe_get().uuid;
188 let element = self.upcast::<Element>();
189 let width = element.get_attr_for_layout(&ns!(), &local_name!("width"));
190 let height = element.get_attr_for_layout(&ns!(), &local_name!("height"));
191 let view_box = element.get_attr_for_layout(&ns!(), &local_name!("viewBox"));
192 SVGElementData {
193 source: self
194 .unsafe_get()
195 .cached_serialized_data_url
196 .borrow()
197 .clone(),
198 width,
199 height,
200 view_box,
201 svg_id,
202 }
203 }
204}
205
206impl VirtualMethods for SVGSVGElement {
207 fn super_type(&self) -> Option<&dyn VirtualMethods> {
208 Some(self.upcast::<SVGGraphicsElement>() as &dyn VirtualMethods)
209 }
210
211 fn attribute_mutated(
212 &self,
213 cx: &mut js::context::JSContext,
214 attr: AttrRef<'_>,
215 mutation: AttributeMutation,
216 ) {
217 self.super_type()
218 .unwrap()
219 .attribute_mutated(cx, attr, mutation);
220
221 self.invalidate_cached_serialized_subtree_and_rasterization_result(cx.no_gc());
222 }
223
224 fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
225 match attr.local_name() {
226 &local_name!("width") | &local_name!("height") => true,
227 _ => self
228 .super_type()
229 .unwrap()
230 .attribute_affects_presentational_hints(attr),
231 }
232 }
233
234 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
235 match *name {
236 local_name!("width") | local_name!("height") => {
237 let value = &value.str();
238 let parser_input = &mut ParserInput::new(value);
239 let parser = &mut Parser::new(parser_input);
240 let doc = self.owner_document();
241 let url = doc.url().into_url().into();
242 let context = ParserContext::new(
243 Origin::Author,
244 &url,
245 None,
246 ParsingMode::ALLOW_UNITLESS_LENGTH,
247 doc.quirks_mode(),
248 Default::default(),
249 None,
250 None,
251 Default::default(),
252 );
253 let val = LengthPercentage::parse_quirky(
254 &context,
255 parser,
256 style::values::specified::AllowQuirks::Always,
257 );
258 AttrValue::LengthPercentage(value.to_string(), val.ok())
259 },
260 _ => self
261 .super_type()
262 .unwrap()
263 .parse_plain_attribute(name, value),
264 }
265 }
266
267 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
268 if let Some(super_type) = self.super_type() {
269 super_type.children_changed(cx, mutation);
270 }
271
272 self.invalidate_cached_serialized_subtree_and_rasterization_result(cx.no_gc());
273 }
274
275 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext<'_>) {
276 if let Some(s) = self.super_type() {
277 s.unbind_from_tree(cx, context);
278 }
279
280 self.invalidate_cached_serialized_subtree_and_rasterization_result(cx.no_gc());
281 }
282}