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