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 #[css(function = "url")]
25 #[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
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().chars().next().map_or(false, |c| c == '#')
113     }
114 
115     /// Return the unresolved url as string, or the empty string if it's
116     /// invalid.
as_str(&self) -> &str117     pub fn as_str(&self) -> &str {
118         &*self.serialization
119     }
120 }
121 
122 impl Parse for CssUrl {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>123     fn parse<'i, 't>(
124         context: &ParserContext,
125         input: &mut Parser<'i, 't>,
126     ) -> Result<Self, ParseError<'i>> {
127         Self::parse_with_cors_mode(context, input, CorsMode::None)
128     }
129 }
130 
131 impl Eq for CssUrl {}
132 
133 impl MallocSizeOf for CssUrl {
size_of(&self, _ops: &mut MallocSizeOfOps) -> usize134     fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
135         // XXX: measure `serialization` once bug 1397971 lands
136 
137         // We ignore `extra_data`, because RefPtr is tricky, and there aren't
138         // many of them in practise (sharing is common).
139 
140         0
141     }
142 }
143 
144 /// A key type for LOAD_DATA_TABLE.
145 #[derive(Eq, Hash, PartialEq)]
146 struct LoadDataKey(*const LoadDataSource);
147 
148 unsafe impl Sync for LoadDataKey {}
149 unsafe impl Send for LoadDataKey {}
150 
151 bitflags! {
152     /// Various bits of mutable state that are kept for image loads.
153     #[repr(C)]
154     pub struct LoadDataFlags: u8 {
155         /// Whether we tried to resolve the uri at least once.
156         const TRIED_TO_RESOLVE_URI = 1 << 0;
157         /// Whether we tried to resolve the image at least once.
158         const TRIED_TO_RESOLVE_IMAGE = 1 << 1;
159     }
160 }
161 
162 /// This is usable and movable from multiple threads just fine, as long as it's
163 /// not cloned (it is not clonable), and the methods that mutate it run only on
164 /// the main thread (when all the other threads we care about are paused).
165 unsafe impl Sync for LoadData {}
166 unsafe impl Send for LoadData {}
167 
168 /// The load data for a given URL. This is mutable from C++, and shouldn't be
169 /// accessed from rust for anything.
170 #[repr(C)]
171 #[derive(Debug)]
172 pub struct LoadData {
173     /// A strong reference to the imgRequestProxy, if any, that should be
174     /// released on drop.
175     ///
176     /// These are raw pointers because they are not safe to reference-count off
177     /// the main thread.
178     resolved_image: *mut structs::imgRequestProxy,
179     /// A strong reference to the resolved URI of this image.
180     resolved_uri: *mut structs::nsIURI,
181     /// A few flags that are set when resolving the image or such.
182     flags: LoadDataFlags,
183 }
184 
185 impl Drop for LoadData {
drop(&mut self)186     fn drop(&mut self) {
187         unsafe { bindings::Gecko_LoadData_Drop(self) }
188     }
189 }
190 
191 impl Default for LoadData {
default() -> Self192     fn default() -> Self {
193         Self {
194             resolved_image: std::ptr::null_mut(),
195             resolved_uri: std::ptr::null_mut(),
196             flags: LoadDataFlags::empty(),
197         }
198     }
199 }
200 
201 /// The data for a load, or a lazy-loaded, static member that will be stored in
202 /// LOAD_DATA_TABLE, keyed by the memory location of this object, which is
203 /// always in the heap because it's inside the CssUrlData object.
204 ///
205 /// This type is meant not to be used from C++ so we don't derive helper
206 /// methods.
207 ///
208 /// cbindgen:derive-helper-methods=false
209 #[derive(Debug)]
210 #[repr(u8, C)]
211 pub enum LoadDataSource {
212     /// An owned copy of the load data.
213     Owned(LoadData),
214     /// A lazily-resolved copy of it.
215     Lazy,
216 }
217 
218 impl LoadDataSource {
219     /// Gets the load data associated with the source.
220     ///
221     /// This relies on the source on being in a stable location if lazy.
222     #[inline]
get(&self) -> *const LoadData223     pub unsafe fn get(&self) -> *const LoadData {
224         match *self {
225             LoadDataSource::Owned(ref d) => return d,
226             LoadDataSource::Lazy => {},
227         }
228 
229         let key = LoadDataKey(self);
230 
231         {
232             let guard = LOAD_DATA_TABLE.read().unwrap();
233             if let Some(r) = guard.get(&key) {
234                 return &**r;
235             }
236         }
237         let mut guard = LOAD_DATA_TABLE.write().unwrap();
238         let r = guard.entry(key).or_insert_with(Default::default);
239         &**r
240     }
241 }
242 
243 impl ToShmem for LoadDataSource {
to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self>244     fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
245         Ok(ManuallyDrop::new(match self {
246             LoadDataSource::Owned(..) => LoadDataSource::Lazy,
247             LoadDataSource::Lazy => LoadDataSource::Lazy,
248         }))
249     }
250 }
251 
252 /// A specified non-image `url()` value.
253 pub type SpecifiedUrl = CssUrl;
254 
255 /// Clears LOAD_DATA_TABLE.  Entries in this table, which are for specified URL
256 /// values that come from shared memory style sheets, would otherwise persist
257 /// until the end of the process and be reported as leaks.
shutdown()258 pub fn shutdown() {
259     LOAD_DATA_TABLE.write().unwrap().clear();
260 }
261 
262 impl ToComputedValue for SpecifiedUrl {
263     type ComputedValue = ComputedUrl;
264 
265     #[inline]
to_computed_value(&self, _: &Context) -> Self::ComputedValue266     fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
267         ComputedUrl(self.clone())
268     }
269 
270     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self271     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
272         computed.0.clone()
273     }
274 }
275 
276 /// A specified image `url()` value.
277 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
278 pub struct SpecifiedImageUrl(pub SpecifiedUrl);
279 
280 impl SpecifiedImageUrl {
281     /// 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) -> Self282     pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self {
283         SpecifiedImageUrl(SpecifiedUrl::parse_from_string(url, context, cors_mode))
284     }
285 
286     /// Provides an alternate method for parsing that associates the URL
287     /// with anonymous CORS headers.
parse_with_cors_anonymous<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>288     pub fn parse_with_cors_anonymous<'i, 't>(
289         context: &ParserContext,
290         input: &mut Parser<'i, 't>,
291     ) -> Result<Self, ParseError<'i>> {
292         Ok(SpecifiedImageUrl(SpecifiedUrl::parse_with_cors_mode(
293             context,
294             input,
295             CorsMode::Anonymous,
296         )?))
297     }
298 }
299 
300 impl Parse for SpecifiedImageUrl {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>301     fn parse<'i, 't>(
302         context: &ParserContext,
303         input: &mut Parser<'i, 't>,
304     ) -> Result<Self, ParseError<'i>> {
305         SpecifiedUrl::parse(context, input).map(SpecifiedImageUrl)
306     }
307 }
308 
309 impl ToComputedValue for SpecifiedImageUrl {
310     type ComputedValue = ComputedImageUrl;
311 
312     #[inline]
to_computed_value(&self, context: &Context) -> Self::ComputedValue313     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
314         ComputedImageUrl(self.0.to_computed_value(context))
315     }
316 
317     #[inline]
from_computed_value(computed: &Self::ComputedValue) -> Self318     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
319         SpecifiedImageUrl(ToComputedValue::from_computed_value(&computed.0))
320     }
321 }
322 
323 /// The computed value of a CSS non-image `url()`.
324 ///
325 /// The only difference between specified and computed URLs is the
326 /// serialization.
327 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)]
328 #[repr(C)]
329 pub struct ComputedUrl(pub SpecifiedUrl);
330 
331 impl ComputedUrl {
serialize_with<W>( &self, function: unsafe extern "C" fn(*const Self, *mut nsCString), dest: &mut CssWriter<W>, ) -> fmt::Result where W: Write,332     fn serialize_with<W>(
333         &self,
334         function: unsafe extern "C" fn(*const Self, *mut nsCString),
335         dest: &mut CssWriter<W>,
336     ) -> fmt::Result
337     where
338         W: Write,
339     {
340         dest.write_str("url(")?;
341         unsafe {
342             let mut string = nsCString::new();
343             function(self, &mut string);
344             string.as_str_unchecked().to_css(dest)?;
345         }
346         dest.write_char(')')
347     }
348 }
349 
350 impl ToCss for ComputedUrl {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,351     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
352     where
353         W: Write,
354     {
355         self.serialize_with(bindings::Gecko_GetComputedURLSpec, dest)
356     }
357 }
358 
359 /// The computed value of a CSS image `url()`.
360 #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)]
361 #[repr(transparent)]
362 pub struct ComputedImageUrl(pub ComputedUrl);
363 
364 impl ToCss for ComputedImageUrl {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,365     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
366     where
367         W: Write,
368     {
369         self.0
370             .serialize_with(bindings::Gecko_GetComputedImageURLSpec, dest)
371     }
372 }
373 
374 lazy_static! {
375     /// A table mapping CssUrlData objects to their lazily created LoadData
376     /// objects.
377     static ref LOAD_DATA_TABLE: RwLock<HashMap<LoadDataKey, Box<LoadData>>> = {
378         Default::default()
379     };
380 }
381