script/dom/svg/
svgsvgelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    // The XML source of subtree rooted at this SVG element, serialized into
34    // a base64 encoded `data:` url. This is cached to avoid recomputation
35    // on each layout and must be invalidated when the subtree changes.
36    #[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            // TODO: This should accept lengths in arbitrary units instead of assuming px.
234            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}