Skip to main content

style/url/
servo.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
5//! Servo-specific logic for CSS url() values.
6
7use crate::derives::*;
8use crate::parser::ParserContext;
9use crate::stylesheets::CorsMode;
10use crate::values::computed::{Context, ToComputedValue};
11use servo_arc::Arc;
12use std::fmt::{self, Write};
13use std::ops::Deref;
14use style_traits::{CssWriter, ToCss};
15use to_shmem::{SharedMemoryBuilder, ToShmem};
16use url::Url;
17
18/// A CSS url() value for servo.
19///
20/// Servo eagerly resolves SpecifiedUrls, which it can then take advantage of
21/// when computing values. In contrast, Gecko uses a different URL backend, so
22/// eagerly resolving with rust-url would be duplicated work.
23///
24/// However, this approach is still not necessarily optimal: See
25/// <https://bugzilla.mozilla.org/show_bug.cgi?id=1347435#c6>
26#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, SpecifiedValueInfo)]
27#[css(function = "url")]
28#[repr(C)]
29pub struct CssUrl(#[ignore_malloc_size_of = "Arc"] pub Arc<CssUrlData>);
30
31/// Data shared between CssUrls.
32///
33#[derive(Debug, Deserialize, MallocSizeOf, Serialize, SpecifiedValueInfo)]
34#[repr(C)]
35pub struct CssUrlData {
36    /// The original URI. This might be optional since we may insert computed
37    /// values of images into the cascade directly, and we don't bother to
38    /// convert their serialization.
39    ///
40    /// Refcounted since cloning this should be cheap and data: uris can be
41    /// really large.
42    #[ignore_malloc_size_of = "Arc"]
43    original: Option<Arc<String>>,
44
45    /// The resolved value for the url, if valid.
46    #[ignore_malloc_size_of = "Arc"]
47    resolved: Option<Arc<Url>>,
48}
49
50impl ToShmem for CssUrl {
51    fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
52        unimplemented!(
53            "If servo wants to share stylesheets across processes, ToShmem for Url must be implemented"
54        );
55    }
56}
57
58impl Deref for CssUrl {
59    type Target = CssUrlData;
60    fn deref(&self) -> &Self::Target {
61        &self.0
62    }
63}
64
65impl CssUrl {
66    /// Try to parse a URL from a string value that is a valid CSS token for a
67    /// URL.
68    ///
69    /// FIXME(emilio): Should honor CorsMode.
70    pub(super) fn new_from_string(
71        url: String,
72        context: &ParserContext,
73        _cors_mode: CorsMode,
74    ) -> Self {
75        let serialization = Arc::new(url);
76        // Per https://drafts.csswg.org/css-values-4/#url-empty
77        // If the original url is empty, then the resolved url is considered invalid.
78        let resolved = (!serialization.is_empty())
79            .then(|| context.url_data.0.join(&serialization))
80            .and_then(Result::ok)
81            .map(Arc::new);
82        CssUrl(Arc::new(CssUrlData {
83            original: Some(serialization),
84            resolved: resolved,
85        }))
86    }
87
88    /// Returns true if the URL is definitely invalid. For Servo URLs, we can
89    /// use its |resolved| status.
90    pub fn is_invalid(&self) -> bool {
91        self.resolved.is_none()
92    }
93
94    /// Returns true if this URL looks like a fragment.
95    /// See https://drafts.csswg.org/css-values/#local-urls
96    ///
97    /// Since Servo currently stores resolved URLs, this is hard to implement. We
98    /// either need to change servo to lazily resolve (like Gecko), or note this
99    /// information in the tokenizer.
100    pub fn is_fragment(&self) -> bool {
101        error!("Can't determine whether the url is a fragment.");
102        false
103    }
104
105    /// Returns the resolved url if it was valid.
106    pub fn url(&self) -> Option<&Arc<Url>> {
107        self.resolved.as_ref()
108    }
109
110    /// Return the resolved url as string, or the empty string if it's invalid.
111    ///
112    /// TODO(emilio): Should we return the original one if needed?
113    pub fn as_str(&self) -> &str {
114        match self.resolved {
115            Some(ref url) => url.as_str(),
116            None => "",
117        }
118    }
119
120    /// Creates an already specified url value from an already resolved URL
121    /// for insertion in the cascade.
122    pub fn for_cascade(url: Arc<::url::Url>) -> Self {
123        CssUrl(Arc::new(CssUrlData {
124            original: None,
125            resolved: Some(url),
126        }))
127    }
128
129    /// Gets a new url from a string for unit tests.
130    pub fn new_for_testing(url: &str) -> Self {
131        CssUrl(Arc::new(CssUrlData {
132            original: Some(Arc::new(url.into())),
133            resolved: (!url.is_empty())
134                .then(|| ::url::Url::parse(url))
135                .and_then(Result::ok)
136                .map(Arc::new),
137        }))
138    }
139}
140
141impl PartialEq for CssUrl {
142    fn eq(&self, other: &Self) -> bool {
143        // TODO(emilio): maybe we care about equality of the specified values if
144        // present? Seems not.
145        self.resolved == other.resolved
146    }
147}
148
149impl Eq for CssUrl {}
150
151impl ToCss for CssUrl {
152    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
153    where
154        W: Write,
155    {
156        let string = match self.0.original {
157            Some(ref original) => &**original,
158            None => match self.resolved {
159                Some(ref url) => url.as_str(),
160                // This can only happen if the url wasn't specified by the
161                // user *and* it's an invalid url that has been transformed
162                // back to specified value via the "uncompute" functionality.
163                None => "about:invalid",
164            },
165        };
166
167        dest.write_str("url(")?;
168        string.to_css(dest)?;
169        dest.write_char(')')
170    }
171}
172
173/// A specified url() value for servo.
174pub type SpecifiedUrl = CssUrl;
175
176impl ToComputedValue for SpecifiedUrl {
177    type ComputedValue = ComputedUrl;
178
179    // If we can't resolve the URL from the specified one, we fall back to the original
180    // but still return it as a ComputedUrl::Invalid
181    fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
182        match self.resolved {
183            Some(ref url) => ComputedUrl::Valid(url.clone()),
184            None => match self.original {
185                Some(ref url) => ComputedUrl::Invalid(url.clone()),
186                None => {
187                    unreachable!("Found specified url with neither resolved or original URI!");
188                },
189            },
190        }
191    }
192
193    fn from_computed_value(computed: &ComputedUrl) -> Self {
194        let data = match *computed {
195            ComputedUrl::Valid(ref url) => CssUrlData {
196                original: None,
197                resolved: Some(url.clone()),
198            },
199            ComputedUrl::Invalid(ref url) => CssUrlData {
200                original: Some(url.clone()),
201                resolved: None,
202            },
203        };
204        CssUrl(Arc::new(data))
205    }
206}
207
208/// The computed value of a CSS `url()`, resolved relative to the stylesheet URL.
209#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
210pub enum ComputedUrl {
211    /// The `url()` was invalid or it wasn't specified by the user.
212    Invalid(#[ignore_malloc_size_of = "Arc"] Arc<String>),
213    /// The resolved `url()` relative to the stylesheet URL.
214    Valid(#[ignore_malloc_size_of = "Arc"] Arc<Url>),
215}
216
217impl ComputedUrl {
218    /// Returns the resolved url if it was valid.
219    pub fn url(&self) -> Option<&Arc<Url>> {
220        match *self {
221            ComputedUrl::Valid(ref url) => Some(url),
222            _ => None,
223        }
224    }
225}
226
227impl ToCss for ComputedUrl {
228    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
229    where
230        W: Write,
231    {
232        let string = match *self {
233            ComputedUrl::Valid(ref url) => url.as_str(),
234            ComputedUrl::Invalid(ref invalid_string) => invalid_string,
235        };
236
237        dest.write_str("url(")?;
238        string.to_css(dest)?;
239        dest.write_char(')')
240    }
241}