script/dom/svg/
svgsvgelement.rs1use std::cell::RefCell;
6
7use base64::Engine as _;
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name, ns};
10use js::rust::HandleObject;
11use layout_api::SVGElementData;
12use servo_url::ServoUrl;
13use style::attr::{AttrValue, parse_integer, parse_unsigned_integer};
14use style::str::char_is_whitespace;
15use xml5ever::serialize::TraversalScope;
16
17use crate::dom::attr::Attr;
18use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
19use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
20use crate::dom::bindings::inheritance::Castable;
21use crate::dom::bindings::root::{DomRoot, LayoutDom};
22use crate::dom::bindings::str::DOMString;
23use crate::dom::document::Document;
24use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
25use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, ShadowIncluding};
26use crate::dom::svg::svggraphicselement::SVGGraphicsElement;
27use crate::dom::virtualmethods::VirtualMethods;
28use crate::script_runtime::CanGc;
29
30#[dom_struct]
31pub(crate) struct SVGSVGElement {
32 svggraphicselement: SVGGraphicsElement,
33 #[no_trace]
37 cached_serialized_data_url: RefCell<Option<Result<ServoUrl, ()>>>,
38}
39
40impl SVGSVGElement {
41 fn new_inherited(
42 local_name: LocalName,
43 prefix: Option<Prefix>,
44 document: &Document,
45 ) -> SVGSVGElement {
46 SVGSVGElement {
47 svggraphicselement: SVGGraphicsElement::new_inherited(local_name, prefix, document),
48 cached_serialized_data_url: Default::default(),
49 }
50 }
51
52 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
53 pub(crate) fn new(
54 local_name: LocalName,
55 prefix: Option<Prefix>,
56 document: &Document,
57 proto: Option<HandleObject>,
58 can_gc: CanGc,
59 ) -> DomRoot<SVGSVGElement> {
60 Node::reflect_node_with_proto(
61 Box::new(SVGSVGElement::new_inherited(local_name, prefix, document)),
62 document,
63 proto,
64 can_gc,
65 )
66 }
67
68 pub(crate) fn serialize_and_cache_subtree(&self) {
69 let can_gc = CanGc::note();
70 let cloned_nodes = self.process_use_elements(can_gc);
71
72 let serialize_result = self
73 .upcast::<Node>()
74 .xml_serialize(TraversalScope::IncludeNode);
75
76 self.cleanup_cloned_nodes(&cloned_nodes, can_gc);
77
78 let Ok(xml_source) = serialize_result else {
79 *self.cached_serialized_data_url.borrow_mut() = Some(Err(()));
80 return;
81 };
82
83 let xml_source: String = xml_source.into();
84 let base64_encoded_source = base64::engine::general_purpose::STANDARD.encode(xml_source);
85 let data_url = format!("data:image/svg+xml;base64,{}", base64_encoded_source);
86 match ServoUrl::parse(&data_url) {
87 Ok(url) => *self.cached_serialized_data_url.borrow_mut() = Some(Ok(url)),
88 Err(error) => error!("Unable to parse serialized SVG data url: {error}"),
89 };
90 }
91
92 fn process_use_elements(&self, can_gc: CanGc) -> Vec<DomRoot<Node>> {
93 let mut cloned_nodes = Vec::new();
94 let root_node = self.upcast::<Node>();
95
96 for node in root_node.traverse_preorder(ShadowIncluding::No) {
97 if let Some(element) = node.downcast::<Element>() {
98 if element.local_name() == &local_name!("use") {
99 if let Some(cloned) = self.process_single_use_element(element, can_gc) {
100 cloned_nodes.push(cloned);
101 }
102 }
103 }
104 }
105
106 cloned_nodes
107 }
108
109 fn process_single_use_element(
110 &self,
111 use_element: &Element,
112 can_gc: CanGc,
113 ) -> Option<DomRoot<Node>> {
114 let href = use_element.get_string_attribute(&local_name!("href"));
115 let href_view = href.str();
116 let id_str = href_view.strip_prefix("#")?;
117 let id = DOMString::from(id_str);
118 let document = self.upcast::<Node>().owner_doc();
119 let referenced_element = document.GetElementById(id)?;
120 let referenced_node = referenced_element.upcast::<Node>();
121 let has_svg_ancestor = referenced_node
122 .inclusive_ancestors(ShadowIncluding::No)
123 .any(|ancestor| ancestor.is::<SVGSVGElement>());
124 if !has_svg_ancestor {
125 return None;
126 }
127 let cloned_node = Node::clone(
128 referenced_node,
129 None,
130 CloneChildrenFlag::CloneChildren,
131 can_gc,
132 );
133 let root_node = self.upcast::<Node>();
134 let _ = root_node.AppendChild(&cloned_node, can_gc);
135
136 Some(cloned_node)
137 }
138
139 fn cleanup_cloned_nodes(&self, cloned_nodes: &[DomRoot<Node>], can_gc: CanGc) {
140 if cloned_nodes.is_empty() {
141 return;
142 }
143 let root_node = self.upcast::<Node>();
144
145 for cloned_node in cloned_nodes {
146 let _ = root_node.RemoveChild(cloned_node, can_gc);
147 }
148 }
149
150 fn invalidate_cached_serialized_subtree(&self) {
151 *self.cached_serialized_data_url.borrow_mut() = None;
152 self.upcast::<Node>().dirty(NodeDamage::Other);
153 }
154}
155
156pub(crate) trait LayoutSVGSVGElementHelpers {
157 fn data(self) -> SVGElementData;
158}
159
160fn ratio_from_view_box(view_box: &AttrValue) -> Option<f32> {
161 let mut iter = view_box.chars();
162 let _min_x = parse_integer(&mut iter).ok()?;
163 let _min_y = parse_integer(&mut iter).ok()?;
164 let width = parse_unsigned_integer(&mut iter).ok()?;
165 if width == 0 {
166 return None;
167 }
168 let height = parse_unsigned_integer(&mut iter).ok()?;
169 if height == 0 {
170 return None;
171 }
172 let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
173 iter.next().is_none().then(|| width as f32 / height as f32)
174}
175
176impl LayoutSVGSVGElementHelpers for LayoutDom<'_, SVGSVGElement> {
177 fn data(self) -> SVGElementData {
178 let element = self.upcast::<Element>();
179 let get_size = |attr| {
180 element
181 .get_attr_for_layout(&ns!(), &attr)
182 .map(|val| val.as_int())
183 .filter(|val| *val >= 0)
184 };
185 let width = get_size(local_name!("width"));
186 let height = get_size(local_name!("height"));
187 let ratio = match (width, height) {
188 (Some(width), Some(height)) if width != 0 && height != 0 => {
189 Some(width as f32 / height as f32)
190 },
191 _ => element
192 .get_attr_for_layout(&ns!(), &local_name!("viewBox"))
193 .and_then(ratio_from_view_box),
194 };
195 SVGElementData {
196 source: self
197 .unsafe_get()
198 .cached_serialized_data_url
199 .borrow()
200 .clone(),
201 width,
202 height,
203 ratio,
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(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
214 self.super_type()
215 .unwrap()
216 .attribute_mutated(attr, mutation, can_gc);
217
218 self.invalidate_cached_serialized_subtree();
219 }
220
221 fn attribute_affects_presentational_hints(&self, attr: &Attr) -> bool {
222 match attr.local_name() {
223 &local_name!("width") | &local_name!("height") => true,
224 _ => self
225 .super_type()
226 .unwrap()
227 .attribute_affects_presentational_hints(attr),
228 }
229 }
230
231 fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
232 match *name {
233 local_name!("width") | local_name!("height") => AttrValue::from_i32(value.into(), -1),
235 _ => self
236 .super_type()
237 .unwrap()
238 .parse_plain_attribute(name, value),
239 }
240 }
241
242 fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
243 if let Some(super_type) = self.super_type() {
244 super_type.children_changed(mutation, can_gc);
245 }
246
247 self.invalidate_cached_serialized_subtree();
248 }
249}