Skip to main content

style/stylesheets/
stylesheet.rs

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
5use crate::context::QuirksMode;
6use crate::derives::*;
7use crate::error_reporting::{ContextualParseError, ParseErrorReporter};
8use crate::media_queries::{Device, MediaList};
9use crate::parser::ParserContext;
10use crate::shared_lock::{DeepCloneWithLock, Locked};
11use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard};
12use crate::stylesheets::loader::StylesheetLoader;
13use crate::stylesheets::rule_parser::{State, TopLevelRuleParser};
14use crate::stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator};
15use crate::stylesheets::rules_iterator::{NestedRuleIterationCondition, RulesIterator};
16use crate::stylesheets::{
17    CssRule, CssRules, CustomMediaEvaluator, CustomMediaMap, Origin, UrlExtraData,
18};
19use crate::use_counters::UseCounters;
20use crate::{Namespace, Prefix};
21use cssparser::{Parser, ParserInput, StyleSheetParser};
22#[cfg(feature = "gecko")]
23use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
24use rustc_hash::FxHashMap;
25use servo_arc::Arc;
26use std::ops::Deref;
27use std::sync::atomic::{AtomicBool, Ordering};
28use style_traits::ParsingMode;
29
30use super::scope_rule::ImplicitScopeRoot;
31
32/// A set of namespaces applying to a given stylesheet.
33///
34/// The namespace id is used in gecko
35#[derive(Clone, Debug, Default, MallocSizeOf)]
36#[allow(missing_docs)]
37pub struct Namespaces {
38    pub default: Option<Namespace>,
39    pub prefixes: FxHashMap<Prefix, Namespace>,
40}
41
42/// The contents of a given stylesheet. This effectively maps to a
43/// StyleSheetInner in Gecko.
44#[derive(Debug)]
45pub struct StylesheetContents {
46    /// List of rules in the order they were found (important for
47    /// cascading order)
48    pub rules: Arc<Locked<CssRules>>,
49    /// The origin of this stylesheet.
50    pub origin: Origin,
51    /// The url data this stylesheet should use.
52    pub url_data: UrlExtraData,
53    /// The namespaces that apply to this stylesheet.
54    pub namespaces: Namespaces,
55    /// The quirks mode of this stylesheet.
56    pub quirks_mode: QuirksMode,
57    /// This stylesheet's source map URL.
58    pub source_map_url: Option<String>,
59    /// This stylesheet's source URL.
60    pub source_url: Option<String>,
61    /// The use counters of the original stylesheet.
62    pub use_counters: UseCounters,
63
64    /// We don't want to allow construction outside of this file, to guarantee
65    /// that all contents are created with Arc<>.
66    _forbid_construction: (),
67}
68
69impl StylesheetContents {
70    /// Parse a given CSS string, with a given url-data, origin, and
71    /// quirks mode.
72    pub fn from_str(
73        css: &str,
74        url_data: UrlExtraData,
75        origin: Origin,
76        shared_lock: &SharedRwLock,
77        stylesheet_loader: Option<&dyn StylesheetLoader>,
78        error_reporter: Option<&dyn ParseErrorReporter>,
79        quirks_mode: QuirksMode,
80        allow_import_rules: AllowImportRules,
81        sanitization_data: Option<&mut SanitizationData>,
82    ) -> Arc<Self> {
83        let use_counters = UseCounters::default();
84        let (namespaces, rules, source_map_url, source_url) = Stylesheet::parse_rules(
85            css,
86            &url_data,
87            origin,
88            &shared_lock,
89            stylesheet_loader,
90            error_reporter,
91            quirks_mode,
92            Some(&use_counters),
93            allow_import_rules,
94            sanitization_data,
95        );
96
97        Arc::new(Self {
98            rules: CssRules::new(rules, &shared_lock),
99            origin,
100            url_data,
101            namespaces,
102            quirks_mode,
103            source_map_url,
104            source_url,
105            use_counters,
106            _forbid_construction: (),
107        })
108    }
109
110    /// Creates a new StylesheetContents with the specified pre-parsed rules,
111    /// origin, URL data, and quirks mode.
112    ///
113    /// Since the rules have already been parsed, and the intention is that
114    /// this function is used for read only User Agent style sheets, an empty
115    /// namespace map is used, and the source map and source URLs are set to
116    /// None.
117    ///
118    /// An empty namespace map should be fine, as it is only used for parsing,
119    /// not serialization of existing selectors.  Since UA sheets are read only,
120    /// we should never need the namespace map.
121    pub fn from_shared_data(
122        rules: Arc<Locked<CssRules>>,
123        origin: Origin,
124        url_data: UrlExtraData,
125        quirks_mode: QuirksMode,
126    ) -> Arc<Self> {
127        debug_assert!(rules.is_static());
128        Arc::new(Self {
129            rules,
130            origin,
131            url_data,
132            namespaces: Namespaces::default(),
133            quirks_mode,
134            source_map_url: None,
135            source_url: None,
136            use_counters: UseCounters::default(),
137            _forbid_construction: (),
138        })
139    }
140
141    /// Returns a reference to the list of rules.
142    #[inline]
143    pub fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] {
144        &self.rules.read_with(guard).0
145    }
146
147    /// Measure heap usage.
148    #[cfg(feature = "gecko")]
149    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
150        if self.rules.is_static() {
151            return 0;
152        }
153        // Measurement of other fields may be added later.
154        self.rules.unconditional_shallow_size_of(ops)
155            + self.rules.read_with(guard).size_of(guard, ops)
156    }
157
158    /// Return an iterator using the condition `C`.
159    #[inline]
160    pub fn iter_rules<'a, 'b, C, CMM>(
161        &'a self,
162        device: &'a Device,
163        custom_media: CMM,
164        guard: &'a SharedRwLockReadGuard<'b>,
165    ) -> RulesIterator<'a, 'b, C, CMM>
166    where
167        C: NestedRuleIterationCondition,
168        CMM: Deref<Target = CustomMediaMap>,
169    {
170        RulesIterator::new(
171            device,
172            self.quirks_mode,
173            custom_media,
174            guard,
175            self.rules(guard).iter(),
176        )
177    }
178
179    /// Return an iterator over the effective rules within the style-sheet, as
180    /// according to the supplied `Device`.
181    #[inline]
182    pub fn effective_rules<'a, 'b, CMM: Deref<Target = CustomMediaMap>>(
183        &'a self,
184        device: &'a Device,
185        custom_media: CMM,
186        guard: &'a SharedRwLockReadGuard<'b>,
187    ) -> EffectiveRulesIterator<'a, 'b, CMM> {
188        self.iter_rules::<EffectiveRules, CMM>(device, custom_media, guard)
189    }
190
191    /// Perform a deep clone, of this stylesheet, with an explicit URL data if needed.
192    pub fn deep_clone(
193        &self,
194        lock: &SharedRwLock,
195        url_data: Option<&UrlExtraData>,
196        guard: &SharedRwLockReadGuard,
197    ) -> Arc<Self> {
198        // Make a deep clone of the rules, using the new lock.
199        let rules = self
200            .rules
201            .read_with(guard)
202            .deep_clone_with_lock(lock, guard);
203
204        let url_data = url_data.cloned().unwrap_or_else(|| self.url_data.clone());
205
206        Arc::new(Self {
207            rules: Arc::new(lock.wrap(rules)),
208            quirks_mode: self.quirks_mode,
209            origin: self.origin,
210            url_data,
211            namespaces: self.namespaces.clone(),
212            source_map_url: self.source_map_url.clone(),
213            source_url: self.source_url.clone(),
214            use_counters: self.use_counters.clone(),
215            _forbid_construction: (),
216        })
217    }
218}
219
220/// The structure servo uses to represent a stylesheet.
221#[derive(Debug)]
222pub struct Stylesheet {
223    /// The contents of this stylesheet.
224    pub contents: Locked<Arc<StylesheetContents>>,
225    /// The lock used for objects inside this stylesheet
226    pub shared_lock: SharedRwLock,
227    /// List of media associated with the Stylesheet.
228    pub media: Arc<Locked<MediaList>>,
229    /// Whether this stylesheet should be disabled.
230    pub disabled: AtomicBool,
231}
232
233/// A trait to represent a given stylesheet in a document.
234pub trait StylesheetInDocument: ::std::fmt::Debug {
235    /// Get whether this stylesheet is enabled.
236    fn enabled(&self) -> bool;
237
238    /// Get the media associated with this stylesheet.
239    fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>;
240
241    /// Returns a reference to the contents of the stylesheet.
242    fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents;
243
244    /// Returns whether the style-sheet applies for the current device.
245    fn is_effective_for_device(
246        &self,
247        device: &Device,
248        custom_media: &CustomMediaMap,
249        guard: &SharedRwLockReadGuard,
250    ) -> bool {
251        let media = match self.media(guard) {
252            Some(m) => m,
253            None => return true,
254        };
255        media.evaluate(
256            device,
257            self.contents(guard).quirks_mode,
258            &mut CustomMediaEvaluator::new(custom_media, guard),
259        )
260    }
261
262    /// Return the implicit scope root for this stylesheet, if one exists.
263    fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot>;
264}
265
266impl StylesheetInDocument for Stylesheet {
267    fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
268        Some(self.media.read_with(guard))
269    }
270
271    fn enabled(&self) -> bool {
272        !self.disabled()
273    }
274
275    #[inline]
276    fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents {
277        self.contents.read_with(guard)
278    }
279
280    fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> {
281        None
282    }
283}
284
285/// A simple wrapper over an `Arc<Stylesheet>`, with pointer comparison, and
286/// suitable for its use in a `StylesheetSet`.
287#[derive(Clone, Debug)]
288#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
289pub struct DocumentStyleSheet(
290    #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] pub Arc<Stylesheet>,
291);
292
293impl PartialEq for DocumentStyleSheet {
294    fn eq(&self, other: &Self) -> bool {
295        Arc::ptr_eq(&self.0, &other.0)
296    }
297}
298
299impl StylesheetInDocument for DocumentStyleSheet {
300    fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
301        self.0.media(guard)
302    }
303
304    fn enabled(&self) -> bool {
305        self.0.enabled()
306    }
307
308    #[inline]
309    fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents {
310        self.0.contents(guard)
311    }
312
313    fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> {
314        None
315    }
316}
317
318/// The kind of sanitization to use when parsing a stylesheet.
319#[repr(u8)]
320#[derive(Clone, Copy, Debug, PartialEq)]
321pub enum SanitizationKind {
322    /// Perform no sanitization.
323    None,
324    /// Allow only @font-face, style rules, and @namespace.
325    Standard,
326    /// Allow everything but conditional rules.
327    NoConditionalRules,
328}
329
330/// Whether @import rules are allowed.
331#[repr(u8)]
332#[derive(Clone, Copy, Debug, PartialEq)]
333pub enum AllowImportRules {
334    /// @import rules will be parsed.
335    Yes,
336    /// @import rules will not be parsed.
337    No,
338}
339
340impl SanitizationKind {
341    fn allows(self, rule: &CssRule) -> bool {
342        debug_assert_ne!(self, SanitizationKind::None);
343        // NOTE(emilio): If this becomes more complex (not filtering just by
344        // top-level rules), we should thread all the data through nested rules
345        // and such. But this doesn't seem necessary at the moment.
346        let is_standard = matches!(self, SanitizationKind::Standard);
347        match *rule {
348            CssRule::Document(..) |
349            CssRule::Media(..) |
350            CssRule::CustomMedia(..) |
351            CssRule::Supports(..) |
352            CssRule::Import(..) |
353            CssRule::Container(..) |
354            // TODO(emilio): Perhaps Layer should not be always sanitized? But
355            // we sanitize @media and co, so this seems safer for now.
356            CssRule::LayerStatement(..) |
357            CssRule::LayerBlock(..) |
358            // TODO(dshin): Same comment as Layer applies - shouldn't give away
359            // something like display size - erring on the side of "safe" for now.
360            CssRule::Scope(..) |
361            CssRule::StartingStyle(..) => false,
362
363            CssRule::FontFace(..) |
364            CssRule::Namespace(..) |
365            CssRule::Style(..) |
366            CssRule::NestedDeclarations(..) |
367            CssRule::PositionTry(..) => true,
368
369            CssRule::Keyframes(..) |
370            CssRule::Page(..) |
371            CssRule::Margin(..) |
372            CssRule::Property(..) |
373            CssRule::FontFeatureValues(..) |
374            CssRule::FontPaletteValues(..) |
375            CssRule::CounterStyle(..) => !is_standard,
376        }
377    }
378}
379
380/// A struct to hold the data relevant to style sheet sanitization.
381#[derive(Debug)]
382pub struct SanitizationData {
383    kind: SanitizationKind,
384    output: String,
385}
386
387impl SanitizationData {
388    /// Create a new input for sanitization.
389    #[inline]
390    pub fn new(kind: SanitizationKind) -> Option<Self> {
391        if matches!(kind, SanitizationKind::None) {
392            return None;
393        }
394        Some(Self {
395            kind,
396            output: String::new(),
397        })
398    }
399
400    /// Take the sanitized output.
401    #[inline]
402    pub fn take(self) -> String {
403        self.output
404    }
405}
406
407impl Stylesheet {
408    fn parse_rules(
409        css: &str,
410        url_data: &UrlExtraData,
411        origin: Origin,
412        shared_lock: &SharedRwLock,
413        stylesheet_loader: Option<&dyn StylesheetLoader>,
414        error_reporter: Option<&dyn ParseErrorReporter>,
415        quirks_mode: QuirksMode,
416        use_counters: Option<&UseCounters>,
417        allow_import_rules: AllowImportRules,
418        mut sanitization_data: Option<&mut SanitizationData>,
419    ) -> (Namespaces, Vec<CssRule>, Option<String>, Option<String>) {
420        let mut input = ParserInput::new(css);
421        let mut input = Parser::new(&mut input);
422
423        let context = ParserContext::new(
424            origin,
425            url_data,
426            None,
427            ParsingMode::DEFAULT,
428            quirks_mode,
429            /* namespaces = */ Default::default(),
430            error_reporter,
431            use_counters,
432        );
433
434        let mut rule_parser = TopLevelRuleParser {
435            shared_lock,
436            loader: stylesheet_loader,
437            context,
438            state: State::Start,
439            dom_error: None,
440            insert_rule_context: None,
441            allow_import_rules,
442            declaration_parser_state: Default::default(),
443            first_declaration_block: Default::default(),
444            wants_first_declaration_block: false,
445            error_reporting_state: Default::default(),
446            rules: Vec::new(),
447        };
448
449        {
450            let mut iter = StyleSheetParser::new(&mut input, &mut rule_parser);
451            while let Some(result) = iter.next() {
452                match result {
453                    Ok(rule_start) => {
454                        // TODO(emilio, nesting): sanitize nested CSS rules, probably?
455                        if let Some(ref mut data) = sanitization_data {
456                            if let Some(ref rule) = iter.parser.rules.last() {
457                                if !data.kind.allows(rule) {
458                                    iter.parser.rules.pop();
459                                    continue;
460                                }
461                            }
462                            let end = iter.input.position().byte_index();
463                            data.output.push_str(&css[rule_start.byte_index()..end]);
464                        }
465                    },
466                    Err((error, slice)) => {
467                        let location = error.location;
468                        let error = ContextualParseError::InvalidRule(slice, error);
469                        iter.parser.context.log_css_error(location, error);
470                    },
471                }
472            }
473        }
474
475        let source_map_url = input.current_source_map_url().map(String::from);
476        let source_url = input.current_source_url().map(String::from);
477        (
478            rule_parser.context.namespaces.into_owned(),
479            rule_parser.rules,
480            source_map_url,
481            source_url,
482        )
483    }
484
485    /// Creates an empty stylesheet and parses it with a given base url, origin and media.
486    pub fn from_str(
487        css: &str,
488        url_data: UrlExtraData,
489        origin: Origin,
490        media: Arc<Locked<MediaList>>,
491        shared_lock: SharedRwLock,
492        stylesheet_loader: Option<&dyn StylesheetLoader>,
493        error_reporter: Option<&dyn ParseErrorReporter>,
494        quirks_mode: QuirksMode,
495        allow_import_rules: AllowImportRules,
496    ) -> Self {
497        // FIXME: Consider adding use counters to Servo?
498        let contents = StylesheetContents::from_str(
499            css,
500            url_data,
501            origin,
502            &shared_lock,
503            stylesheet_loader,
504            error_reporter,
505            quirks_mode,
506            allow_import_rules,
507            /* sanitized_output = */ None,
508        );
509
510        Stylesheet {
511            contents: shared_lock.wrap(contents),
512            shared_lock,
513            media,
514            disabled: AtomicBool::new(false),
515        }
516    }
517
518    /// Returns whether the stylesheet has been explicitly disabled through the
519    /// CSSOM.
520    pub fn disabled(&self) -> bool {
521        self.disabled.load(Ordering::SeqCst)
522    }
523
524    /// Records that the stylesheet has been explicitly disabled through the
525    /// CSSOM.
526    ///
527    /// Returns whether the the call resulted in a change in disabled state.
528    ///
529    /// Disabled stylesheets remain in the document, but their rules are not
530    /// added to the Stylist.
531    pub fn set_disabled(&self, disabled: bool) -> bool {
532        self.disabled.swap(disabled, Ordering::SeqCst) != disabled
533    }
534}
535
536#[cfg(feature = "servo")]
537impl Clone for Stylesheet {
538    fn clone(&self) -> Self {
539        // Create a new lock for our clone.
540        let lock = self.shared_lock.clone();
541        let guard = self.shared_lock.read();
542
543        // Make a deep clone of the media, using the new lock.
544        let media = self.media.read_with(&guard).clone();
545        let media = Arc::new(lock.wrap(media));
546        let contents = lock.wrap(
547            self.contents
548                .read_with(&guard)
549                .deep_clone(&lock, None, &guard),
550        );
551
552        Stylesheet {
553            contents,
554            media,
555            shared_lock: lock,
556            disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)),
557        }
558    }
559}