1use 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_with_cx};
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::dom::bindings::codegen::Bindings::CSSRuleListBinding::CSSRuleListMethods;
26use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
27use crate::dom::bindings::reflector::DomGlobal;
28use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
29use crate::dom::cssgroupingrule::CSSGroupingRule;
30use crate::dom::html::htmlelement::HTMLElement;
31use crate::dom::window::Window;
32use crate::stylesheet_loader::ElementStylesheetLoader;
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(None),
40 RulesMutateError::IndexSize => Error::IndexSize(None),
41 RulesMutateError::HierarchyRequest => Error::HierarchyRequest(None),
42 RulesMutateError::InvalidState => Error::InvalidState(None),
43 }
44 }
45}
46
47#[dom_struct]
48pub(crate) struct CSSRuleList {
49 reflector_: Reflector,
50 created_by_rule: Option<Dom<CSSGroupingRule>>,
51 parent_stylesheet: Dom<CSSStyleSheet>,
52 #[ignore_malloc_size_of = "Stylo"]
53 rules: RefCell<RulesSource>,
54 dom_rules: DomRefCell<Vec<MutNullableDom<CSSRule>>>,
55}
56
57pub(crate) enum RulesSource {
58 Rules(Arc<Locked<CssRules>>),
59 Keyframes(Arc<Locked<KeyframesRule>>),
60}
61
62impl CSSRuleList {
63 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
64 pub(crate) fn new_inherited(
65 created_by_rule: Option<&CSSGroupingRule>,
66 parent_stylesheet: &CSSStyleSheet,
67 rules: RulesSource,
68 ) -> CSSRuleList {
69 let guard = parent_stylesheet.shared_lock().read();
70 let dom_rules = match rules {
71 RulesSource::Rules(ref rules) => rules
72 .read_with(&guard)
73 .0
74 .iter()
75 .map(|_| MutNullableDom::new(None))
76 .collect(),
77 RulesSource::Keyframes(ref rules) => rules
78 .read_with(&guard)
79 .keyframes
80 .iter()
81 .map(|_| MutNullableDom::new(None))
82 .collect(),
83 };
84
85 CSSRuleList {
86 reflector_: Reflector::new(),
87 created_by_rule: created_by_rule.map(Dom::from_ref),
88 parent_stylesheet: Dom::from_ref(parent_stylesheet),
89 rules: RefCell::new(rules),
90 dom_rules: DomRefCell::new(dom_rules),
91 }
92 }
93
94 pub(crate) fn new(
95 cx: &mut JSContext,
96 window: &Window,
97 created_by_rule: Option<&CSSGroupingRule>,
98 parent_stylesheet: &CSSStyleSheet,
99 rules: RulesSource,
100 ) -> DomRoot<CSSRuleList> {
101 reflect_dom_object_with_cx(
102 Box::new(CSSRuleList::new_inherited(
103 created_by_rule,
104 parent_stylesheet,
105 rules,
106 )),
107 window,
108 cx,
109 )
110 }
111
112 pub(crate) fn insert_rule(
115 &self,
116 cx: &mut JSContext,
117 rule: &DOMString,
118 idx: u32,
119 ) -> Fallible<u32> {
120 self.parent_stylesheet.will_modify();
121 let css_rules = if let RulesSource::Rules(rules) = &*self.rules.borrow() {
122 rules.clone()
123 } else {
124 panic!("Called insert_rule on non-CssRule-backed CSSRuleList");
125 };
126
127 let global = self.global();
128 let window = global.as_window();
129 let index = idx as usize;
130
131 let parent_stylesheet = self.parent_stylesheet.style_stylesheet();
132 let owner = self
133 .parent_stylesheet
134 .owner_node()
135 .and_then(DomRoot::downcast::<HTMLElement>);
136 let loader = owner
137 .as_ref()
138 .map(|element| ElementStylesheetLoader::new(element));
139 let allow_import_rules = if self.parent_stylesheet.is_constructed() {
140 AllowImportRules::No
141 } else {
142 AllowImportRules::Yes
143 };
144
145 let mut containing_rule_types = CssRuleTypes::default();
146 let mut current_rule = self
147 .created_by_rule
148 .as_ref()
149 .map(|rule| rule.upcast::<CSSRule>())
150 .map(DomRoot::from_ref);
151 while let Some(containing_rule) = current_rule {
152 containing_rule_types.insert(containing_rule.rule_type());
153
154 current_rule = containing_rule
155 .parent_rule()
156 .map(|rule| rule.upcast::<CSSRule>())
157 .map(DomRoot::from_ref);
158 }
159
160 let new_rule = {
161 let guard = parent_stylesheet.shared_lock.read();
162 css_rules
163 .read_with(&guard)
164 .parse_rule_for_insert(
165 &parent_stylesheet.shared_lock,
166 &rule.str(),
167 parent_stylesheet.contents(&guard),
168 index,
169 containing_rule_types,
170 self.created_by_rule
171 .as_ref()
172 .map(|rule| rule.upcast::<CSSRule>().rule_type()),
173 loader.as_ref().map(|l| l as &dyn StyleStylesheetLoader),
174 allow_import_rules,
175 )
176 .map_err(Convert::convert)?
177 };
178 {
179 let mut guard = parent_stylesheet.shared_lock.write();
180 css_rules
181 .write_with(&mut guard)
182 .0
183 .insert(index, new_rule.clone());
184 }
185
186 let parent_stylesheet = &*self.parent_stylesheet;
187 parent_stylesheet.will_modify();
188
189 let dom_rule = CSSRule::new_specific(
190 cx,
191 window,
192 self.created_by_rule.as_deref(),
193 parent_stylesheet,
194 new_rule,
195 );
196 self.dom_rules
197 .safe_borrow_mut(cx.no_gc())
198 .insert(index, MutNullableDom::new(Some(&*dom_rule)));
199 parent_stylesheet.notify_invalidations();
200 Ok(idx)
201 }
202
203 pub(crate) fn remove_rule(&self, cx: &mut JSContext, index: u32) -> ErrorResult {
205 self.parent_stylesheet.will_modify();
206
207 let index = index as usize;
208 let mut guard = self.parent_stylesheet.shared_lock().write();
209
210 match *self.rules.borrow() {
211 RulesSource::Rules(ref css_rules) => {
212 css_rules
213 .write_with(&mut guard)
214 .remove_rule(index)
215 .map_err(Convert::convert)?;
216 let mut dom_rules = self.dom_rules.safe_borrow_mut(cx.no_gc());
217 if let Some(r) = dom_rules[index].get() {
218 r.detach()
219 }
220 dom_rules.remove(index);
221 self.parent_stylesheet.notify_invalidations();
222 Ok(())
223 },
224 RulesSource::Keyframes(ref kf) => {
225 let mut dom_rules = self.dom_rules.safe_borrow_mut(cx.no_gc());
227 if let Some(r) = dom_rules[index].get() {
228 r.detach()
229 }
230 dom_rules.remove(index);
231 kf.write_with(&mut guard).keyframes.remove(index);
232 self.parent_stylesheet.notify_invalidations();
233 Ok(())
234 },
235 }
236 }
237
238 pub(crate) fn deparent_all(&self) {
240 for rule in self.dom_rules.borrow().iter() {
241 if let Some(r) = rule.get() {
242 DomRoot::upcast(r).deparent()
243 }
244 }
245 }
246
247 pub(crate) fn item(&self, cx: &mut JSContext, idx: u32) -> Option<DomRoot<CSSRule>> {
248 self.dom_rules.borrow().get(idx as usize).map(|rule| {
249 rule.or_init(|| {
250 let parent_stylesheet = &self.parent_stylesheet;
251 let lock = parent_stylesheet.shared_lock();
252 match *self.rules.borrow() {
253 RulesSource::Rules(ref rules) => {
254 let rule = {
255 let guard = lock.read();
256 rules.read_with(&guard).0[idx as usize].clone()
257 };
258 CSSRule::new_specific(
259 cx,
260 self.global().as_window(),
261 self.created_by_rule.as_deref(),
262 parent_stylesheet,
263 rule,
264 )
265 },
266 RulesSource::Keyframes(ref rules) => {
267 let rule = {
268 let guard = lock.read();
269 rules.read_with(&guard).keyframes[idx as usize].clone()
270 };
271 DomRoot::upcast(CSSKeyframeRule::new(
272 cx,
273 self.global().as_window(),
274 self.created_by_rule.as_deref(),
275 parent_stylesheet,
276 rule,
277 ))
278 },
279 }
280 })
281 })
282 }
283
284 pub(crate) fn append_lazy_dom_rule(&self) {
290 if let RulesSource::Rules(..) = &*self.rules.borrow() {
291 panic!("Can only call append_lazy_rule with keyframes-backed CSSRules");
292 }
293 self.dom_rules.borrow_mut().push(MutNullableDom::new(None));
294 }
295
296 pub(super) fn update_rules(&self, rules: RulesSource, guard: &SharedRwLockReadGuard) {
297 let dom_rules = self.dom_rules.borrow();
298 match rules {
299 RulesSource::Rules(ref css_rules) => {
300 if let RulesSource::Keyframes(..) = &*self.rules.borrow() {
301 panic!("Called update_rules on non-CssRule-backed CSSRuleList with CssRules");
302 }
303
304 let css_rules_iter = css_rules.read_with(guard).0.iter();
305 for (dom_rule, css_rule) in izip!(dom_rules.iter(), css_rules_iter) {
306 let Some(dom_rule) = dom_rule.get() else {
307 continue;
308 };
309 dom_rule.update_rule(css_rule, guard);
310 }
311 },
312 RulesSource::Keyframes(ref keyframesrule) => {
313 if let RulesSource::Rules(..) = &*self.rules.borrow() {
314 panic!("Called update_rules on CssRule-backed CSSRuleList with non-CssRules");
315 }
316
317 let keyframerules_iter = keyframesrule.read_with(guard).keyframes.iter();
318 for (dom_rule, keyframerule) in izip!(dom_rules.iter(), keyframerules_iter) {
319 let Some(dom_rule) = dom_rule.get() else {
320 continue;
321 };
322 let Some(dom_rule) = dom_rule.downcast::<CSSKeyframeRule>() else {
323 continue;
324 };
325 dom_rule.update_rule(keyframerule.clone(), guard);
326 }
327 },
328 }
329
330 *self.rules.borrow_mut() = rules;
331 }
332}
333
334impl CSSRuleListMethods<crate::DomTypeHolder> for CSSRuleList {
335 fn Item(&self, cx: &mut JSContext, idx: u32) -> Option<DomRoot<CSSRule>> {
337 self.item(cx, idx)
338 }
339
340 fn Length(&self) -> u32 {
342 self.dom_rules.borrow().len() as u32
343 }
344
345 fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<CSSRule>> {
347 self.Item(cx, index)
348 }
349}