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 atomic_refcell::AtomicRefCell;
6use base64::Engine as _;
7use cssparser::{Parser, ParserInput};
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name, ns};
10use js::context::{JSContext, NoGC};
11use js::rust::HandleObject;
12use layout_api::SVGElementData;
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: AtomicRefCell<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, no_gc: &NoGC) {
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(no_gc, NodeDamage::Other);
182    }
183}
184
185impl<'dom> LayoutDom<'dom, SVGSVGElement> {
186    pub(crate) fn data(self) -> SVGElementData<'dom> {
187        let svg_id = self.unsafe_get().uuid;
188        let element = self.upcast::<Element>();
189        let width = element.get_attr_for_layout(&ns!(), &local_name!("width"));
190        let height = element.get_attr_for_layout(&ns!(), &local_name!("height"));
191        let view_box = element.get_attr_for_layout(&ns!(), &local_name!("viewBox"));
192        SVGElementData {
193            source: self
194                .unsafe_get()
195                .cached_serialized_data_url
196                .borrow()
197                .clone(),
198            width,
199            height,
200            view_box,
201            svg_id,
202        }
203    }
204}
205
206impl VirtualMethods for SVGSVGElement {
207    fn super_type(&self) -> Option<&dyn VirtualMethods> {
208        Some(self.upcast::<SVGGraphicsElement>() as &dyn VirtualMethods)
209    }
210
211    fn attribute_mutated(
212        &self,
213        cx: &mut js::context::JSContext,
214        attr: AttrRef<'_>,
215        mutation: AttributeMutation,
216    ) {
217        self.super_type()
218            .unwrap()
219            .attribute_mutated(cx, attr, mutation);
220
221        self.invalidate_cached_serialized_subtree_and_rasterization_result(cx.no_gc());
222    }
223
224    fn attribute_affects_presentational_hints(&self, attr: AttrRef<'_>) -> bool {
225        match attr.local_name() {
226            &local_name!("width") | &local_name!("height") => true,
227            _ => self
228                .super_type()
229                .unwrap()
230                .attribute_affects_presentational_hints(attr),
231        }
232    }
233
234    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
235        match *name {
236            local_name!("width") | local_name!("height") => {
237                let value = &value.str();
238                let parser_input = &mut ParserInput::new(value);
239                let parser = &mut Parser::new(parser_input);
240                let doc = self.owner_document();
241                let url = doc.url().into_url().into();
242                let context = ParserContext::new(
243                    Origin::Author,
244                    &url,
245                    None,
246                    ParsingMode::ALLOW_UNITLESS_LENGTH,
247                    doc.quirks_mode(),
248                    /* namespaces = */ Default::default(),
249                    None,
250                    None,
251                    /* attr_taint = */ Default::default(),
252                );
253                let val = LengthPercentage::parse_quirky(
254                    &context,
255                    parser,
256                    style::values::specified::AllowQuirks::Always,
257                );
258                AttrValue::LengthPercentage(value.to_string(), val.ok())
259            },
260            _ => self
261                .super_type()
262                .unwrap()
263                .parse_plain_attribute(name, value),
264        }
265    }
266
267    fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
268        if let Some(super_type) = self.super_type() {
269            super_type.children_changed(cx, mutation);
270        }
271
272        self.invalidate_cached_serialized_subtree_and_rasterization_result(cx.no_gc());
273    }
274
275    fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext<'_>) {
276        if let Some(s) = self.super_type() {
277            s.unbind_from_tree(cx, context);
278        }
279
280        self.invalidate_cached_serialized_subtree_and_rasterization_result(cx.no_gc());
281    }
282}