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