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