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, CssRulesHelpers, KeyframesRule,
17 RulesMutateError, StylesheetLoader as StyleStylesheetLoader,
18};
19
20use crate::conversions::Convert;
21use crate::dom::bindings::cell::DomRefCell;
22use crate::dom::bindings::codegen::Bindings::CSSRuleListBinding::CSSRuleListMethods;
23use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
24use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
25use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
26use crate::dom::csskeyframerule::CSSKeyframeRule;
27use crate::dom::cssrule::CSSRule;
28use crate::dom::cssstylesheet::CSSStyleSheet;
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 = css_rules
140 .insert_rule(
141 &parent_stylesheet.shared_lock,
142 &rule.str(),
143 &parent_stylesheet.contents,
144 index,
145 containing_rule_types,
146 parse_relative_rule_type,
147 loader.as_ref().map(|l| l as &dyn StyleStylesheetLoader),
148 allow_import_rules,
149 )
150 .map_err(Convert::convert)?;
151
152 let parent_stylesheet = &*self.parent_stylesheet;
153 parent_stylesheet.will_modify();
154 let dom_rule = CSSRule::new_specific(window, parent_stylesheet, new_rule, can_gc);
155 self.dom_rules
156 .borrow_mut()
157 .insert(index, MutNullableDom::new(Some(&*dom_rule)));
158 parent_stylesheet.notify_invalidations();
159 Ok(idx)
160 }
161
162 pub(crate) fn remove_rule(&self, index: u32) -> ErrorResult {
164 self.parent_stylesheet.will_modify();
165
166 let index = index as usize;
167 let mut guard = self.parent_stylesheet.shared_lock().write();
168
169 match *self.rules.borrow() {
170 RulesSource::Rules(ref css_rules) => {
171 css_rules
172 .write_with(&mut guard)
173 .remove_rule(index)
174 .map_err(Convert::convert)?;
175 let mut dom_rules = self.dom_rules.borrow_mut();
176 if let Some(r) = dom_rules[index].get() {
177 r.detach()
178 }
179 dom_rules.remove(index);
180 self.parent_stylesheet.notify_invalidations();
181 Ok(())
182 },
183 RulesSource::Keyframes(ref kf) => {
184 let mut dom_rules = self.dom_rules.borrow_mut();
186 if let Some(r) = dom_rules[index].get() {
187 r.detach()
188 }
189 dom_rules.remove(index);
190 kf.write_with(&mut guard).keyframes.remove(index);
191 self.parent_stylesheet.notify_invalidations();
192 Ok(())
193 },
194 }
195 }
196
197 pub(crate) fn deparent_all(&self) {
199 for rule in self.dom_rules.borrow().iter() {
200 if let Some(r) = rule.get() {
201 DomRoot::upcast(r).deparent()
202 }
203 }
204 }
205
206 pub(crate) fn item(&self, idx: u32, can_gc: CanGc) -> Option<DomRoot<CSSRule>> {
207 self.dom_rules.borrow().get(idx as usize).map(|rule| {
208 rule.or_init(|| {
209 let parent_stylesheet = &self.parent_stylesheet;
210 let lock = parent_stylesheet.shared_lock();
211 match *self.rules.borrow() {
212 RulesSource::Rules(ref rules) => {
213 let rule = {
214 let guard = lock.read();
215 rules.read_with(&guard).0[idx as usize].clone()
216 };
217 CSSRule::new_specific(
218 self.global().as_window(),
219 parent_stylesheet,
220 rule,
221 can_gc,
222 )
223 },
224 RulesSource::Keyframes(ref rules) => {
225 let rule = {
226 let guard = lock.read();
227 rules.read_with(&guard).keyframes[idx as usize].clone()
228 };
229 DomRoot::upcast(CSSKeyframeRule::new(
230 self.global().as_window(),
231 parent_stylesheet,
232 rule,
233 can_gc,
234 ))
235 },
236 }
237 })
238 })
239 }
240
241 pub(crate) fn append_lazy_dom_rule(&self) {
247 if let RulesSource::Rules(..) = &*self.rules.borrow() {
248 panic!("Can only call append_lazy_rule with keyframes-backed CSSRules");
249 }
250 self.dom_rules.borrow_mut().push(MutNullableDom::new(None));
251 }
252
253 pub(super) fn update_rules(&self, rules: RulesSource, guard: &SharedRwLockReadGuard) {
254 let dom_rules = self.dom_rules.borrow();
255 match rules {
256 RulesSource::Rules(ref css_rules) => {
257 if let RulesSource::Keyframes(..) = &*self.rules.borrow() {
258 panic!("Called update_rules on non-CssRule-backed CSSRuleList with CssRules");
259 }
260
261 let css_rules_iter = css_rules.read_with(guard).0.iter();
262 for (dom_rule, css_rule) in izip!(dom_rules.iter(), css_rules_iter) {
263 let Some(dom_rule) = dom_rule.get() else {
264 continue;
265 };
266 dom_rule.update_rule(css_rule, guard);
267 }
268 },
269 RulesSource::Keyframes(ref keyframesrule) => {
270 if let RulesSource::Rules(..) = &*self.rules.borrow() {
271 panic!("Called update_rules on CssRule-backed CSSRuleList with non-CssRules");
272 }
273
274 let keyframerules_iter = keyframesrule.read_with(guard).keyframes.iter();
275 for (dom_rule, keyframerule) in izip!(dom_rules.iter(), keyframerules_iter) {
276 let Some(dom_rule) = dom_rule.get() else {
277 continue;
278 };
279 let Some(dom_rule) = dom_rule.downcast::<CSSKeyframeRule>() else {
280 continue;
281 };
282 dom_rule.update_rule(keyframerule.clone(), guard);
283 }
284 },
285 }
286
287 *self.rules.borrow_mut() = rules;
288 }
289}
290
291impl CSSRuleListMethods<crate::DomTypeHolder> for CSSRuleList {
292 fn Item(&self, idx: u32, can_gc: CanGc) -> Option<DomRoot<CSSRule>> {
294 self.item(idx, can_gc)
295 }
296
297 fn Length(&self) -> u32 {
299 self.dom_rules.borrow().len() as u32
300 }
301
302 fn IndexedGetter(&self, index: u32, can_gc: CanGc) -> Option<DomRoot<CSSRule>> {
304 self.Item(index, can_gc)
305 }
306}