Skip to main content

script/dom/css/
cssrulelist.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
5use std::cell::RefCell;
6
7use dom_struct::dom_struct;
8use itertools::izip;
9use js::context::JSContext;
10use script_bindings::cell::DomRefCell;
11use script_bindings::inheritance::Castable;
12use script_bindings::reflector::{Reflector, reflect_dom_object};
13use script_bindings::str::DOMString;
14use servo_arc::Arc;
15use style::shared_lock::{Locked, SharedRwLockReadGuard};
16use style::stylesheets::{
17    AllowImportRules, CssRuleTypes, CssRules, KeyframesRule, RulesMutateError,
18    StylesheetInDocument, StylesheetLoader as StyleStylesheetLoader,
19};
20
21use super::csskeyframerule::CSSKeyframeRule;
22use super::cssrule::CSSRule;
23use super::cssstylesheet::CSSStyleSheet;
24use crate::conversions::Convert;
25use crate::css::stylesheet_loader::ElementStylesheetLoader;
26use crate::dom::bindings::codegen::Bindings::CSSRuleListBinding::CSSRuleListMethods;
27use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
28use crate::dom::bindings::reflector::DomGlobal;
29use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
30use crate::dom::cssgroupingrule::CSSGroupingRule;
31use crate::dom::html::htmlelement::HTMLElement;
32use crate::dom::window::Window;
33
34unsafe_no_jsmanaged_fields!(RulesSource);
35
36impl Convert<Error> for RulesMutateError {
37    fn convert(self) -> Error {
38        match self {
39            RulesMutateError::Syntax => Error::Syntax(Some("Invalid CSS syntax".into())),
40            RulesMutateError::IndexSize => Error::IndexSize(Some("Index is out of bounds".into())),
41            RulesMutateError::HierarchyRequest => Error::HierarchyRequest(Some(
42                "This request would result in an incorrect node tree".into(),
43            )),
44            RulesMutateError::InvalidState => {
45                Error::InvalidState(Some("CSS rule is invalid".into()))
46            },
47        }
48    }
49}
50
51#[dom_struct]
52pub(crate) struct CSSRuleList {
53    reflector_: Reflector,
54    created_by_rule: Option<Dom<CSSGroupingRule>>,
55    parent_stylesheet: Dom<CSSStyleSheet>,
56    #[ignore_malloc_size_of = "Stylo"]
57    rules: RefCell<RulesSource>,
58    dom_rules: DomRefCell<Vec<MutNullableDom<CSSRule>>>,
59}
60
61pub(crate) enum RulesSource {
62    Rules(Arc<Locked<CssRules>>),
63    Keyframes(Arc<Locked<KeyframesRule>>),
64}
65
66impl CSSRuleList {
67    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
68    pub(crate) fn new_inherited(
69        created_by_rule: Option<&CSSGroupingRule>,
70        parent_stylesheet: &CSSStyleSheet,
71        rules: RulesSource,
72    ) -> CSSRuleList {
73        let guard = parent_stylesheet.shared_lock().read();
74        let dom_rules = match rules {
75            RulesSource::Rules(ref rules) => rules
76                .read_with(&guard)
77                .0
78                .iter()
79                .map(|_| MutNullableDom::new(None))
80                .collect(),
81            RulesSource::Keyframes(ref rules) => rules
82                .read_with(&guard)
83                .keyframes
84                .iter()
85                .map(|_| MutNullableDom::new(None))
86                .collect(),
87        };
88
89        CSSRuleList {
90            reflector_: Reflector::new(),
91            created_by_rule: created_by_rule.map(Dom::from_ref),
92            parent_stylesheet: Dom::from_ref(parent_stylesheet),
93            rules: RefCell::new(rules),
94            dom_rules: DomRefCell::new(dom_rules),
95        }
96    }
97
98    pub(crate) fn new(
99        cx: &mut JSContext,
100        window: &Window,
101        created_by_rule: Option<&CSSGroupingRule>,
102        parent_stylesheet: &CSSStyleSheet,
103        rules: RulesSource,
104    ) -> DomRoot<CSSRuleList> {
105        reflect_dom_object(
106            cx,
107            Box::new(CSSRuleList::new_inherited(
108                created_by_rule,
109                parent_stylesheet,
110                rules,
111            )),
112            window,
113        )
114    }
115
116    /// Should only be called for CssRules-backed rules. Use append_lazy_rule
117    /// for keyframes-backed rules.
118    pub(crate) fn insert_rule(
119        &self,
120        cx: &mut JSContext,
121        rule: &DOMString,
122        idx: u32,
123    ) -> Fallible<u32> {
124        self.parent_stylesheet.will_modify(cx.no_gc());
125        let css_rules = if let RulesSource::Rules(rules) = &*self.rules.borrow() {
126            rules.clone()
127        } else {
128            panic!("Called insert_rule on non-CssRule-backed CSSRuleList");
129        };
130
131        let global = self.global();
132        let window = global.as_window();
133        let index = idx as usize;
134
135        let parent_stylesheet = self.parent_stylesheet.style_stylesheet();
136        let owner = self
137            .parent_stylesheet
138            .owner_node()
139            .and_then(DomRoot::downcast::<HTMLElement>);
140        let loader = owner
141            .as_ref()
142            .map(|element| ElementStylesheetLoader::new(element));
143        let allow_import_rules = if self.parent_stylesheet.is_constructed() {
144            AllowImportRules::No
145        } else {
146            AllowImportRules::Yes
147        };
148
149        let mut containing_rule_types = CssRuleTypes::default();
150        let mut current_rule = self
151            .created_by_rule
152            .as_ref()
153            .map(|rule| rule.upcast::<CSSRule>())
154            .map(DomRoot::from_ref);
155        while let Some(containing_rule) = current_rule {
156            containing_rule_types.insert(containing_rule.rule_type());
157
158            current_rule = containing_rule
159                .parent_rule()
160                .map(|rule| rule.upcast::<CSSRule>())
161                .map(DomRoot::from_ref);
162        }
163
164        let new_rule = {
165            let guard = parent_stylesheet.shared_lock.read();
166            css_rules
167                .read_with(&guard)
168                .parse_rule_for_insert(
169                    &parent_stylesheet.shared_lock,
170                    &rule.str(),
171                    parent_stylesheet.contents(&guard),
172                    index,
173                    containing_rule_types,
174                    self.created_by_rule
175                        .as_ref()
176                        .map(|rule| rule.upcast::<CSSRule>().rule_type()),
177                    loader.as_ref().map(|l| l as &dyn StyleStylesheetLoader),
178                    allow_import_rules,
179                )
180                .map_err(Convert::convert)?
181        };
182        {
183            let mut guard = parent_stylesheet.shared_lock.write();
184            css_rules
185                .write_with(&mut guard)
186                .0
187                .insert(index, new_rule.clone());
188        }
189
190        let parent_stylesheet = &*self.parent_stylesheet;
191        parent_stylesheet.will_modify(cx.no_gc());
192
193        let dom_rule = CSSRule::new_specific(
194            cx,
195            window,
196            self.created_by_rule.as_deref(),
197            parent_stylesheet,
198            new_rule,
199        );
200        self.dom_rules
201            .safe_borrow_mut(cx.no_gc())
202            .insert(index, MutNullableDom::new(Some(&*dom_rule)));
203        parent_stylesheet.notify_invalidations(cx.no_gc());
204        Ok(idx)
205    }
206
207    /// In case of a keyframe rule, index must be valid.
208    pub(crate) fn remove_rule(&self, cx: &mut JSContext, index: u32) -> ErrorResult {
209        self.parent_stylesheet.will_modify(cx.no_gc());
210
211        let index = index as usize;
212        let mut guard = self.parent_stylesheet.shared_lock().write();
213
214        match *self.rules.borrow() {
215            RulesSource::Rules(ref css_rules) => {
216                css_rules
217                    .write_with(&mut guard)
218                    .remove_rule(index)
219                    .map_err(Convert::convert)?;
220                let mut dom_rules = self.dom_rules.safe_borrow_mut(cx.no_gc());
221                if let Some(r) = dom_rules[index].get() {
222                    r.detach()
223                }
224                dom_rules.remove(index);
225                self.parent_stylesheet.notify_invalidations(cx.no_gc());
226                Ok(())
227            },
228            RulesSource::Keyframes(ref kf) => {
229                // https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-deleterule
230                let mut dom_rules = self.dom_rules.safe_borrow_mut(cx.no_gc());
231                if let Some(r) = dom_rules[index].get() {
232                    r.detach()
233                }
234                dom_rules.remove(index);
235                kf.write_with(&mut guard).keyframes.remove(index);
236                self.parent_stylesheet.notify_invalidations(cx.no_gc());
237                Ok(())
238            },
239        }
240    }
241
242    /// Remove parent stylesheets from all children
243    pub(crate) fn deparent_all(&self) {
244        for rule in self.dom_rules.borrow().iter() {
245            if let Some(r) = rule.get() {
246                DomRoot::upcast(r).deparent()
247            }
248        }
249    }
250
251    pub(crate) fn item(&self, cx: &mut JSContext, idx: u32) -> Option<DomRoot<CSSRule>> {
252        self.dom_rules.borrow().get(idx as usize).map(|rule| {
253            rule.or_init(|| {
254                let parent_stylesheet = &self.parent_stylesheet;
255                let lock = parent_stylesheet.shared_lock();
256                match *self.rules.borrow() {
257                    RulesSource::Rules(ref rules) => {
258                        let rule = {
259                            let guard = lock.read();
260                            rules.read_with(&guard).0[idx as usize].clone()
261                        };
262                        CSSRule::new_specific(
263                            cx,
264                            self.global().as_window(),
265                            self.created_by_rule.as_deref(),
266                            parent_stylesheet,
267                            rule,
268                        )
269                    },
270                    RulesSource::Keyframes(ref rules) => {
271                        let rule = {
272                            let guard = lock.read();
273                            rules.read_with(&guard).keyframes[idx as usize].clone()
274                        };
275                        DomRoot::upcast(CSSKeyframeRule::new(
276                            cx,
277                            self.global().as_window(),
278                            self.created_by_rule.as_deref(),
279                            parent_stylesheet,
280                            rule,
281                        ))
282                    },
283                }
284            })
285        })
286    }
287
288    /// Add a rule to the list of DOM rules. This list is lazy,
289    /// so we just append a placeholder.
290    ///
291    /// Should only be called for keyframes-backed rules, use insert_rule
292    /// for CssRules-backed rules
293    pub(crate) fn append_lazy_dom_rule(&self) {
294        if let RulesSource::Rules(..) = &*self.rules.borrow() {
295            panic!("Can only call append_lazy_rule with keyframes-backed CSSRules");
296        }
297        self.dom_rules.borrow_mut().push(MutNullableDom::new(None));
298    }
299
300    pub(super) fn update_rules(&self, rules: RulesSource, guard: &SharedRwLockReadGuard) {
301        let dom_rules = self.dom_rules.borrow();
302        match rules {
303            RulesSource::Rules(ref css_rules) => {
304                if let RulesSource::Keyframes(..) = &*self.rules.borrow() {
305                    panic!("Called update_rules on non-CssRule-backed CSSRuleList with CssRules");
306                }
307
308                let css_rules_iter = css_rules.read_with(guard).0.iter();
309                for (dom_rule, css_rule) in izip!(dom_rules.iter(), css_rules_iter) {
310                    let Some(dom_rule) = dom_rule.get() else {
311                        continue;
312                    };
313                    dom_rule.update_rule(css_rule, guard);
314                }
315            },
316            RulesSource::Keyframes(ref keyframesrule) => {
317                if let RulesSource::Rules(..) = &*self.rules.borrow() {
318                    panic!("Called update_rules on CssRule-backed CSSRuleList with non-CssRules");
319                }
320
321                let keyframerules_iter = keyframesrule.read_with(guard).keyframes.iter();
322                for (dom_rule, keyframerule) in izip!(dom_rules.iter(), keyframerules_iter) {
323                    let Some(dom_rule) = dom_rule.get() else {
324                        continue;
325                    };
326                    let Some(dom_rule) = dom_rule.downcast::<CSSKeyframeRule>() else {
327                        continue;
328                    };
329                    dom_rule.update_rule(keyframerule.clone(), guard);
330                }
331            },
332        }
333
334        *self.rules.borrow_mut() = rules;
335    }
336}
337
338impl CSSRuleListMethods<crate::DomTypeHolder> for CSSRuleList {
339    /// <https://drafts.csswg.org/cssom/#ref-for-dom-cssrulelist-item-1>
340    fn Item(&self, cx: &mut JSContext, idx: u32) -> Option<DomRoot<CSSRule>> {
341        self.item(cx, idx)
342    }
343
344    /// <https://drafts.csswg.org/cssom/#dom-cssrulelist-length>
345    fn Length(&self) -> u32 {
346        self.dom_rules.borrow().len() as u32
347    }
348
349    // check-tidy: no specs after this line
350    fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<CSSRule>> {
351        self.Item(cx, index)
352    }
353}