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_with_cx};
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::window::Window;
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, cx: &mut JSContext, f: F) -> 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 element) => {
78 let document = element.owner_document();
79 let shared_lock = document.style_shared_author_lock();
80 let mut attribute_pdb = element.style_attribute().borrow_mut().take();
81
82 if element.needs_preserved_style_attribute_after_change() {
87 attribute_pdb = attribute_pdb.map(|attribute_pdb| {
88 let new_pdb = attribute_pdb.read_with(&shared_lock.read()).clone();
89 Arc::new(shared_lock.wrap(new_pdb))
90 });
91 }
92
93 let (attribute_pdb, result) = if let Some(attribute_pdb) = attribute_pdb {
94 let mut guard = shared_lock.write();
95 let writable_pdb = attribute_pdb.write_with(&mut guard);
96 let result = f(writable_pdb, &mut changed);
97 (attribute_pdb, result)
98 } else {
99 let mut new_pdb = PropertyDeclarationBlock::new();
100 let result = f(&mut new_pdb, &mut changed);
101
102 changed = !new_pdb.declarations().is_empty();
103 if !changed {
104 return result;
105 }
106
107 (Arc::new(shared_lock.wrap(new_pdb)), result)
108 };
109
110 if !changed {
115 *element.style_attribute().borrow_mut() = Some(attribute_pdb);
116 return result;
117 }
118
119 element.set_attribute(
120 cx,
121 &local_name!("style"),
122 AttrValue::from_declaration(attribute_pdb, shared_lock.clone()),
123 );
124 result
125 },
126 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
127 rule.parent_stylesheet().will_modify(cx);
128 let result = {
129 let mut guard = rule.shared_lock().write();
130 f(&mut *pdb.borrow().write_with(&mut guard), &mut changed)
131 };
132 if changed {
133 rule.parent_stylesheet().notify_invalidations();
134 }
135 result
136 },
137 }
138 }
139
140 fn with_block<F, R>(&self, f: F) -> R
141 where
142 F: FnOnce(&PropertyDeclarationBlock) -> R,
143 {
144 match *self {
145 CSSStyleOwner::Null => {
146 unreachable!("Should never call with_block for CSStyleOwner::Null")
147 },
148 CSSStyleOwner::Element(ref el) => match *el.style_attribute().borrow() {
149 Some(ref pdb) => {
150 let document = el.owner_document();
151 let guard = document.style_shared_author_lock().read();
152 f(pdb.read_with(&guard))
153 },
154 None => {
155 let pdb = PropertyDeclarationBlock::new();
156 f(&pdb)
157 },
158 },
159 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
160 let guard = rule.shared_lock().read();
161 f(pdb.borrow().read_with(&guard))
162 },
163 }
164 }
165
166 fn window(&self) -> DomRoot<Window> {
167 match *self {
168 CSSStyleOwner::Null => {
169 unreachable!("Should never try to access window of CSStyleOwner::Null")
170 },
171 CSSStyleOwner::Element(ref el) => el.owner_window(),
172 CSSStyleOwner::CSSRule(ref rule, _) => DomRoot::from_ref(rule.global().as_window()),
173 }
174 }
175
176 fn base_url(&self) -> ServoUrl {
177 match *self {
178 CSSStyleOwner::Null => {
179 unreachable!("Should never try to access base URL of CSStyleOwner::Null")
180 },
181 CSSStyleOwner::Element(ref el) => el.owner_document().base_url(),
182 CSSStyleOwner::CSSRule(ref rule, _) => ServoUrl::from({
183 let guard = rule.shared_lock().read();
184 rule.parent_stylesheet()
185 .style_stylesheet()
186 .contents(&guard)
187 .url_data
188 .0
189 .clone()
190 }),
191 }
192 }
193}
194
195#[derive(MallocSizeOf, PartialEq)]
196pub(crate) enum CSSModificationAccess {
197 ReadWrite,
198 Readonly,
199}
200
201macro_rules! css_properties(
202 ( $([$getter:ident, $setter:ident, $id:expr],)* ) => (
203 $(
204 fn $getter(&self) -> DOMString {
205 debug_assert!(
206 $id.enabled_for_all_content(),
207 "Someone forgot a #[Pref] annotation"
208 );
209 self.get_property_value($id)
210 }
211 fn $setter(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
212 debug_assert!(
213 $id.enabled_for_all_content(),
214 "Someone forgot a #[Pref] annotation"
215 );
216 self.set_property(cx, $id, value, DOMString::new())
217 }
218 )*
219 );
220);
221
222fn remove_property(decls: &mut PropertyDeclarationBlock, id: &PropertyId) -> bool {
223 let first_declaration = decls.first_declaration_to_remove(id);
224 let first_declaration = match first_declaration {
225 Some(i) => i,
226 None => return false,
227 };
228 decls.remove_property(id, first_declaration);
229 true
230}
231
232impl CSSStyleDeclaration {
233 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
234 pub(crate) fn new_inherited(
235 owner: CSSStyleOwner,
236 pseudo: Option<PseudoElement>,
237 modification_access: CSSModificationAccess,
238 ) -> CSSStyleDeclaration {
239 assert!(
242 !matches!(owner, CSSStyleOwner::Null) ||
243 modification_access == CSSModificationAccess::Readonly
244 );
245
246 CSSStyleDeclaration {
247 reflector_: Reflector::new(),
248 owner,
249 readonly: modification_access == CSSModificationAccess::Readonly,
250 pseudo,
251 }
252 }
253
254 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
255 pub(crate) fn new(
256 cx: &mut JSContext,
257 global: &Window,
258 owner: CSSStyleOwner,
259 pseudo: Option<PseudoElement>,
260 modification_access: CSSModificationAccess,
261 ) -> DomRoot<CSSStyleDeclaration> {
262 reflect_dom_object_with_cx(
263 Box::new(CSSStyleDeclaration::new_inherited(
264 owner,
265 pseudo,
266 modification_access,
267 )),
268 global,
269 cx,
270 )
271 }
272
273 pub(crate) fn update_property_declaration_block(
274 &self,
275 pdb: &Arc<Locked<PropertyDeclarationBlock>>,
276 ) {
277 if let CSSStyleOwner::CSSRule(_, pdb_cell) = &self.owner {
278 *pdb_cell.borrow_mut() = pdb.clone();
279 } else {
280 panic!("update_rule called on CSSStyleDeclaration with a Element owner");
281 }
282 }
283
284 fn get_computed_style(&self, property: PropertyId) -> DOMString {
285 match self.owner {
286 CSSStyleOwner::CSSRule(..) => {
287 panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner")
288 },
289 CSSStyleOwner::Element(ref el) => {
290 let node = el.upcast::<Node>();
291 if !node.is_connected() {
292 return DOMString::new();
293 }
294 let addr = node.to_trusted_node_address();
295 node.owner_window()
296 .resolved_style_query(addr, self.pseudo, property)
297 },
298 CSSStyleOwner::Null => DOMString::new(),
299 }
300 }
301
302 fn get_property_value(&self, id: PropertyId) -> DOMString {
303 if matches!(self.owner, CSSStyleOwner::Null) {
304 return DOMString::new();
305 }
306
307 if self.readonly {
308 return self.get_computed_style(id);
310 }
311
312 let mut string = String::new();
313
314 self.owner.with_block(|pdb| {
315 pdb.property_value_to_css(&id, &mut string).unwrap();
316 });
317
318 DOMString::from(string)
319 }
320
321 fn set_property(
323 &self,
324 cx: &mut JSContext,
325 id: PropertyId,
326 value: DOMString,
327 priority: DOMString,
328 ) -> ErrorResult {
329 self.set_property_inner(cx, PotentiallyParsedPropertyId::Parsed(id), value, priority)
330 }
331
332 fn set_property_inner(
337 &self,
338 cx: &mut JSContext,
339 id: PotentiallyParsedPropertyId,
340 value: DOMString,
341 priority: DOMString,
342 ) -> ErrorResult {
343 if self.readonly {
345 return Err(Error::NoModificationAllowed(None));
346 }
347
348 let id = match id {
349 PotentiallyParsedPropertyId::Parsed(id) => {
350 if !id.enabled_for_all_content() {
351 return Ok(());
352 }
353
354 id
355 },
356 PotentiallyParsedPropertyId::NotParsed(unparsed) => {
357 match PropertyId::parse_enabled_for_all_content(&unparsed.str()) {
358 Ok(id) => id,
359 Err(..) => return Ok(()),
360 }
361 },
362 };
363 let base_url = UrlExtraData(self.owner.base_url().get_arc());
364 self.owner.mutate_associated_block(cx, |pdb, changed| {
365 if value.is_empty() {
368 *changed = remove_property(pdb, &id);
369 return Ok(());
370 }
371
372 let importance = match &*priority.str() {
375 "" => Importance::Normal,
376 p if p.eq_ignore_ascii_case("important") => Importance::Important,
377 _ => {
378 *changed = false;
379 return Ok(());
380 },
381 };
382
383 let window = self.owner.window();
385 let quirks_mode = window.Document().quirks_mode();
386 let mut declarations = SourcePropertyDeclaration::default();
387 let result = parse_one_declaration_into(
388 &mut declarations,
389 id,
390 &value.str(),
391 Origin::Author,
392 &base_url,
393 Some(window.css_error_reporter()),
394 ParsingMode::DEFAULT,
395 quirks_mode,
396 CssRuleType::Style,
397 );
398
399 match result {
401 Ok(()) => {},
402 Err(_) => {
403 *changed = false;
404 return Ok(());
405 },
406 }
407
408 let mut updates = Default::default();
409 *changed = pdb.prepare_for_update(&declarations, importance, &mut updates);
410
411 if !*changed {
412 return Ok(());
413 }
414
415 pdb.update(declarations.drain(), importance, &mut updates);
418
419 Ok(())
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(cx, |pdb, changed| {
537 pdb.property_value_to_css(&id, &mut string).unwrap();
538 *changed = remove_property(pdb, &id);
539 });
540
541 Ok(DOMString::from(string))
543 }
544
545 fn CssFloat(&self) -> DOMString {
547 self.get_property_value(PropertyId::NonCustom(LonghandId::Float.into()))
548 }
549
550 fn SetCssFloat(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
552 self.set_property(
553 cx,
554 PropertyId::NonCustom(LonghandId::Float.into()),
555 value,
556 DOMString::new(),
557 )
558 }
559
560 fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
562 if matches!(self.owner, CSSStyleOwner::Null) {
563 return None;
564 }
565 if self.readonly {
566 let longhand = ENABLED_LONGHAND_PROPERTIES.get(index as usize)?;
569 return Some(DOMString::from(longhand.name()));
570 }
571 self.owner.with_block(|pdb| {
572 let declaration = pdb.declarations().get(index as usize)?;
573 Some(DOMString::from(declaration.id().name()))
574 })
575 }
576
577 fn CssText(&self) -> DOMString {
579 if self.readonly {
580 return DOMString::new();
582 }
583 self.owner.with_block(|pdb| {
584 let mut serialization = String::new();
585 pdb.to_css(&mut serialization).unwrap();
586 DOMString::from(serialization)
587 })
588 }
589
590 fn SetCssText(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
592 let window = self.owner.window();
593
594 if self.readonly {
596 return Err(Error::NoModificationAllowed(None));
597 }
598
599 let quirks_mode = window.Document().quirks_mode();
600 let base_url = UrlExtraData(self.owner.base_url().get_arc());
601 self.owner.mutate_associated_block(cx, |pdb, _changed| {
602 *pdb = parse_style_attribute(
604 &value.str(),
605 &base_url,
606 Some(window.css_error_reporter()),
607 quirks_mode,
608 CssRuleType::Style,
609 );
610 });
611
612 Ok(())
613 }
614
615 style::css_properties_accessors!(css_properties);
617}