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, CssRuleType, 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::html::htmlelement::HTMLElement;
30use crate::dom::window::Window;
31use crate::stylesheet_loader::ElementStylesheetLoader;
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(None),
39 RulesMutateError::IndexSize => Error::IndexSize(None),
40 RulesMutateError::HierarchyRequest => Error::HierarchyRequest(None),
41 RulesMutateError::InvalidState => Error::InvalidState(None),
42 }
43 }
44}
45
46#[dom_struct]
47pub(crate) struct CSSRuleList {
48 reflector_: Reflector,
49 parent_stylesheet: Dom<CSSStyleSheet>,
50 #[ignore_malloc_size_of = "Stylo"]
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, expect(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 pub(crate) fn new(
91 cx: &mut JSContext,
92 window: &Window,
93 parent_stylesheet: &CSSStyleSheet,
94 rules: RulesSource,
95 ) -> DomRoot<CSSRuleList> {
96 reflect_dom_object_with_cx(
97 Box::new(CSSRuleList::new_inherited(parent_stylesheet, rules)),
98 window,
99 cx,
100 )
101 }
102
103 pub(crate) fn insert_rule(
106 &self,
107 cx: &mut JSContext,
108 rule: &DOMString,
109 idx: u32,
110 containing_rule_types: CssRuleTypes,
111 parse_relative_rule_type: Option<CssRuleType>,
112 ) -> Fallible<u32> {
113 self.parent_stylesheet.will_modify();
114 let css_rules = if let RulesSource::Rules(rules) = &*self.rules.borrow() {
115 rules.clone()
116 } else {
117 panic!("Called insert_rule on non-CssRule-backed CSSRuleList");
118 };
119
120 let global = self.global();
121 let window = global.as_window();
122 let index = idx as usize;
123
124 let parent_stylesheet = self.parent_stylesheet.style_stylesheet();
125 let owner = self
126 .parent_stylesheet
127 .owner_node()
128 .and_then(DomRoot::downcast::<HTMLElement>);
129 let loader = owner
130 .as_ref()
131 .map(|element| ElementStylesheetLoader::new(element));
132 let allow_import_rules = if self.parent_stylesheet.is_constructed() {
133 AllowImportRules::No
134 } else {
135 AllowImportRules::Yes
136 };
137 let new_rule = {
138 let guard = parent_stylesheet.shared_lock.read();
139 css_rules
140 .read_with(&guard)
141 .parse_rule_for_insert(
142 &parent_stylesheet.shared_lock,
143 &rule.str(),
144 parent_stylesheet.contents(&guard),
145 index,
146 containing_rule_types,
147 parse_relative_rule_type,
148 loader.as_ref().map(|l| l as &dyn StyleStylesheetLoader),
149 allow_import_rules,
150 )
151 .map_err(Convert::convert)?
152 };
153 {
154 let mut guard = parent_stylesheet.shared_lock.write();
155 css_rules
156 .write_with(&mut guard)
157 .0
158 .insert(index, new_rule.clone());
159 }
160
161 let parent_stylesheet = &*self.parent_stylesheet;
162 parent_stylesheet.will_modify();
163 let dom_rule = CSSRule::new_specific(cx, window, parent_stylesheet, new_rule);
164 self.dom_rules
165 .borrow_mut()
166 .insert(index, MutNullableDom::new(Some(&*dom_rule)));
167 parent_stylesheet.notify_invalidations();
168 Ok(idx)
169 }
170
171 pub(crate) fn remove_rule(&self, index: u32) -> ErrorResult {
173 self.parent_stylesheet.will_modify();
174
175 let index = index as usize;
176 let mut guard = self.parent_stylesheet.shared_lock().write();
177
178 match *self.rules.borrow() {
179 RulesSource::Rules(ref css_rules) => {
180 css_rules
181 .write_with(&mut guard)
182 .remove_rule(index)
183 .map_err(Convert::convert)?;
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 self.parent_stylesheet.notify_invalidations();
190 Ok(())
191 },
192 RulesSource::Keyframes(ref kf) => {
193 let mut dom_rules = self.dom_rules.borrow_mut();
195 if let Some(r) = dom_rules[index].get() {
196 r.detach()
197 }
198 dom_rules.remove(index);
199 kf.write_with(&mut guard).keyframes.remove(index);
200 self.parent_stylesheet.notify_invalidations();
201 Ok(())
202 },
203 }
204 }
205
206 pub(crate) fn deparent_all(&self) {
208 for rule in self.dom_rules.borrow().iter() {
209 if let Some(r) = rule.get() {
210 DomRoot::upcast(r).deparent()
211 }
212 }
213 }
214
215 pub(crate) fn item(&self, cx: &mut JSContext, idx: u32) -> Option<DomRoot<CSSRule>> {
216 self.dom_rules.borrow().get(idx as usize).map(|rule| {
217 rule.or_init(|| {
218 let parent_stylesheet = &self.parent_stylesheet;
219 let lock = parent_stylesheet.shared_lock();
220 match *self.rules.borrow() {
221 RulesSource::Rules(ref rules) => {
222 let rule = {
223 let guard = lock.read();
224 rules.read_with(&guard).0[idx as usize].clone()
225 };
226 CSSRule::new_specific(
227 cx,
228 self.global().as_window(),
229 parent_stylesheet,
230 rule,
231 )
232 },
233 RulesSource::Keyframes(ref rules) => {
234 let rule = {
235 let guard = lock.read();
236 rules.read_with(&guard).keyframes[idx as usize].clone()
237 };
238 DomRoot::upcast(CSSKeyframeRule::new(
239 cx,
240 self.global().as_window(),
241 parent_stylesheet,
242 rule,
243 ))
244 },
245 }
246 })
247 })
248 }
249
250 pub(crate) fn append_lazy_dom_rule(&self) {
256 if let RulesSource::Rules(..) = &*self.rules.borrow() {
257 panic!("Can only call append_lazy_rule with keyframes-backed CSSRules");
258 }
259 self.dom_rules.borrow_mut().push(MutNullableDom::new(None));
260 }
261
262 pub(super) fn update_rules(&self, rules: RulesSource, guard: &SharedRwLockReadGuard) {
263 let dom_rules = self.dom_rules.borrow();
264 match rules {
265 RulesSource::Rules(ref css_rules) => {
266 if let RulesSource::Keyframes(..) = &*self.rules.borrow() {
267 panic!("Called update_rules on non-CssRule-backed CSSRuleList with CssRules");
268 }
269
270 let css_rules_iter = css_rules.read_with(guard).0.iter();
271 for (dom_rule, css_rule) in izip!(dom_rules.iter(), css_rules_iter) {
272 let Some(dom_rule) = dom_rule.get() else {
273 continue;
274 };
275 dom_rule.update_rule(css_rule, guard);
276 }
277 },
278 RulesSource::Keyframes(ref keyframesrule) => {
279 if let RulesSource::Rules(..) = &*self.rules.borrow() {
280 panic!("Called update_rules on CssRule-backed CSSRuleList with non-CssRules");
281 }
282
283 let keyframerules_iter = keyframesrule.read_with(guard).keyframes.iter();
284 for (dom_rule, keyframerule) in izip!(dom_rules.iter(), keyframerules_iter) {
285 let Some(dom_rule) = dom_rule.get() else {
286 continue;
287 };
288 let Some(dom_rule) = dom_rule.downcast::<CSSKeyframeRule>() else {
289 continue;
290 };
291 dom_rule.update_rule(keyframerule.clone(), guard);
292 }
293 },
294 }
295
296 *self.rules.borrow_mut() = rules;
297 }
298}
299
300impl CSSRuleListMethods<crate::DomTypeHolder> for CSSRuleList {
301 fn Item(&self, cx: &mut JSContext, idx: u32) -> Option<DomRoot<CSSRule>> {
303 self.item(cx, idx)
304 }
305
306 fn Length(&self) -> u32 {
308 self.dom_rules.borrow().len() as u32
309 }
310
311 fn IndexedGetter(&self, cx: &mut JSContext, index: u32) -> Option<DomRoot<CSSRule>> {
313 self.Item(cx, index)
314 }
315}