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