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