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