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