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::stylesheets::loader::StylesheetLoader;
10use crate::stylesheets::rule_parser::InsertRuleContext;
11use crate::stylesheets::stylesheet::StylesheetContents;
12use crate::stylesheets::{AllowImportRules, CssRule, CssRuleTypes, RulesMutateError};
13#[cfg(feature = "gecko")]
14use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps};
15use servo_arc::Arc;
16use std::fmt::{self, Write};
17use style_traits::CssStringWriter;
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    /// Parses a rule for <https://drafts.csswg.org/cssom/#insert-a-css-rule>. Caller is
125    /// responsible for calling insert() afterwards.
126    ///
127    /// Written in this funky way because parsing an @import rule may cause us
128    /// to clone a stylesheet from the same document due to caching in the CSS
129    /// loader.
130    ///
131    /// TODO(emilio): We could also pass the write guard down into the loader
132    /// instead, but that seems overkill.
133    pub fn parse_rule_for_insert(
134        &self,
135        lock: &SharedRwLock,
136        rule: &str,
137        parent_stylesheet_contents: &StylesheetContents,
138        index: usize,
139        containing_rule_types: CssRuleTypes,
140        parse_relative_rule_type: Option<CssRuleType>,
141        loader: Option<&dyn StylesheetLoader>,
142        allow_import_rules: AllowImportRules,
143    ) -> Result<CssRule, RulesMutateError> {
144        // Step 1, 2
145        if index > self.0.len() {
146            return Err(RulesMutateError::IndexSize);
147        }
148
149        let insert_rule_context = InsertRuleContext {
150            rule_list: &self.0,
151            index,
152            containing_rule_types,
153            parse_relative_rule_type,
154        };
155
156        // Steps 3, 4, 5, 6
157        CssRule::parse(
158            &rule,
159            insert_rule_context,
160            parent_stylesheet_contents,
161            lock,
162            loader,
163            allow_import_rules,
164        )
165    }
166}