Skip to main content

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