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