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