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