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::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    // 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(),
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        // Don't use this node if it doesn't have an `<svg>` ancestor.
141        if !referenced_node
142            .inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::No)
143            .any(|ancestor| ancestor.is::<SVGSVGElement>())
144        {
145            return;
146        };
147
148        // Don't use this node if it already exists within the same `<svg>` element.
149        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) {
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(NodeDamage::Other);
182    }
183}
184
185impl<'dom> LayoutDom<'dom, SVGSVGElement> {
186    #[expect(unsafe_code)]
187    pub(crate) fn data(self) -> SVGElementData<'dom> {
188        let svg_id = self.unsafe_get().uuid;
189        let element = self.upcast::<Element>();
190        let width = element.get_attr_for_layout(&ns!(), &local_name!("width"));
191        let height = element.get_attr_for_layout(&ns!(), &local_name!("height"));
192        let view_box = element.get_attr_for_layout(&ns!(), &local_name!("viewBox"));
193        SVGElementData {
194            source: unsafe {
195                self.unsafe_get()
196                    .cached_serialized_data_url
197                    .borrow_for_layout()
198                    .clone()
199            },
200            width,
201            height,
202            view_box,
203            svg_id,
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(
214        &self,
215        cx: &mut js::context::JSContext,
216        attr: AttrRef<'_>,
217        mutation: AttributeMutation,
218    ) {
219        self.super_type()
220            .unwrap()
221            .attribute_mutated(cx, attr, mutation);
222
223        self.invalidate_cached_serialized_subtree_and_rasterization_result();
224    }
225
226    fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
227        match attr.local_name() {
228            &local_name!("width") | &local_name!("height") => true,
229            _ => self
230                .super_type()
231                .unwrap()
232                .attribute_affects_presentational_hints(attr),
233        }
234    }
235
236    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
237        match *name {
238            local_name!("width") | local_name!("height") => {
239                let value = &value.str();
240                let parser_input = &mut ParserInput::new(value);
241                let parser = &mut Parser::new(parser_input);
242                let doc = self.owner_document();
243                let url = doc.url().into_url().into();
244                let context = ParserContext::new(
245                    Origin::Author,
246                    &url,
247                    None,
248                    ParsingMode::ALLOW_UNITLESS_LENGTH,
249                    doc.quirks_mode(),
250                    /* namespaces = */ Default::default(),
251                    None,
252                    None,
253                    /* attr_taint = */ Default::default(),
254                );
255                let val = LengthPercentage::parse_quirky(
256                    &context,
257                    parser,
258                    style::values::specified::AllowQuirks::Always,
259                );
260                AttrValue::LengthPercentage(value.to_string(), val.ok())
261            },
262            _ => self
263                .super_type()
264                .unwrap()
265                .parse_plain_attribute(name, value),
266        }
267    }
268
269    fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
270        if let Some(super_type) = self.super_type() {
271            super_type.children_changed(cx, mutation);
272        }
273
274        self.invalidate_cached_serialized_subtree_and_rasterization_result();
275    }
276
277    fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext<'_>) {
278        if let Some(s) = self.super_type() {
279            s.unbind_from_tree(cx, context);
280        }
281
282        self.invalidate_cached_serialized_subtree_and_rasterization_result();
283    }
284}