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