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 script_bindings::reflector::{Reflector, reflect_dom_object};
13use servo_arc::Arc;
14use servo_url::ServoUrl;
15use style::attr::AttrValue;
16use style::properties::{
17 Importance, LonghandId, PropertyDeclarationBlock, PropertyId, ShorthandId,
18 SourcePropertyDeclaration, parse_one_declaration_into, parse_style_attribute,
19};
20use style::selector_parser::PseudoElement;
21use style::shared_lock::Locked;
22use style::stylesheets::{CssRuleType, Origin, StylesheetInDocument, UrlExtraData};
23use style_traits::ParsingMode;
24
25use super::cssrule::CSSRule;
26use crate::dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
27use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
28use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
29use crate::dom::bindings::inheritance::Castable;
30use crate::dom::bindings::reflector::DomGlobal;
31use crate::dom::bindings::root::{Dom, DomRoot};
32use crate::dom::bindings::str::DOMString;
33use crate::dom::element::Element;
34use crate::dom::node::{Node, NodeTraits};
35use crate::dom::types::CSSFontFaceDescriptors;
36use crate::dom::window::Window;
37
38#[dom_struct]
40pub(crate) struct CSSStyleDeclaration {
41 reflector_: Reflector,
42 owner: CSSStyleOwner,
43 readonly: bool,
44 #[no_trace]
45 pseudo: Option<PseudoElement>,
46}
47
48#[derive(JSTraceable, MallocSizeOf)]
49#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
50pub(crate) enum CSSStyleOwner {
51 Null,
54 Element(Dom<Element>),
55 CSSRule(
56 Dom<CSSRule>,
57 #[ignore_malloc_size_of = "Stylo"]
58 #[no_trace]
59 RefCell<Arc<Locked<PropertyDeclarationBlock>>>,
60 ),
61}
62
63impl CSSStyleOwner {
64 fn mutate_associated_block<F, R>(&self, cx: &mut JSContext, f: F) -> R
67 where
68 F: FnOnce(&mut PropertyDeclarationBlock, &mut bool) -> R,
69 {
70 let mut changed = true;
74 match *self {
75 CSSStyleOwner::Null => unreachable!(
76 "CSSStyleDeclaration should always be read-only when CSSStyleOwner is Null"
77 ),
78 CSSStyleOwner::Element(ref element) => {
79 let document = element.owner_document();
80 let shared_lock = document.style_shared_author_lock();
81 let mut attribute_pdb =
82 element.style_attribute().safe_borrow_mut(cx.no_gc()).take();
83
84 if element.needs_preserved_style_attribute_after_change() {
89 attribute_pdb = attribute_pdb.map(|attribute_pdb| {
90 let new_pdb = attribute_pdb.read_with(&shared_lock.read()).clone();
91 Arc::new(shared_lock.wrap(new_pdb))
92 });
93 }
94
95 let (attribute_pdb, result) = if let Some(attribute_pdb) = attribute_pdb {
96 let mut guard = shared_lock.write();
97 let writable_pdb = attribute_pdb.write_with(&mut guard);
98 let result = f(writable_pdb, &mut changed);
99 (attribute_pdb, result)
100 } else {
101 let mut new_pdb = PropertyDeclarationBlock::new();
102 let result = f(&mut new_pdb, &mut changed);
103
104 changed = !new_pdb.declarations().is_empty();
105 if !changed {
106 return result;
107 }
108
109 (Arc::new(shared_lock.wrap(new_pdb)), result)
110 };
111
112 if !changed {
117 *element.style_attribute().safe_borrow_mut(cx.no_gc()) = Some(attribute_pdb);
118 return result;
119 }
120
121 element.set_attribute(
122 cx,
123 &local_name!("style"),
124 AttrValue::from_declaration(attribute_pdb, shared_lock.clone()),
125 );
126 result
127 },
128 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
129 rule.parent_stylesheet().will_modify();
130 let result = {
131 let mut guard = rule.shared_lock().write();
132 f(&mut *pdb.borrow().write_with(&mut guard), &mut changed)
133 };
134 if changed {
135 rule.parent_stylesheet().notify_invalidations(cx.no_gc());
136 }
137 result
138 },
139 }
140 }
141
142 fn with_block<F, R>(&self, f: F) -> R
143 where
144 F: FnOnce(&PropertyDeclarationBlock) -> R,
145 {
146 match *self {
147 CSSStyleOwner::Null => {
148 unreachable!("Should never call with_block for CSStyleOwner::Null")
149 },
150 CSSStyleOwner::Element(ref el) => match *el.style_attribute().borrow() {
151 Some(ref pdb) => {
152 let document = el.owner_document();
153 let guard = document.style_shared_author_lock().read();
154 f(pdb.read_with(&guard))
155 },
156 None => {
157 let pdb = PropertyDeclarationBlock::new();
158 f(&pdb)
159 },
160 },
161 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
162 let guard = rule.shared_lock().read();
163 f(pdb.borrow().read_with(&guard))
164 },
165 }
166 }
167
168 fn window(&self) -> DomRoot<Window> {
169 match *self {
170 CSSStyleOwner::Null => {
171 unreachable!("Should never try to access window of CSStyleOwner::Null")
172 },
173 CSSStyleOwner::Element(ref el) => el.owner_window(),
174 CSSStyleOwner::CSSRule(ref rule, _) => DomRoot::from_ref(rule.global().as_window()),
175 }
176 }
177
178 fn base_url(&self) -> ServoUrl {
179 match *self {
180 CSSStyleOwner::Null => {
181 unreachable!("Should never try to access base URL of CSStyleOwner::Null")
182 },
183 CSSStyleOwner::Element(ref el) => el.owner_document().base_url(),
184 CSSStyleOwner::CSSRule(ref rule, _) => ServoUrl::from({
185 let guard = rule.shared_lock().read();
186 rule.parent_stylesheet()
187 .style_stylesheet()
188 .contents(&guard)
189 .url_data
190 .0
191 .clone()
192 }),
193 }
194 }
195}
196
197#[derive(MallocSizeOf, PartialEq)]
198pub(crate) enum CSSModificationAccess {
199 ReadWrite,
200 Readonly,
201}
202
203macro_rules! css_properties(
204 ( $([$getter:ident, $setter:ident, $id:expr],)* ) => (
205 $(
206 fn $getter(&self) -> DOMString {
207 debug_assert!(
208 $id.enabled_for_all_content(),
209 "Someone forgot a #[Pref] annotation"
210 );
211 self.get_property_value($id)
212 }
213 fn $setter(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
214 debug_assert!(
215 $id.enabled_for_all_content(),
216 "Someone forgot a #[Pref] annotation"
217 );
218 self.set_property(cx, $id, value, DOMString::new())
219 }
220 )*
221 );
222);
223
224fn remove_property(decls: &mut PropertyDeclarationBlock, id: &PropertyId) -> bool {
225 let first_declaration = decls.first_declaration_to_remove(id);
226 let first_declaration = match first_declaration {
227 Some(i) => i,
228 None => return false,
229 };
230 decls.remove_property(id, first_declaration);
231 true
232}
233
234impl CSSStyleDeclaration {
235 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
236 pub(crate) fn new_inherited(
237 owner: CSSStyleOwner,
238 pseudo: Option<PseudoElement>,
239 modification_access: CSSModificationAccess,
240 ) -> CSSStyleDeclaration {
241 assert!(
244 !matches!(owner, CSSStyleOwner::Null) ||
245 modification_access == CSSModificationAccess::Readonly
246 );
247
248 CSSStyleDeclaration {
249 reflector_: Reflector::new(),
250 owner,
251 readonly: modification_access == CSSModificationAccess::Readonly,
252 pseudo,
253 }
254 }
255
256 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
257 pub(crate) fn new(
258 cx: &mut JSContext,
259 global: &Window,
260 owner: CSSStyleOwner,
261 pseudo: Option<PseudoElement>,
262 modification_access: CSSModificationAccess,
263 ) -> DomRoot<CSSStyleDeclaration> {
264 reflect_dom_object(
265 cx,
266 Box::new(CSSStyleDeclaration::new_inherited(
267 owner,
268 pseudo,
269 modification_access,
270 )),
271 global,
272 )
273 }
274
275 pub(crate) fn update_property_declaration_block(
276 &self,
277 pdb: &Arc<Locked<PropertyDeclarationBlock>>,
278 ) {
279 if let CSSStyleOwner::CSSRule(_, pdb_cell) = &self.owner {
280 *pdb_cell.borrow_mut() = pdb.clone();
281 } else {
282 panic!("update_rule called on CSSStyleDeclaration with a Element owner");
283 }
284 }
285
286 fn get_computed_style(&self, property: PropertyId) -> DOMString {
287 match self.owner {
288 CSSStyleOwner::CSSRule(..) => {
289 panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner")
290 },
291 CSSStyleOwner::Element(ref el) => {
292 let node = el.upcast::<Node>();
293 if !node.is_connected() {
294 return DOMString::new();
295 }
296 let addr = node.to_trusted_node_address();
297 node.owner_window()
298 .resolved_style_query(addr, self.pseudo, property)
299 },
300 CSSStyleOwner::Null => DOMString::new(),
301 }
302 }
303
304 fn get_property_value(&self, id: PropertyId) -> DOMString {
305 if matches!(self.owner, CSSStyleOwner::Null) {
306 return DOMString::new();
307 }
308
309 if self.readonly {
310 return self.get_computed_style(id);
312 }
313
314 let mut string = String::new();
315
316 self.owner.with_block(|pdb| {
317 pdb.property_value_to_css(&id, &mut string).unwrap();
318 });
319
320 DOMString::from(string)
321 }
322
323 fn set_property(
325 &self,
326 cx: &mut JSContext,
327 id: PropertyId,
328 value: DOMString,
329 priority: DOMString,
330 ) -> ErrorResult {
331 self.set_property_inner(cx, PotentiallyParsedPropertyId::Parsed(id), value, priority)
332 }
333
334 fn set_property_inner(
339 &self,
340 cx: &mut JSContext,
341 id: PotentiallyParsedPropertyId,
342 value: DOMString,
343 priority: DOMString,
344 ) -> ErrorResult {
345 if self.readonly {
347 return Err(Error::NoModificationAllowed(Some(
348 "This CSS style declaration is read-only".into(),
349 )));
350 }
351
352 let id = match id {
353 PotentiallyParsedPropertyId::Parsed(id) => {
354 if !id.enabled_for_all_content() {
355 return Ok(());
356 }
357
358 id
359 },
360 PotentiallyParsedPropertyId::NotParsed(unparsed) => {
361 match PropertyId::parse_enabled_for_all_content(&unparsed.str()) {
362 Ok(id) => id,
363 Err(..) => return Ok(()),
364 }
365 },
366 };
367 let base_url = UrlExtraData(self.owner.base_url().get_arc());
368 self.owner.mutate_associated_block(cx, |pdb, changed| {
369 if value.is_empty() {
372 *changed = remove_property(pdb, &id);
373 return Ok(());
374 }
375
376 let importance = match &*priority.str() {
379 "" => Importance::Normal,
380 p if p.eq_ignore_ascii_case("important") => Importance::Important,
381 _ => {
382 *changed = false;
383 return Ok(());
384 },
385 };
386
387 let window = self.owner.window();
389 let quirks_mode = window.Document().quirks_mode();
390 let mut declarations = SourcePropertyDeclaration::default();
391 let result = parse_one_declaration_into(
392 &mut declarations,
393 id,
394 &value.str(),
395 Origin::Author,
396 &base_url,
397 Some(window.css_error_reporter()),
398 ParsingMode::DEFAULT,
399 quirks_mode,
400 CssRuleType::Style,
401 );
402
403 match result {
405 Ok(()) => {},
406 Err(_) => {
407 *changed = false;
408 return Ok(());
409 },
410 }
411
412 let mut updates = Default::default();
413 *changed = pdb.prepare_for_update(&declarations, importance, &mut updates);
414
415 if !*changed {
416 return Ok(());
417 }
418
419 pdb.update(declarations.drain(), importance, &mut updates);
422
423 Ok(())
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 if let Some(css_font_face_descriptors) = self.downcast::<CSSFontFaceDescriptors>() {
484 css_font_face_descriptors.get_property_value(&property.str())
485 } else {
486 let Ok(id) = PropertyId::parse_enabled_for_all_content(&property.str()) else {
487 return DOMString::new();
488 };
489 self.get_property_value(id)
490 }
491 }
492
493 fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
495 if self.is::<CSSFontFaceDescriptors>() {
496 return DOMString::new();
499 }
500
501 if self.readonly {
502 return DOMString::new();
504 }
505 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
506 Ok(id) => id,
507 Err(..) => return DOMString::new(),
508 };
509
510 self.owner.with_block(|pdb| {
511 if pdb.property_priority(&id).important() {
512 DOMString::from_static("important")
513 } else {
514 DOMString::new()
516 }
517 })
518 }
519
520 fn SetProperty(
522 &self,
523 cx: &mut JSContext,
524 property: DOMString,
525 value: DOMString,
526 priority: DOMString,
527 ) -> ErrorResult {
528 self.set_property_inner(
529 cx,
530 PotentiallyParsedPropertyId::NotParsed(property),
531 value,
532 priority,
533 )
534 }
535
536 fn RemoveProperty(&self, cx: &mut JSContext, property: DOMString) -> Fallible<DOMString> {
538 if self.readonly {
540 return Err(Error::NoModificationAllowed(Some(
541 "This CSS style declaration is read-only".into(),
542 )));
543 }
544
545 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
546 Ok(id) => id,
547 Err(..) => return Ok(DOMString::new()),
548 };
549
550 let mut string = String::new();
551 self.owner.mutate_associated_block(cx, |pdb, changed| {
552 pdb.property_value_to_css(&id, &mut string).unwrap();
553 *changed = remove_property(pdb, &id);
554 });
555
556 Ok(DOMString::from(string))
558 }
559
560 fn CssFloat(&self) -> DOMString {
562 self.get_property_value(PropertyId::NonCustom(LonghandId::Float.into()))
563 }
564
565 fn SetCssFloat(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
567 self.set_property(
568 cx,
569 PropertyId::NonCustom(LonghandId::Float.into()),
570 value,
571 DOMString::new(),
572 )
573 }
574
575 fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
577 if matches!(self.owner, CSSStyleOwner::Null) {
578 return None;
579 }
580 if self.readonly {
581 let longhand = ENABLED_LONGHAND_PROPERTIES.get(index as usize)?;
584 return Some(DOMString::from(longhand.name()));
585 }
586 self.owner.with_block(|pdb| {
587 let declaration = pdb.declarations().get(index as usize)?;
588 Some(DOMString::from(declaration.id().name()))
589 })
590 }
591
592 fn CssText(&self) -> DOMString {
594 if self.readonly {
595 return DOMString::new();
597 }
598 self.owner.with_block(|pdb| {
599 let mut serialization = String::new();
600 pdb.to_css(&mut serialization).unwrap();
601 DOMString::from(serialization)
602 })
603 }
604
605 fn SetCssText(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
607 if self.readonly {
609 return Err(Error::NoModificationAllowed(Some(
610 "This CSS style declaration is read-only".into(),
611 )));
612 }
613
614 let window = self.owner.window();
615 let quirks_mode = window.Document().quirks_mode();
616 let base_url = UrlExtraData(self.owner.base_url().get_arc());
617 self.owner.mutate_associated_block(cx, |pdb, _changed| {
618 *pdb = parse_style_attribute(
620 &value.str(),
621 &base_url,
622 Some(window.css_error_reporter()),
623 quirks_mode,
624 CssRuleType::Style,
625 );
626 });
627
628 Ok(())
629 }
630
631 style::css_properties_accessors!(css_properties);
633}