tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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());