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 
7 use crate::gecko_bindings::bindings;
8 use crate::gecko_bindings::structs;
9 use crate::parser::{Parse, ParserContext};
10 use crate::stylesheets::{CorsMode, UrlExtraData};
11 use crate::values::computed::{Context, ToComputedValue};
12 use cssparser::Parser;
13 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
14 use nsstring::nsCString;
15 use servo_arc::Arc;
16 use std::collections::HashMap;
17 use std::fmt::{self, Write};
18 use std::mem::ManuallyDrop;
19 use std::sync::RwLock;
20 use style_traits::{CssWriter, ParseError, ToCss};
21 use to_shmem::{self, SharedMemoryBuilder, ToShmem};
22 
23 /// A CSS url() value for gecko.
24 #[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
25 #[css(function = "url")]
26 #[repr(C)]
27 pub struct CssUrl(pub Arc<CssUrlData>);
28 
29 /// Data shared between CssUrls.
30 ///
31 /// cbindgen:derive-eq=false
32 /// cbindgen:derive-neq=false
33 #[derive(Debug, SpecifiedValueInfo, ToCss, ToShmem)]
34 #[repr(C)]
35 pub struct CssUrlData {
36     /// The URL in unresolved string form.
37     serialization: crate::OwnedStr,
38 
39     /// The URL extra data.
40     #[css(skip)]
41     pub extra_data: UrlExtraData,
42 
43     /// The CORS mode that will be used for the load.
44     #[css(skip)]
45     cors_mode: CorsMode,
46 
47     /// Data to trigger a load from Gecko. This is mutable in C++.
48     ///
49     /// TODO(emilio): Maybe we can eagerly resolve URLs and make this immutable?
50     #[css(skip)]
51     load_data: LoadDataSource,
52 }
53 
54 impl PartialEq for CssUrlData {
eq(&self, other: &Self) -> bool55     fn eq(&self, other: &Self) -> bool {
56         self.serialization == other.serialization &&
57             self.extra_data == other.extra_data &&
58             self.cors_mode == other.cors_mode
59     }
60 }
61 
62 impl CssUrl {
parse_with_cors_mode<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, cors_mode: CorsMode, ) -> Result<Self, ParseError<'i>>63     fn parse_with_cors_mode<'i, 't>(
64         context: &ParserContext,
65         input: &mut Parser<'i, 't>,
66         cors_mode: CorsMode,
67     ) -> Result<Self, ParseError<'i>> {
68         let url = input.expect_url()?;
69         Ok(Self::parse_from_string(
70             url.as_ref().to_owned(),
71             context,
72             cors_mode,
73         ))
74     }
75 
76     /// Parse a URL from a string value that is a valid CSS token for a URL.
parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self77     pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self {
78         CssUrl(Arc::new(CssUrlData {
79             serialization: url.into(),
80             extra_data: context.url_data.clone(),
81             cors_mode,
82             load_data: LoadDataSource::Owned(LoadData::default()),
83         }))
84     }
85 
86     /// Returns true if the URL is definitely invalid. We don't eagerly resolve
87     /// URLs in gecko, so we just return false here.
88     /// use its |resolved| status.
is_invalid(&self) -> bool89     pub fn is_invalid(&self) -> bool {
90         false
91     }
92 
93     /// Returns true if this URL looks like a fragment.
94     /// See https://drafts.csswg.org/css-values/#local-urls
95     #[inline]
is_fragment(&self) -> bool96     pub fn is_fragment(&self) -> bool {
97         self.0.is_fragment()
98     }
99 
100     /// Return the unresolved url as string, or the empty string if it's
101     /// invalid.
102     #[inline]
as_str(&self) -> &str103     pub fn as_str(&self) -> &str {
104         self.0.as_str()
105     }
106 }
107 
108 impl CssUrlData {
109     /// Returns true if this URL looks like a fragment.
110     /// See https://drafts.csswg.org/css-values/#local-urls
is_fragment(&self) -> bool111     pub fn is_fragment(&self) -> bool {
112         self.as_str()
113             .as_bytes()
114             .iter()
115             .next()
116             .map_or(false, |b| *b == b'#')
117     }
118 
119     /// Return the unresolved url as string, or the empty string if it's
120     /// invalid.
as_str(&self) -> &str121     pub fn as_str(&self) -> &str {
122         &*self.serialization
123     }
124 }
125 
126 impl Parse for CssUrl {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>127     fn parse<'i, 't>(
128         context: &ParserContext,
129         input: &mut Parser<'i, 't>,
130     ) -> Result<Self, ParseError<'i>> {
131         Self::parse_with_cors_mode(context, input, CorsMode::None)
132     }
133 }
134 
135 impl Eq for CssUrl {}
136 
137 impl MallocSizeOf for CssUrl {
size_of(&self, _ops: &mut MallocSizeOfOps) -> usize138     fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
139         // XXX: measure `serialization` once bug 1397971 lands
140 
141         // We ignore `extra_data`, because RefPtr is tricky, and there aren't
142         // many of them in practise (sharing is common).
143 
144         0
145     }
146 }
147 
148 /// A key type for LOAD_DATA_TABLE.
149 #[derive(Eq, Hash, PartialEq)]
150 struct LoadDataKey(*const LoadDataSource);
151 
152 unsafe impl Sync for LoadDataKey {}
153 unsafe impl Send for LoadDataKey {}
154 
155 bitflags! {
156     /// Various bits of mutable state that are kept for image loads.
157     #[repr(C)]
158     pub struct LoadDataFlags: u8 {
159         /// Whether we tried to resolve the uri at least once.
160         const TRIED_TO_RESOLVE_URI = 1 << 0;
161         /// Whether we tried to resolve the image at least once.
162         const TRIED_TO_RESOLVE_IMAGE = 1 << 1;
163     }
164 }
165 
166 /// This is usable and movable from multiple threads just fine, as long as it's
167 /// not cloned (it is not clonable), and the methods that mutate it run only on
168 /// the main thread (when all the other threads we care about are paused).
169 unsafe impl Sync for LoadData {}
170 unsafe impl Send for LoadData {}
171 
172 /// The load data for a given URL. This is mutable from C++, and shouldn't be
173 /// accessed from rust for anything.
174 #[repr(C)]
175 #[derive(Debug)]
176 pub struct LoadData {
177     /// A strong reference to the imgRequestProxy, if any, that should be
178     /// released on drop.
179     ///
180     /// These are raw pointers because they are not safe to reference-count off
181     /// the main thread.
182     resolved_image: *mut structs::imgRequestProxy,
183     /// A strong reference to the resolved URI of this image.
184     resolved_uri: *mut structs::nsIURI,
185     /// A few flags that are set when resolving the image or such.
186     flags: LoadDataFlags,
187 }
188 
189 impl Drop for LoadData {
drop(&mut self)190     fn drop(&mut self) {
191         unsafe { bindings::Gecko_LoadData_Drop(self) }
192     }
193 }
194 
195 impl Default for LoadData {
default() -> Self196     fn default() -> Self {
197         Self {
198             resolved_image: std::ptr::null_mut(),
199             resolved_uri: std::ptr::null_mut(),
200             flags: LoadDataFlags::empty(),
201         }
202     }
203 }
204 
205 /// The data for a load, or a lazy-loaded, static member that will be stored in
206 /// LOAD_DATA_TABLE, keyed by the memory location of this object, which is
207 /// always in the heap because it's inside the CssUrlData object.
208 ///
209 /// This type is meant not to be used from C++ so we don't derive helper
210 /// methods.
211 ///
212 /// cbindgen:derive-helper-methods=false
213 #[derive(Debug)]
214 #[repr(u8, C)]
215 pub enum LoadDataSource {
216     /// An owned copy of the load data.
217     Owned(LoadData),
218     /// A lazily-resolved copy of it.
219     Lazy,
220 }
221 
222 impl LoadDataSource {
223     /// Gets the load data associated with the source.
224     ///
225     /// This relies on the source on being in a stable location if lazy.
226     #[inline]
get(&self) -> *const LoadData227     pub unsafe fn get(&self) -> *const LoadData {
228         match *self {
229             LoadDataSource::Owned(ref d) => return d,
230             LoadDataSource::Lazy => {},
231         }
232 
233         let key = LoadDataKey(self);
234 
235         {
236             let guard = LOAD_DATA_TABLE.read().unwrap();
237             if let Some(r) = guard.get(&key) {
238                 return &**r;
239             }
240         }
241         let mut guard = LOAD_DATA_TABLE.write().unwrap();
242         let r = guard.entry(key).or_insert_with(Default::default);
243         &**r
244     }
245 }
246 
247 impl ToShmem for LoadDataSource {
to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self>248     fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
249         Ok(ManuallyDrop::new(match self {
250             LoadDataSource::Owned(..) => LoadDataSource::Lazy,
251             LoadDataSource::Lazy => LoadDataSource::Lazy,
252         }))
253     }
254 }
255 
256 /// A specified non-image `url()` value.
257 pub type SpecifiedUrl = CssUrl;
258 
259 /// Clears LOAD_DATA_TABLE.  Entries in this table, which are for specified URL
260 /// values that come from shared memory style sheets, would otherwise persist
261 /// until the end of the process and be reported as leaks.
shutdown()262 pub fn shutdown() {
263     LOAD_DATA_TABLE.write().unwrap().clear();
264 }
265 
266 impl ToComputedValue for SpecifiedUrl {
267     type ComputedValue = ComputedUrl;
268 
269     #[inline]
to_computed_value(&self, _: &Context) -> Self::ComputedValue270     fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
271         ComputedUrl(self.clone())
272     }
273 
274     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self275     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
276         computed.0.clone()
277     }
278 }
279 
280 /// A specified image `url()` value.
281 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
282 pub struct SpecifiedImageUrl(pub SpecifiedUrl);
283 
284 impl SpecifiedImageUrl {
285     /// Parse a URL from a string value that is a valid CSS token for a URL.
parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self286     pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self {
287         SpecifiedImageUrl(SpecifiedUrl::parse_from_string(url, context, cors_mode))
288     }
289 
290     /// Provides an alternate method for parsing that associates the URL
291     /// with anonymous CORS headers.
parse_with_cors_mode<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, cors_mode: CorsMode, ) -> Result<Self, ParseError<'i>>292     pub fn parse_with_cors_mode<'i, 't>(
293         context: &ParserContext,
294         input: &mut Parser<'i, 't>,
295         cors_mode: CorsMode,
296     ) -> Result<Self, ParseError<'i>> {
297         Ok(SpecifiedImageUrl(SpecifiedUrl::parse_with_cors_mode(
298             context, input, cors_mode,
299         )?))
300     }
301 }
302 
303 impl Parse for SpecifiedImageUrl {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>304     fn parse<'i, 't>(
305         context: &ParserContext,
306         input: &mut Parser<'i, 't>,
307     ) -> Result<Self, ParseError<'i>> {
308         SpecifiedUrl::parse(context, input).map(SpecifiedImageUrl)
309     }
310 }
311 
312 impl ToComputedValue for SpecifiedImageUrl {
313     type ComputedValue = ComputedImageUrl;
314 
315     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue316     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
317         ComputedImageUrl(self.0.to_computed_value(context))
318     }
319 
320     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self321     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
322         SpecifiedImageUrl(ToComputedValue::from_computed_value(&computed.0))
323     }
324 }
325 
326 /// The computed value of a CSS non-image `url()`.
327 ///
328 /// The only difference between specified and computed URLs is the
329 /// serialization.
330 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)]
331 #[repr(C)]
332 pub struct ComputedUrl(pub SpecifiedUrl);
333 
334 impl ComputedUrl {
serialize_with<W>( &self, function: unsafe extern "C" fn(*const Self, *mut nsCString), dest: &mut CssWriter<W>, ) -> fmt::Result where W: Write,335     fn serialize_with<W>(
336         &self,
337         function: unsafe extern "C" fn(*const Self, *mut nsCString),
338         dest: &mut CssWriter<W>,
339     ) -> fmt::Result
340     where
341         W: Write,
342     {
343         dest.write_str("url(")?;
344         unsafe {
345             let mut string = nsCString::new();
346             function(self, &mut string);
347             string.as_str_unchecked().to_css(dest)?;
348         }
349         dest.write_char(')')
350     }
351 }
352 
353 impl ToCss for ComputedUrl {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,354     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
355     where
356         W: Write,
357     {
358         self.serialize_with(bindings::Gecko_GetComputedURLSpec, dest)
359     }
360 }
361 
362 /// The computed value of a CSS image `url()`.
363 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)]
364 #[repr(transparent)]
365 pub struct ComputedImageUrl(pub ComputedUrl);
366 
367 impl ToCss for ComputedImageUrl {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,368     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
369     where
370         W: Write,
371     {
372         self.0
373             .serialize_with(bindings::Gecko_GetComputedImageURLSpec, dest)
374     }
375 }
376 
377 lazy_static! {
378     /// A table mapping CssUrlData objects to their lazily created LoadData
379     /// objects.
380     static ref LOAD_DATA_TABLE: RwLock<HashMap<LoadDataKey, Box<LoadData>>> = {
381         Default::default()
382     };
383 }
384