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