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