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