1use std::cell::RefCell;
6use std::cmp::Ordering;
7use std::sync::LazyLock;
8
9use dom_struct::dom_struct;
10use html5ever::local_name;
11use servo_arc::Arc;
12use servo_url::ServoUrl;
13use style::attr::AttrValue;
14use style::properties::{
15 Importance, LonghandId, PropertyDeclarationBlock, PropertyId, ShorthandId,
16 SourcePropertyDeclaration, parse_one_declaration_into, parse_style_attribute,
17};
18use style::selector_parser::PseudoElement;
19use style::shared_lock::Locked;
20use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
21use style_traits::ParsingMode;
22
23use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
24use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
25use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
26use crate::dom::bindings::inheritance::Castable;
27use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
28use crate::dom::bindings::root::{Dom, DomRoot};
29use crate::dom::bindings::str::DOMString;
30use crate::dom::cssrule::CSSRule;
31use crate::dom::element::Element;
32use crate::dom::node::{Node, NodeTraits};
33use crate::dom::window::Window;
34use crate::script_runtime::CanGc;
35
36#[dom_struct]
38pub(crate) struct CSSStyleDeclaration {
39 reflector_: Reflector,
40 owner: CSSStyleOwner,
41 readonly: bool,
42 #[no_trace]
43 pseudo: Option<PseudoElement>,
44}
45
46#[derive(JSTraceable, MallocSizeOf)]
47#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
48pub(crate) enum CSSStyleOwner {
49 Null,
52 Element(Dom<Element>),
53 CSSRule(
54 Dom<CSSRule>,
55 #[ignore_malloc_size_of = "Arc"]
56 #[no_trace]
57 RefCell<Arc<Locked<PropertyDeclarationBlock>>>,
58 ),
59}
60
61impl CSSStyleOwner {
62 fn mutate_associated_block<F, R>(&self, f: F, can_gc: CanGc) -> R
65 where
66 F: FnOnce(&mut PropertyDeclarationBlock, &mut bool) -> R,
67 {
68 let mut changed = true;
72 match *self {
73 CSSStyleOwner::Null => unreachable!(
74 "CSSStyleDeclaration should always be read-only when CSSStyleOwner is Null"
75 ),
76 CSSStyleOwner::Element(ref el) => {
77 let document = el.owner_document();
78 let shared_lock = document.style_shared_lock();
79 let mut attr = el.style_attribute().borrow_mut().take();
80 let result = if let Some(lock) = attr.as_ref() {
81 let mut guard = shared_lock.write();
82 let pdb = lock.write_with(&mut guard);
83 f(pdb, &mut changed)
84 } else {
85 let mut pdb = PropertyDeclarationBlock::new();
86 let result = f(&mut pdb, &mut changed);
87
88 changed = !pdb.declarations().is_empty();
91 if changed {
92 attr = Some(Arc::new(shared_lock.wrap(pdb)));
93 }
94
95 result
96 };
97
98 if changed {
99 if let Some(pdb) = attr {
106 let guard = shared_lock.read();
107 let mut serialization = String::new();
108 pdb.read_with(&guard).to_css(&mut serialization).unwrap();
109 el.set_attribute(
110 &local_name!("style"),
111 AttrValue::Declaration(serialization, pdb),
112 can_gc,
113 );
114 }
115 } else {
116 *el.style_attribute().borrow_mut() = attr;
118 }
119
120 result
121 },
122 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
123 rule.parent_stylesheet().will_modify();
124 let result = {
125 let mut guard = rule.shared_lock().write();
126 f(&mut *pdb.borrow().write_with(&mut guard), &mut changed)
127 };
128 if changed {
129 rule.parent_stylesheet().notify_invalidations();
130 }
131 result
132 },
133 }
134 }
135
136 fn with_block<F, R>(&self, f: F) -> R
137 where
138 F: FnOnce(&PropertyDeclarationBlock) -> R,
139 {
140 match *self {
141 CSSStyleOwner::Null => {
142 unreachable!("Should never call with_block for CSStyleOwner::Null")
143 },
144 CSSStyleOwner::Element(ref el) => match *el.style_attribute().borrow() {
145 Some(ref pdb) => {
146 let document = el.owner_document();
147 let guard = document.style_shared_lock().read();
148 f(pdb.read_with(&guard))
149 },
150 None => {
151 let pdb = PropertyDeclarationBlock::new();
152 f(&pdb)
153 },
154 },
155 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
156 let guard = rule.shared_lock().read();
157 f(pdb.borrow().read_with(&guard))
158 },
159 }
160 }
161
162 fn window(&self) -> DomRoot<Window> {
163 match *self {
164 CSSStyleOwner::Null => {
165 unreachable!("Should never try to access window of CSStyleOwner::Null")
166 },
167 CSSStyleOwner::Element(ref el) => el.owner_window(),
168 CSSStyleOwner::CSSRule(ref rule, _) => DomRoot::from_ref(rule.global().as_window()),
169 }
170 }
171
172 fn base_url(&self) -> ServoUrl {
173 match *self {
174 CSSStyleOwner::Null => {
175 unreachable!("Should never try to access base URL of CSStyleOwner::Null")
176 },
177 CSSStyleOwner::Element(ref el) => el.owner_document().base_url(),
178 CSSStyleOwner::CSSRule(ref rule, _) => ServoUrl::from(
179 rule.parent_stylesheet()
180 .style_stylesheet()
181 .contents
182 .url_data
183 .read()
184 .0
185 .clone(),
186 )
187 .clone(),
188 }
189 }
190}
191
192#[derive(MallocSizeOf, PartialEq)]
193pub(crate) enum CSSModificationAccess {
194 ReadWrite,
195 Readonly,
196}
197
198macro_rules! css_properties(
199 ( $([$getter:ident, $setter:ident, $id:expr],)* ) => (
200 $(
201 fn $getter(&self) -> DOMString {
202 debug_assert!(
203 $id.enabled_for_all_content(),
204 "Someone forgot a #[Pref] annotation"
205 );
206 self.get_property_value($id)
207 }
208 fn $setter(&self, value: DOMString) -> ErrorResult {
209 debug_assert!(
210 $id.enabled_for_all_content(),
211 "Someone forgot a #[Pref] annotation"
212 );
213 self.set_property($id, value, DOMString::new(), CanGc::note())
214 }
215 )*
216 );
217);
218
219fn remove_property(decls: &mut PropertyDeclarationBlock, id: &PropertyId) -> bool {
220 let first_declaration = decls.first_declaration_to_remove(id);
221 let first_declaration = match first_declaration {
222 Some(i) => i,
223 None => return false,
224 };
225 decls.remove_property(id, first_declaration);
226 true
227}
228
229impl CSSStyleDeclaration {
230 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
231 pub(crate) fn new_inherited(
232 owner: CSSStyleOwner,
233 pseudo: Option<PseudoElement>,
234 modification_access: CSSModificationAccess,
235 ) -> CSSStyleDeclaration {
236 assert!(
239 !matches!(owner, CSSStyleOwner::Null) ||
240 modification_access == CSSModificationAccess::Readonly
241 );
242
243 CSSStyleDeclaration {
244 reflector_: Reflector::new(),
245 owner,
246 readonly: modification_access == CSSModificationAccess::Readonly,
247 pseudo,
248 }
249 }
250
251 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
252 pub(crate) fn new(
253 global: &Window,
254 owner: CSSStyleOwner,
255 pseudo: Option<PseudoElement>,
256 modification_access: CSSModificationAccess,
257 can_gc: CanGc,
258 ) -> DomRoot<CSSStyleDeclaration> {
259 reflect_dom_object(
260 Box::new(CSSStyleDeclaration::new_inherited(
261 owner,
262 pseudo,
263 modification_access,
264 )),
265 global,
266 can_gc,
267 )
268 }
269
270 pub(crate) fn update_property_declaration_block(
271 &self,
272 pdb: &Arc<Locked<PropertyDeclarationBlock>>,
273 ) {
274 if let CSSStyleOwner::CSSRule(_, pdb_cell) = &self.owner {
275 *pdb_cell.borrow_mut() = pdb.clone();
276 } else {
277 panic!("update_rule called on CSSStyleDeclaration with a Element owner");
278 }
279 }
280
281 fn get_computed_style(&self, property: PropertyId) -> DOMString {
282 match self.owner {
283 CSSStyleOwner::CSSRule(..) => {
284 panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner")
285 },
286 CSSStyleOwner::Element(ref el) => {
287 let node = el.upcast::<Node>();
288 if !node.is_connected() {
289 return DOMString::new();
290 }
291 let addr = node.to_trusted_node_address();
292 node.owner_window()
293 .resolved_style_query(addr, self.pseudo, property)
294 },
295 CSSStyleOwner::Null => DOMString::new(),
296 }
297 }
298
299 fn get_property_value(&self, id: PropertyId) -> DOMString {
300 if matches!(self.owner, CSSStyleOwner::Null) {
301 return DOMString::new();
302 }
303
304 if self.readonly {
305 return self.get_computed_style(id);
307 }
308
309 let mut string = String::new();
310
311 self.owner.with_block(|pdb| {
312 pdb.property_value_to_css(&id, &mut string).unwrap();
313 });
314
315 DOMString::from(string)
316 }
317
318 fn set_property(
320 &self,
321 id: PropertyId,
322 value: DOMString,
323 priority: DOMString,
324 can_gc: CanGc,
325 ) -> ErrorResult {
326 self.set_property_inner(
327 PotentiallyParsedPropertyId::Parsed(id),
328 value,
329 priority,
330 can_gc,
331 )
332 }
333
334 fn set_property_inner(
339 &self,
340 id: PotentiallyParsedPropertyId,
341 value: DOMString,
342 priority: DOMString,
343 can_gc: CanGc,
344 ) -> ErrorResult {
345 if self.readonly {
347 return Err(Error::NoModificationAllowed);
348 }
349
350 let id = match id {
351 PotentiallyParsedPropertyId::Parsed(id) => {
352 if !id.enabled_for_all_content() {
353 return Ok(());
354 }
355
356 id
357 },
358 PotentiallyParsedPropertyId::NotParsed(unparsed) => {
359 match PropertyId::parse_enabled_for_all_content(&unparsed) {
360 Ok(id) => id,
361 Err(..) => return Ok(()),
362 }
363 },
364 };
365
366 self.owner.mutate_associated_block(
367 |pdb, changed| {
368 if value.is_empty() {
371 *changed = remove_property(pdb, &id);
372 return Ok(());
373 }
374
375 let importance = match &*priority {
378 "" => Importance::Normal,
379 p if p.eq_ignore_ascii_case("important") => Importance::Important,
380 _ => {
381 *changed = false;
382 return Ok(());
383 },
384 };
385
386 let window = self.owner.window();
388 let quirks_mode = window.Document().quirks_mode();
389 let mut declarations = SourcePropertyDeclaration::default();
390 let result = parse_one_declaration_into(
391 &mut declarations,
392 id,
393 &value,
394 Origin::Author,
395 &UrlExtraData(self.owner.base_url().get_arc()),
396 window.css_error_reporter(),
397 ParsingMode::DEFAULT,
398 quirks_mode,
399 CssRuleType::Style,
400 );
401
402 match result {
404 Ok(()) => {},
405 Err(_) => {
406 *changed = false;
407 return Ok(());
408 },
409 }
410
411 let mut updates = Default::default();
412 *changed = pdb.prepare_for_update(&declarations, importance, &mut updates);
413
414 if !*changed {
415 return Ok(());
416 }
417
418 pdb.update(declarations.drain(), importance, &mut updates);
421
422 Ok(())
423 },
424 can_gc,
425 )
426 }
427}
428
429pub(crate) static ENABLED_LONGHAND_PROPERTIES: LazyLock<Vec<LonghandId>> = LazyLock::new(|| {
430 let mut enabled_longhands: Vec<LonghandId> = ShorthandId::All.longhands().collect();
433 if PropertyId::NonCustom(LonghandId::Direction.into()).enabled_for_all_content() {
434 enabled_longhands.push(LonghandId::Direction);
435 }
436 if PropertyId::NonCustom(LonghandId::UnicodeBidi.into()).enabled_for_all_content() {
437 enabled_longhands.push(LonghandId::UnicodeBidi);
438 }
439
440 enabled_longhands.sort_unstable_by(|a, b| {
442 let a = a.name();
443 let b = b.name();
444 let is_a_vendor_prefixed = a.starts_with('-');
445 let is_b_vendor_prefixed = b.starts_with('-');
446 if is_a_vendor_prefixed == is_b_vendor_prefixed {
447 a.partial_cmp(b).unwrap()
448 } else if is_b_vendor_prefixed {
449 Ordering::Less
450 } else {
451 Ordering::Greater
452 }
453 });
454 enabled_longhands
455});
456
457enum PotentiallyParsedPropertyId {
458 Parsed(PropertyId),
459 NotParsed(DOMString),
460}
461
462impl CSSStyleDeclarationMethods<crate::DomTypeHolder> for CSSStyleDeclaration {
463 fn Length(&self) -> u32 {
465 if matches!(self.owner, CSSStyleOwner::Null) {
466 return 0;
467 }
468
469 if self.readonly {
470 return ENABLED_LONGHAND_PROPERTIES.len() as u32;
473 }
474 self.owner.with_block(|pdb| pdb.declarations().len() as u32)
475 }
476
477 fn Item(&self, index: u32) -> DOMString {
479 self.IndexedGetter(index).unwrap_or_default()
480 }
481
482 fn GetPropertyValue(&self, property: DOMString) -> DOMString {
484 let id = match PropertyId::parse_enabled_for_all_content(&property) {
485 Ok(id) => id,
486 Err(..) => return DOMString::new(),
487 };
488 self.get_property_value(id)
489 }
490
491 fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
493 if self.readonly {
494 return DOMString::new();
496 }
497 let id = match PropertyId::parse_enabled_for_all_content(&property) {
498 Ok(id) => id,
499 Err(..) => return DOMString::new(),
500 };
501
502 self.owner.with_block(|pdb| {
503 if pdb.property_priority(&id).important() {
504 DOMString::from("important")
505 } else {
506 DOMString::new()
508 }
509 })
510 }
511
512 fn SetProperty(
514 &self,
515 property: DOMString,
516 value: DOMString,
517 priority: DOMString,
518 can_gc: CanGc,
519 ) -> ErrorResult {
520 self.set_property_inner(
521 PotentiallyParsedPropertyId::NotParsed(property),
522 value,
523 priority,
524 can_gc,
525 )
526 }
527
528 fn RemoveProperty(&self, property: DOMString, can_gc: CanGc) -> Fallible<DOMString> {
530 if self.readonly {
532 return Err(Error::NoModificationAllowed);
533 }
534
535 let id = match PropertyId::parse_enabled_for_all_content(&property) {
536 Ok(id) => id,
537 Err(..) => return Ok(DOMString::new()),
538 };
539
540 let mut string = String::new();
541 self.owner.mutate_associated_block(
542 |pdb, changed| {
543 pdb.property_value_to_css(&id, &mut string).unwrap();
544 *changed = remove_property(pdb, &id);
545 },
546 can_gc,
547 );
548
549 Ok(DOMString::from(string))
551 }
552
553 fn CssFloat(&self) -> DOMString {
555 self.get_property_value(PropertyId::NonCustom(LonghandId::Float.into()))
556 }
557
558 fn SetCssFloat(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
560 self.set_property(
561 PropertyId::NonCustom(LonghandId::Float.into()),
562 value,
563 DOMString::new(),
564 can_gc,
565 )
566 }
567
568 fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
570 if matches!(self.owner, CSSStyleOwner::Null) {
571 return None;
572 }
573 if self.readonly {
574 let longhand = ENABLED_LONGHAND_PROPERTIES.get(index as usize)?;
577 return Some(DOMString::from(longhand.name()));
578 }
579 self.owner.with_block(|pdb| {
580 let declaration = pdb.declarations().get(index as usize)?;
581 Some(DOMString::from(declaration.id().name()))
582 })
583 }
584
585 fn CssText(&self) -> DOMString {
587 if self.readonly {
588 return DOMString::new();
590 }
591 self.owner.with_block(|pdb| {
592 let mut serialization = String::new();
593 pdb.to_css(&mut serialization).unwrap();
594 DOMString::from(serialization)
595 })
596 }
597
598 fn SetCssText(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
600 let window = self.owner.window();
601
602 if self.readonly {
604 return Err(Error::NoModificationAllowed);
605 }
606
607 let quirks_mode = window.Document().quirks_mode();
608 self.owner.mutate_associated_block(
609 |pdb, _changed| {
610 *pdb = parse_style_attribute(
612 &value,
613 &UrlExtraData(self.owner.base_url().get_arc()),
614 window.css_error_reporter(),
615 quirks_mode,
616 CssRuleType::Style,
617 );
618 },
619 can_gc,
620 );
621
622 Ok(())
623 }
624
625 style::css_properties_accessors!(css_properties);
627}