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