script/dom/
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::inheritance::Castable;
19use crate::dom::bindings::root::{DomRoot, LayoutDom};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::document::Document;
22use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
23use crate::dom::node::{Node, NodeDamage};
24use crate::dom::svggraphicselement::SVGGraphicsElement;
25use crate::dom::virtualmethods::VirtualMethods;
26use crate::script_runtime::CanGc;
27
28#[dom_struct]
29pub(crate) struct SVGSVGElement {
30    svggraphicselement: SVGGraphicsElement,
31    // The XML source of subtree rooted at this SVG element, serialized into
32    // a base64 encoded `data:` url. This is cached to avoid recomputation
33    // on each layout and must be invalidated when the subtree changes.
34    #[no_trace]
35    cached_serialized_data_url: RefCell<Option<Result<ServoUrl, ()>>>,
36}
37
38impl SVGSVGElement {
39    fn new_inherited(
40        local_name: LocalName,
41        prefix: Option<Prefix>,
42        document: &Document,
43    ) -> SVGSVGElement {
44        SVGSVGElement {
45            svggraphicselement: SVGGraphicsElement::new_inherited(local_name, prefix, document),
46            cached_serialized_data_url: Default::default(),
47        }
48    }
49
50    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
51    pub(crate) fn new(
52        local_name: LocalName,
53        prefix: Option<Prefix>,
54        document: &Document,
55        proto: Option<HandleObject>,
56        can_gc: CanGc,
57    ) -> DomRoot<SVGSVGElement> {
58        Node::reflect_node_with_proto(
59            Box::new(SVGSVGElement::new_inherited(local_name, prefix, document)),
60            document,
61            proto,
62            can_gc,
63        )
64    }
65
66    pub(crate) fn serialize_and_cache_subtree(&self) {
67        let Ok(xml_source) = self
68            .upcast::<Node>()
69            .xml_serialize(TraversalScope::IncludeNode)
70        else {
71            *self.cached_serialized_data_url.borrow_mut() = Some(Err(()));
72            return;
73        };
74
75        let xml_source: String = xml_source.into();
76        let base64_encoded_source = base64::engine::general_purpose::STANDARD.encode(xml_source);
77        let data_url = format!("data:image/svg+xml;base64,{}", base64_encoded_source);
78        match ServoUrl::parse(&data_url) {
79            Ok(url) => *self.cached_serialized_data_url.borrow_mut() = Some(Ok(url)),
80            Err(error) => error!("Unable to parse serialized SVG data url: {error}"),
81        };
82    }
83
84    fn invalidate_cached_serialized_subtree(&self) {
85        *self.cached_serialized_data_url.borrow_mut() = None;
86        self.upcast::<Node>().dirty(NodeDamage::Other);
87    }
88}
89
90pub(crate) trait LayoutSVGSVGElementHelpers {
91    fn data(self) -> SVGElementData;
92}
93
94fn ratio_from_view_box(view_box: &AttrValue) -> Option<f32> {
95    let mut iter = view_box.chars();
96    let _min_x = parse_integer(&mut iter).ok()?;
97    let _min_y = parse_integer(&mut iter).ok()?;
98    let width = parse_unsigned_integer(&mut iter).ok()?;
99    if width == 0 {
100        return None;
101    }
102    let height = parse_unsigned_integer(&mut iter).ok()?;
103    if height == 0 {
104        return None;
105    }
106    let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
107    iter.next().is_none().then(|| width as f32 / height as f32)
108}
109
110impl LayoutSVGSVGElementHelpers for LayoutDom<'_, SVGSVGElement> {
111    fn data(self) -> SVGElementData {
112        let element = self.upcast::<Element>();
113        let get_size = |attr| {
114            element
115                .get_attr_for_layout(&ns!(), &attr)
116                .map(|val| val.as_int())
117                .filter(|val| *val >= 0)
118        };
119        let width = get_size(local_name!("width"));
120        let height = get_size(local_name!("height"));
121        let ratio = match (width, height) {
122            (Some(width), Some(height)) if width != 0 && height != 0 => {
123                Some(width as f32 / height as f32)
124            },
125            _ => element
126                .get_attr_for_layout(&ns!(), &local_name!("viewBox"))
127                .and_then(ratio_from_view_box),
128        };
129        SVGElementData {
130            source: self
131                .unsafe_get()
132                .cached_serialized_data_url
133                .borrow()
134                .clone(),
135            width,
136            height,
137            ratio,
138        }
139    }
140}
141
142impl VirtualMethods for SVGSVGElement {
143    fn super_type(&self) -> Option<&dyn VirtualMethods> {
144        Some(self.upcast::<SVGGraphicsElement>() as &dyn VirtualMethods)
145    }
146
147    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
148        self.super_type()
149            .unwrap()
150            .attribute_mutated(attr, mutation, can_gc);
151
152        self.invalidate_cached_serialized_subtree();
153    }
154
155    fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
156        match *name {
157            // TODO: This should accept lengths in arbitrary units instead of assuming px.
158            local_name!("width") | local_name!("height") => AttrValue::from_i32(value.into(), -1),
159            _ => self
160                .super_type()
161                .unwrap()
162                .parse_plain_attribute(name, value),
163        }
164    }
165
166    fn children_changed(&self, mutation: &super::node::ChildrenMutation) {
167        if let Some(super_type) = self.super_type() {
168            super_type.children_changed(mutation);
169        }
170
171        self.invalidate_cached_serialized_subtree();
172    }
173}