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, allow(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 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
90 pub(crate) fn new(
91 window: &Window,
92 parent_stylesheet: &CSSStyleSheet,
93 rules: RulesSource,
94 can_gc: CanGc,
95 ) -> DomRoot<CSSRuleList> {
96 reflect_dom_object(
97 Box::new(CSSRuleList::new_inherited(parent_stylesheet, rules)),
98 window,
99 can_gc,
100 )
101 }
102
103 pub(crate) fn insert_rule(
106 &self,
107 rule: &DOMString,
108 idx: u32,
109 containing_rule_types: CssRuleTypes,
110 parse_relative_rule_type: Option<CssRuleType>,
111 can_gc: CanGc,
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(window, parent_stylesheet, new_rule, can_gc);
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, idx: u32, can_gc: CanGc) -> 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 self.global().as_window(),
228 parent_stylesheet,
229 rule,
230 can_gc,
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 self.global().as_window(),
240 parent_stylesheet,
241 rule,
242 can_gc,
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, idx: u32, can_gc: CanGc) -> Option<DomRoot<CSSRule>> {
303 self.item(idx, can_gc)
304 }
305
306 fn Length(&self) -> u32 {
308 self.dom_rules.borrow().len() as u32
309 }
310
311 fn IndexedGetter(&self, index: u32, can_gc: CanGc) -> Option<DomRoot<CSSRule>> {
313 self.Item(index, can_gc)
314 }
315}