style/stylesheets/
rule_list.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
5//! A list of CSS rules.
6
7use crate::derives::*;
8use crate::shared_lock::{DeepCloneWithLock, Locked};
9use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
10use crate::stylesheets::loader::StylesheetLoader;
11use crate::stylesheets::rule_parser::InsertRuleContext;
12use crate::stylesheets::stylesheet::StylesheetContents;
13use crate::stylesheets::{AllowImportRules, CssRule, CssRuleTypes, RulesMutateError};
14#[cfg(feature = "gecko")]
15use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps};
16use servo_arc::Arc;
17use std::fmt::{self, Write};
18use style_traits::CssStringWriter;
19
20use super::CssRuleType;
21
22/// A list of CSS rules.
23#[derive(Debug, ToShmem)]
24pub struct CssRules(pub Vec<CssRule>);
25
26impl CssRules {
27    /// Whether this CSS rules is empty.
28    pub fn is_empty(&self) -> bool {
29        self.0.is_empty()
30    }
31}
32
33impl DeepCloneWithLock for CssRules {
34    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
35        CssRules(
36            self.0
37                .iter()
38                .map(|x| x.deep_clone_with_lock(lock, guard))
39                .collect(),
40        )
41    }
42}
43
44impl CssRules {
45    /// Measure heap usage.
46    #[cfg(feature = "gecko")]
47    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
48        let mut n = self.0.shallow_size_of(ops);
49        for rule in self.0.iter() {
50            n += rule.size_of(guard, ops);
51        }
52        n
53    }
54
55    /// Trivially construct a new set of CSS rules.
56    pub fn new(rules: Vec<CssRule>, shared_lock: &SharedRwLock) -> Arc<Locked<CssRules>> {
57        Arc::new(shared_lock.wrap(CssRules(rules)))
58    }
59
60    /// Returns whether all the rules in this list are namespace or import
61    /// rules.
62    fn only_ns_or_import(&self) -> bool {
63        self.0.iter().all(|r| match *r {
64            CssRule::Namespace(..) | CssRule::Import(..) => true,
65            _ => false,
66        })
67    }
68
69    /// <https://drafts.csswg.org/cssom/#remove-a-css-rule>
70    pub fn remove_rule(&mut self, index: usize) -> Result<(), RulesMutateError> {
71        // Step 1, 2
72        if index >= self.0.len() {
73            return Err(RulesMutateError::IndexSize);
74        }
75
76        {
77            // Step 3
78            let ref rule = self.0[index];
79
80            // Step 4
81            if let CssRule::Namespace(..) = *rule {
82                if !self.only_ns_or_import() {
83                    return Err(RulesMutateError::InvalidState);
84                }
85            }
86        }
87
88        // Step 5, 6
89        self.0.remove(index);
90        Ok(())
91    }
92
93    /// Serializes this CSSRules to CSS text as a block of rules.
94    ///
95    /// This should be speced into CSSOM spec at some point. See
96    /// <https://github.com/w3c/csswg-drafts/issues/1985>
97    pub fn to_css_block(
98        &self,
99        guard: &SharedRwLockReadGuard,
100        dest: &mut CssStringWriter,
101    ) -> fmt::Result {
102        dest.write_str(" {")?;
103        self.to_css_block_without_opening(guard, dest)
104    }
105
106    /// As above, but without the opening curly bracket. That's needed for nesting.
107    pub fn to_css_block_without_opening(
108        &self,
109        guard: &SharedRwLockReadGuard,
110        dest: &mut CssStringWriter,
111    ) -> fmt::Result {
112        for rule in self.0.iter() {
113            if rule.is_empty_nested_declarations(guard) {
114                continue;
115            }
116
117            dest.write_str("\n  ")?;
118            let old_len = dest.len();
119            rule.to_css(guard, dest)?;
120            debug_assert_ne!(old_len, dest.len());
121        }
122        dest.write_str("\n}")
123    }
124
125    /// Parses a rule for <https://drafts.csswg.org/cssom/#insert-a-css-rule>. Caller is
126    /// responsible for calling insert() afterwards.
127    ///
128    /// Written in this funky way because parsing an @import rule may cause us
129    /// to clone a stylesheet from the same document due to caching in the CSS
130    /// loader.
131    ///
132    /// TODO(emilio): We could also pass the write guard down into the loader
133    /// instead, but that seems overkill.
134    pub fn parse_rule_for_insert(
135        &self,
136        lock: &SharedRwLock,
137        rule: &str,
138        parent_stylesheet_contents: &StylesheetContents,
139        index: usize,
140        containing_rule_types: CssRuleTypes,
141        parse_relative_rule_type: Option<CssRuleType>,
142        loader: Option<&dyn StylesheetLoader>,
143        allow_import_rules: AllowImportRules,
144    ) -> Result<CssRule, RulesMutateError> {
145        // Step 1, 2
146        if index > self.0.len() {
147            return Err(RulesMutateError::IndexSize);
148        }
149
150        let insert_rule_context = InsertRuleContext {
151            rule_list: &self.0,
152            index,
153            containing_rule_types,
154            parse_relative_rule_type,
155        };
156
157        // Steps 3, 4, 5, 6
158        CssRule::parse(
159            &rule,
160            insert_rule_context,
161            parent_stylesheet_contents,
162            lock,
163            loader,
164            allow_import_rules,
165        )
166    }
167}