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, StylesheetInDocument, UrlExtraData};
21use style_traits::ParsingMode;
22
23use super::cssrule::CSSRule;
24use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
25use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
26use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
29use crate::dom::bindings::root::{Dom, DomRoot};
30use crate::dom::bindings::str::DOMString;
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 = "Stylo"]
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 let guard = rule.shared_lock().read();
180 rule.parent_stylesheet()
181 .style_stylesheet()
182 .contents(&guard)
183 .url_data
184 .0
185 .clone()
186 }),
187 }
188 }
189}
190
191#[derive(MallocSizeOf, PartialEq)]
192pub(crate) enum CSSModificationAccess {
193 ReadWrite,
194 Readonly,
195}
196
197macro_rules! css_properties(
198 ( $([$getter:ident, $setter:ident, $id:expr],)* ) => (
199 $(
200 fn $getter(&self) -> DOMString {
201 debug_assert!(
202 $id.enabled_for_all_content(),
203 "Someone forgot a #[Pref] annotation"
204 );
205 self.get_property_value($id)
206 }
207 fn $setter(&self, value: DOMString) -> ErrorResult {
208 debug_assert!(
209 $id.enabled_for_all_content(),
210 "Someone forgot a #[Pref] annotation"
211 );
212 self.set_property($id, value, DOMString::new(), CanGc::note())
213 }
214 )*
215 );
216);
217
218fn remove_property(decls: &mut PropertyDeclarationBlock, id: &PropertyId) -> bool {
219 let first_declaration = decls.first_declaration_to_remove(id);
220 let first_declaration = match first_declaration {
221 Some(i) => i,
222 None => return false,
223 };
224 decls.remove_property(id, first_declaration);
225 true
226}
227
228impl CSSStyleDeclaration {
229 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
230 pub(crate) fn new_inherited(
231 owner: CSSStyleOwner,
232 pseudo: Option<PseudoElement>,
233 modification_access: CSSModificationAccess,
234 ) -> CSSStyleDeclaration {
235 assert!(
238 !matches!(owner, CSSStyleOwner::Null) ||
239 modification_access == CSSModificationAccess::Readonly
240 );
241
242 CSSStyleDeclaration {
243 reflector_: Reflector::new(),
244 owner,
245 readonly: modification_access == CSSModificationAccess::Readonly,
246 pseudo,
247 }
248 }
249
250 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
251 pub(crate) fn new(
252 global: &Window,
253 owner: CSSStyleOwner,
254 pseudo: Option<PseudoElement>,
255 modification_access: CSSModificationAccess,
256 can_gc: CanGc,
257 ) -> DomRoot<CSSStyleDeclaration> {
258 reflect_dom_object(
259 Box::new(CSSStyleDeclaration::new_inherited(
260 owner,
261 pseudo,
262 modification_access,
263 )),
264 global,
265 can_gc,
266 )
267 }
268
269 pub(crate) fn update_property_declaration_block(
270 &self,
271 pdb: &Arc<Locked<PropertyDeclarationBlock>>,
272 ) {
273 if let CSSStyleOwner::CSSRule(_, pdb_cell) = &self.owner {
274 *pdb_cell.borrow_mut() = pdb.clone();
275 } else {
276 panic!("update_rule called on CSSStyleDeclaration with a Element owner");
277 }
278 }
279
280 fn get_computed_style(&self, property: PropertyId) -> DOMString {
281 match self.owner {
282 CSSStyleOwner::CSSRule(..) => {
283 panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner")
284 },
285 CSSStyleOwner::Element(ref el) => {
286 let node = el.upcast::<Node>();
287 if !node.is_connected() {
288 return DOMString::new();
289 }
290 let addr = node.to_trusted_node_address();
291 node.owner_window()
292 .resolved_style_query(addr, self.pseudo, property)
293 },
294 CSSStyleOwner::Null => DOMString::new(),
295 }
296 }
297
298 fn get_property_value(&self, id: PropertyId) -> DOMString {
299 if matches!(self.owner, CSSStyleOwner::Null) {
300 return DOMString::new();
301 }
302
303 if self.readonly {
304 return self.get_computed_style(id);
306 }
307
308 let mut string = String::new();
309
310 self.owner.with_block(|pdb| {
311 pdb.property_value_to_css(&id, &mut string).unwrap();
312 });
313
314 DOMString::from(string)
315 }
316
317 fn set_property(
319 &self,
320 id: PropertyId,
321 value: DOMString,
322 priority: DOMString,
323 can_gc: CanGc,
324 ) -> ErrorResult {
325 self.set_property_inner(
326 PotentiallyParsedPropertyId::Parsed(id),
327 value,
328 priority,
329 can_gc,
330 )
331 }
332
333 fn set_property_inner(
338 &self,
339 id: PotentiallyParsedPropertyId,
340 value: DOMString,
341 priority: DOMString,
342 can_gc: CanGc,
343 ) -> ErrorResult {
344 if self.readonly {
346 return Err(Error::NoModificationAllowed(None));
347 }
348
349 let id = match id {
350 PotentiallyParsedPropertyId::Parsed(id) => {
351 if !id.enabled_for_all_content() {
352 return Ok(());
353 }
354
355 id
356 },
357 PotentiallyParsedPropertyId::NotParsed(unparsed) => {
358 match PropertyId::parse_enabled_for_all_content(&unparsed.str()) {
359 Ok(id) => id,
360 Err(..) => return Ok(()),
361 }
362 },
363 };
364 let base_url = UrlExtraData(self.owner.base_url().get_arc());
365 self.owner.mutate_associated_block(
366 |pdb, changed| {
367 if value.is_empty() {
370 *changed = remove_property(pdb, &id);
371 return Ok(());
372 }
373
374 let importance = match &*priority.str() {
377 "" => Importance::Normal,
378 p if p.eq_ignore_ascii_case("important") => Importance::Important,
379 _ => {
380 *changed = false;
381 return Ok(());
382 },
383 };
384
385 let window = self.owner.window();
387 let quirks_mode = window.Document().quirks_mode();
388 let mut declarations = SourcePropertyDeclaration::default();
389 let result = parse_one_declaration_into(
390 &mut declarations,
391 id,
392 &value.str(),
393 Origin::Author,
394 &base_url,
395 Some(window.css_error_reporter()),
396 ParsingMode::DEFAULT,
397 quirks_mode,
398 CssRuleType::Style,
399 );
400
401 match result {
403 Ok(()) => {},
404 Err(_) => {
405 *changed = false;
406 return Ok(());
407 },
408 }
409
410 let mut updates = Default::default();
411 *changed = pdb.prepare_for_update(&declarations, importance, &mut updates);
412
413 if !*changed {
414 return Ok(());
415 }
416
417 pdb.update(declarations.drain(), importance, &mut updates);
420
421 Ok(())
422 },
423 can_gc,
424 )
425 }
426}
427
428pub(crate) static ENABLED_LONGHAND_PROPERTIES: LazyLock<Vec<LonghandId>> = LazyLock::new(|| {
429 let mut enabled_longhands: Vec<LonghandId> = ShorthandId::All.longhands().collect();
432 if PropertyId::NonCustom(LonghandId::Direction.into()).enabled_for_all_content() {
433 enabled_longhands.push(LonghandId::Direction);
434 }
435 if PropertyId::NonCustom(LonghandId::UnicodeBidi.into()).enabled_for_all_content() {
436 enabled_longhands.push(LonghandId::UnicodeBidi);
437 }
438
439 enabled_longhands.sort_unstable_by(|a, b| {
441 let a = a.name();
442 let b = b.name();
443 let is_a_vendor_prefixed = a.starts_with('-');
444 let is_b_vendor_prefixed = b.starts_with('-');
445 if is_a_vendor_prefixed == is_b_vendor_prefixed {
446 a.partial_cmp(b).unwrap()
447 } else if is_b_vendor_prefixed {
448 Ordering::Less
449 } else {
450 Ordering::Greater
451 }
452 });
453 enabled_longhands
454});
455
456enum PotentiallyParsedPropertyId {
457 Parsed(PropertyId),
458 NotParsed(DOMString),
459}
460
461impl CSSStyleDeclarationMethods<crate::DomTypeHolder> for CSSStyleDeclaration {
462 fn Length(&self) -> u32 {
464 if matches!(self.owner, CSSStyleOwner::Null) {
465 return 0;
466 }
467
468 if self.readonly {
469 return ENABLED_LONGHAND_PROPERTIES.len() as u32;
472 }
473 self.owner.with_block(|pdb| pdb.declarations().len() as u32)
474 }
475
476 fn Item(&self, index: u32) -> DOMString {
478 self.IndexedGetter(index).unwrap_or_default()
479 }
480
481 fn GetPropertyValue(&self, property: DOMString) -> DOMString {
483 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
484 Ok(id) => id,
485 Err(..) => return DOMString::new(),
486 };
487 self.get_property_value(id)
488 }
489
490 fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
492 if self.readonly {
493 return DOMString::new();
495 }
496 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
497 Ok(id) => id,
498 Err(..) => return DOMString::new(),
499 };
500
501 self.owner.with_block(|pdb| {
502 if pdb.property_priority(&id).important() {
503 DOMString::from("important")
504 } else {
505 DOMString::new()
507 }
508 })
509 }
510
511 fn SetProperty(
513 &self,
514 property: DOMString,
515 value: DOMString,
516 priority: DOMString,
517 can_gc: CanGc,
518 ) -> ErrorResult {
519 self.set_property_inner(
520 PotentiallyParsedPropertyId::NotParsed(property),
521 value,
522 priority,
523 can_gc,
524 )
525 }
526
527 fn RemoveProperty(&self, property: DOMString, can_gc: CanGc) -> Fallible<DOMString> {
529 if self.readonly {
531 return Err(Error::NoModificationAllowed(None));
532 }
533
534 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
535 Ok(id) => id,
536 Err(..) => return Ok(DOMString::new()),
537 };
538
539 let mut string = String::new();
540 self.owner.mutate_associated_block(
541 |pdb, changed| {
542 pdb.property_value_to_css(&id, &mut string).unwrap();
543 *changed = remove_property(pdb, &id);
544 },
545 can_gc,
546 );
547
548 Ok(DOMString::from(string))
550 }
551
552 fn CssFloat(&self) -> DOMString {
554 self.get_property_value(PropertyId::NonCustom(LonghandId::Float.into()))
555 }
556
557 fn SetCssFloat(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
559 self.set_property(
560 PropertyId::NonCustom(LonghandId::Float.into()),
561 value,
562 DOMString::new(),
563 can_gc,
564 )
565 }
566
567 fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
569 if matches!(self.owner, CSSStyleOwner::Null) {
570 return None;
571 }
572 if self.readonly {
573 let longhand = ENABLED_LONGHAND_PROPERTIES.get(index as usize)?;
576 return Some(DOMString::from(longhand.name()));
577 }
578 self.owner.with_block(|pdb| {
579 let declaration = pdb.declarations().get(index as usize)?;
580 Some(DOMString::from(declaration.id().name()))
581 })
582 }
583
584 fn CssText(&self) -> DOMString {
586 if self.readonly {
587 return DOMString::new();
589 }
590 self.owner.with_block(|pdb| {
591 let mut serialization = String::new();
592 pdb.to_css(&mut serialization).unwrap();
593 DOMString::from(serialization)
594 })
595 }
596
597 fn SetCssText(&self, value: DOMString, can_gc: CanGc) -> ErrorResult {
599 let window = self.owner.window();
600
601 if self.readonly {
603 return Err(Error::NoModificationAllowed(None));
604 }
605
606 let quirks_mode = window.Document().quirks_mode();
607 let base_url = UrlExtraData(self.owner.base_url().get_arc());
608 self.owner.mutate_associated_block(
609 |pdb, _changed| {
610 *pdb = parse_style_attribute(
612 &value.str(),
613 &base_url,
614 Some(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}