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