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