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};
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 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 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 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 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 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 fn Item(&self, cx: &mut JSContext, idx: u32) -> Option<DomRoot<CSSRule>> {
341 self.item(cx, idx)
342 }
343
344 fn Length(&self) -> u32 {
346 self.dom_rules.borrow().len() as u32
347 }
348
349 fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<CSSRule>> {
351 self.Item(cx, index)
352 }
353}