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