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