1use std::cell::RefCell;
6use std::cmp::Ordering;
7use std::sync::LazyLock;
8
9use dom_struct::dom_struct;
10use html5ever::local_name;
11use js::context::JSContext;
12use servo_arc::Arc;
13use servo_url::ServoUrl;
14use style::attr::AttrValue;
15use style::properties::{
16 Importance, LonghandId, PropertyDeclarationBlock, PropertyId, ShorthandId,
17 SourcePropertyDeclaration, parse_one_declaration_into, parse_style_attribute,
18};
19use style::selector_parser::PseudoElement;
20use style::shared_lock::Locked;
21use style::stylesheets::{CssRuleType, Origin, StylesheetInDocument, UrlExtraData};
22use style_traits::ParsingMode;
23
24use super::cssrule::CSSRule;
25use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
26use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
27use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
30use crate::dom::bindings::root::{Dom, DomRoot};
31use crate::dom::bindings::str::DOMString;
32use crate::dom::element::Element;
33use crate::dom::node::{Node, NodeTraits};
34use crate::dom::window::Window;
35use crate::script_runtime::CanGc;
36
37#[dom_struct]
39pub(crate) struct CSSStyleDeclaration {
40 reflector_: Reflector,
41 owner: CSSStyleOwner,
42 readonly: bool,
43 #[no_trace]
44 pseudo: Option<PseudoElement>,
45}
46
47#[derive(JSTraceable, MallocSizeOf)]
48#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
49pub(crate) enum CSSStyleOwner {
50 Null,
53 Element(Dom<Element>),
54 CSSRule(
55 Dom<CSSRule>,
56 #[ignore_malloc_size_of = "Stylo"]
57 #[no_trace]
58 RefCell<Arc<Locked<PropertyDeclarationBlock>>>,
59 ),
60}
61
62impl CSSStyleOwner {
63 fn mutate_associated_block<F, R>(&self, f: F, can_gc: CanGc) -> R
66 where
67 F: FnOnce(&mut PropertyDeclarationBlock, &mut bool) -> R,
68 {
69 let mut changed = true;
73 match *self {
74 CSSStyleOwner::Null => unreachable!(
75 "CSSStyleDeclaration should always be read-only when CSSStyleOwner is Null"
76 ),
77 CSSStyleOwner::Element(ref el) => {
78 let document = el.owner_document();
79 let shared_lock = document.style_shared_lock();
80 let mut attr = el.style_attribute().borrow_mut().take();
81 let result = if let Some(lock) = attr.as_ref() {
82 let mut guard = shared_lock.write();
83 let pdb = lock.write_with(&mut guard);
84 f(pdb, &mut changed)
85 } else {
86 let mut pdb = PropertyDeclarationBlock::new();
87 let result = f(&mut pdb, &mut changed);
88
89 changed = !pdb.declarations().is_empty();
92 if changed {
93 attr = Some(Arc::new(shared_lock.wrap(pdb)));
94 }
95
96 result
97 };
98
99 if changed {
100 if let Some(pdb) = attr {
107 let guard = shared_lock.read();
108 let mut serialization = String::new();
109 pdb.read_with(&guard).to_css(&mut serialization).unwrap();
110 el.set_attribute(
111 &local_name!("style"),
112 AttrValue::Declaration(serialization, pdb),
113 can_gc,
114 );
115 }
116 } else {
117 *el.style_attribute().borrow_mut() = attr;
119 }
120
121 result
122 },
123 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
124 rule.parent_stylesheet().will_modify();
125 let result = {
126 let mut guard = rule.shared_lock().write();
127 f(&mut *pdb.borrow().write_with(&mut guard), &mut changed)
128 };
129 if changed {
130 rule.parent_stylesheet().notify_invalidations();
131 }
132 result
133 },
134 }
135 }
136
137 fn with_block<F, R>(&self, f: F) -> R
138 where
139 F: FnOnce(&PropertyDeclarationBlock) -> R,
140 {
141 match *self {
142 CSSStyleOwner::Null => {
143 unreachable!("Should never call with_block for CSStyleOwner::Null")
144 },
145 CSSStyleOwner::Element(ref el) => match *el.style_attribute().borrow() {
146 Some(ref pdb) => {
147 let document = el.owner_document();
148 let guard = document.style_shared_lock().read();
149 f(pdb.read_with(&guard))
150 },
151 None => {
152 let pdb = PropertyDeclarationBlock::new();
153 f(&pdb)
154 },
155 },
156 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
157 let guard = rule.shared_lock().read();
158 f(pdb.borrow().read_with(&guard))
159 },
160 }
161 }
162
163 fn window(&self) -> DomRoot<Window> {
164 match *self {
165 CSSStyleOwner::Null => {
166 unreachable!("Should never try to access window of CSStyleOwner::Null")
167 },
168 CSSStyleOwner::Element(ref el) => el.owner_window(),
169 CSSStyleOwner::CSSRule(ref rule, _) => DomRoot::from_ref(rule.global().as_window()),
170 }
171 }
172
173 fn base_url(&self) -> ServoUrl {
174 match *self {
175 CSSStyleOwner::Null => {
176 unreachable!("Should never try to access base URL of CSStyleOwner::Null")
177 },
178 CSSStyleOwner::Element(ref el) => el.owner_document().base_url(),
179 CSSStyleOwner::CSSRule(ref rule, _) => ServoUrl::from({
180 let guard = rule.shared_lock().read();
181 rule.parent_stylesheet()
182 .style_stylesheet()
183 .contents(&guard)
184 .url_data
185 .0
186 .clone()
187 }),
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, cx: &mut JSContext, value: DOMString) -> ErrorResult {
209 debug_assert!(
210 $id.enabled_for_all_content(),
211 "Someone forgot a #[Pref] annotation"
212 );
213 self.set_property(cx, $id, value, DOMString::new())
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, expect(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, expect(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 cx: &mut JSContext,
322 id: PropertyId,
323 value: DOMString,
324 priority: DOMString,
325 ) -> ErrorResult {
326 self.set_property_inner(cx, PotentiallyParsedPropertyId::Parsed(id), value, priority)
327 }
328
329 fn set_property_inner(
334 &self,
335 cx: &mut JSContext,
336 id: PotentiallyParsedPropertyId,
337 value: DOMString,
338 priority: DOMString,
339 ) -> ErrorResult {
340 if self.readonly {
342 return Err(Error::NoModificationAllowed(None));
343 }
344
345 let id = match id {
346 PotentiallyParsedPropertyId::Parsed(id) => {
347 if !id.enabled_for_all_content() {
348 return Ok(());
349 }
350
351 id
352 },
353 PotentiallyParsedPropertyId::NotParsed(unparsed) => {
354 match PropertyId::parse_enabled_for_all_content(&unparsed.str()) {
355 Ok(id) => id,
356 Err(..) => return Ok(()),
357 }
358 },
359 };
360 let base_url = UrlExtraData(self.owner.base_url().get_arc());
361 self.owner.mutate_associated_block(
362 |pdb, changed| {
363 if value.is_empty() {
366 *changed = remove_property(pdb, &id);
367 return Ok(());
368 }
369
370 let importance = match &*priority.str() {
373 "" => Importance::Normal,
374 p if p.eq_ignore_ascii_case("important") => Importance::Important,
375 _ => {
376 *changed = false;
377 return Ok(());
378 },
379 };
380
381 let window = self.owner.window();
383 let quirks_mode = window.Document().quirks_mode();
384 let mut declarations = SourcePropertyDeclaration::default();
385 let result = parse_one_declaration_into(
386 &mut declarations,
387 id,
388 &value.str(),
389 Origin::Author,
390 &base_url,
391 Some(window.css_error_reporter()),
392 ParsingMode::DEFAULT,
393 quirks_mode,
394 CssRuleType::Style,
395 );
396
397 match result {
399 Ok(()) => {},
400 Err(_) => {
401 *changed = false;
402 return Ok(());
403 },
404 }
405
406 let mut updates = Default::default();
407 *changed = pdb.prepare_for_update(&declarations, importance, &mut updates);
408
409 if !*changed {
410 return Ok(());
411 }
412
413 pdb.update(declarations.drain(), importance, &mut updates);
416
417 Ok(())
418 },
419 CanGc::from_cx(cx),
420 )
421 }
422}
423
424pub(crate) static ENABLED_LONGHAND_PROPERTIES: LazyLock<Vec<LonghandId>> = LazyLock::new(|| {
425 let mut enabled_longhands: Vec<LonghandId> = ShorthandId::All.longhands().collect();
428 if PropertyId::NonCustom(LonghandId::Direction.into()).enabled_for_all_content() {
429 enabled_longhands.push(LonghandId::Direction);
430 }
431 if PropertyId::NonCustom(LonghandId::UnicodeBidi.into()).enabled_for_all_content() {
432 enabled_longhands.push(LonghandId::UnicodeBidi);
433 }
434
435 enabled_longhands.sort_unstable_by(|a, b| {
437 let a = a.name();
438 let b = b.name();
439 let is_a_vendor_prefixed = a.starts_with('-');
440 let is_b_vendor_prefixed = b.starts_with('-');
441 if is_a_vendor_prefixed == is_b_vendor_prefixed {
442 a.partial_cmp(b).unwrap()
443 } else if is_b_vendor_prefixed {
444 Ordering::Less
445 } else {
446 Ordering::Greater
447 }
448 });
449 enabled_longhands
450});
451
452enum PotentiallyParsedPropertyId {
453 Parsed(PropertyId),
454 NotParsed(DOMString),
455}
456
457impl CSSStyleDeclarationMethods<crate::DomTypeHolder> for CSSStyleDeclaration {
458 fn Length(&self) -> u32 {
460 if matches!(self.owner, CSSStyleOwner::Null) {
461 return 0;
462 }
463
464 if self.readonly {
465 return ENABLED_LONGHAND_PROPERTIES.len() as u32;
468 }
469 self.owner.with_block(|pdb| pdb.declarations().len() as u32)
470 }
471
472 fn Item(&self, index: u32) -> DOMString {
474 self.IndexedGetter(index).unwrap_or_default()
475 }
476
477 fn GetPropertyValue(&self, property: DOMString) -> DOMString {
479 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
480 Ok(id) => id,
481 Err(..) => return DOMString::new(),
482 };
483 self.get_property_value(id)
484 }
485
486 fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
488 if self.readonly {
489 return DOMString::new();
491 }
492 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
493 Ok(id) => id,
494 Err(..) => return DOMString::new(),
495 };
496
497 self.owner.with_block(|pdb| {
498 if pdb.property_priority(&id).important() {
499 DOMString::from("important")
500 } else {
501 DOMString::new()
503 }
504 })
505 }
506
507 fn SetProperty(
509 &self,
510 cx: &mut JSContext,
511 property: DOMString,
512 value: DOMString,
513 priority: DOMString,
514 ) -> ErrorResult {
515 self.set_property_inner(
516 cx,
517 PotentiallyParsedPropertyId::NotParsed(property),
518 value,
519 priority,
520 )
521 }
522
523 fn RemoveProperty(&self, cx: &mut JSContext, property: DOMString) -> Fallible<DOMString> {
525 if self.readonly {
527 return Err(Error::NoModificationAllowed(None));
528 }
529
530 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
531 Ok(id) => id,
532 Err(..) => return Ok(DOMString::new()),
533 };
534
535 let mut string = String::new();
536 self.owner.mutate_associated_block(
537 |pdb, changed| {
538 pdb.property_value_to_css(&id, &mut string).unwrap();
539 *changed = remove_property(pdb, &id);
540 },
541 CanGc::from_cx(cx),
542 );
543
544 Ok(DOMString::from(string))
546 }
547
548 fn CssFloat(&self) -> DOMString {
550 self.get_property_value(PropertyId::NonCustom(LonghandId::Float.into()))
551 }
552
553 fn SetCssFloat(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
555 self.set_property(
556 cx,
557 PropertyId::NonCustom(LonghandId::Float.into()),
558 value,
559 DOMString::new(),
560 )
561 }
562
563 fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
565 if matches!(self.owner, CSSStyleOwner::Null) {
566 return None;
567 }
568 if self.readonly {
569 let longhand = ENABLED_LONGHAND_PROPERTIES.get(index as usize)?;
572 return Some(DOMString::from(longhand.name()));
573 }
574 self.owner.with_block(|pdb| {
575 let declaration = pdb.declarations().get(index as usize)?;
576 Some(DOMString::from(declaration.id().name()))
577 })
578 }
579
580 fn CssText(&self) -> DOMString {
582 if self.readonly {
583 return DOMString::new();
585 }
586 self.owner.with_block(|pdb| {
587 let mut serialization = String::new();
588 pdb.to_css(&mut serialization).unwrap();
589 DOMString::from(serialization)
590 })
591 }
592
593 fn SetCssText(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
595 let window = self.owner.window();
596
597 if self.readonly {
599 return Err(Error::NoModificationAllowed(None));
600 }
601
602 let quirks_mode = window.Document().quirks_mode();
603 let base_url = UrlExtraData(self.owner.base_url().get_arc());
604 self.owner.mutate_associated_block(
605 |pdb, _changed| {
606 *pdb = parse_style_attribute(
608 &value.str(),
609 &base_url,
610 Some(window.css_error_reporter()),
611 quirks_mode,
612 CssRuleType::Style,
613 );
614 },
615 CanGc::from_cx(cx),
616 );
617
618 Ok(())
619 }
620
621 style::css_properties_accessors!(css_properties);
623}