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::window::Window;
36use crate::script_runtime::CanGc;
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 = element.style_attribute().borrow_mut().take();
82
83 if element.needs_preserved_style_attribute_after_change() {
88 attribute_pdb = attribute_pdb.map(|attribute_pdb| {
89 let new_pdb = attribute_pdb.read_with(&shared_lock.read()).clone();
90 Arc::new(shared_lock.wrap(new_pdb))
91 });
92 }
93
94 let (attribute_pdb, result) = if let Some(attribute_pdb) = attribute_pdb {
95 let mut guard = shared_lock.write();
96 let writable_pdb = attribute_pdb.write_with(&mut guard);
97 let result = f(writable_pdb, &mut changed);
98 (attribute_pdb, result)
99 } else {
100 let mut new_pdb = PropertyDeclarationBlock::new();
101 let result = f(&mut new_pdb, &mut changed);
102
103 changed = !new_pdb.declarations().is_empty();
104 if !changed {
105 return result;
106 }
107
108 (Arc::new(shared_lock.wrap(new_pdb)), result)
109 };
110
111 if !changed {
116 *element.style_attribute().borrow_mut() = Some(attribute_pdb);
117 return result;
118 }
119
120 element.set_attribute(
121 cx,
122 &local_name!("style"),
123 AttrValue::from_declaration(attribute_pdb, shared_lock.clone()),
124 );
125 result
126 },
127 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
128 rule.parent_stylesheet().will_modify();
129 let result = {
130 let mut guard = rule.shared_lock().write();
131 f(&mut *pdb.borrow().write_with(&mut guard), &mut changed)
132 };
133 if changed {
134 rule.parent_stylesheet().notify_invalidations();
135 }
136 result
137 },
138 }
139 }
140
141 fn with_block<F, R>(&self, f: F) -> R
142 where
143 F: FnOnce(&PropertyDeclarationBlock) -> R,
144 {
145 match *self {
146 CSSStyleOwner::Null => {
147 unreachable!("Should never call with_block for CSStyleOwner::Null")
148 },
149 CSSStyleOwner::Element(ref el) => match *el.style_attribute().borrow() {
150 Some(ref pdb) => {
151 let document = el.owner_document();
152 let guard = document.style_shared_author_lock().read();
153 f(pdb.read_with(&guard))
154 },
155 None => {
156 let pdb = PropertyDeclarationBlock::new();
157 f(&pdb)
158 },
159 },
160 CSSStyleOwner::CSSRule(ref rule, ref pdb) => {
161 let guard = rule.shared_lock().read();
162 f(pdb.borrow().read_with(&guard))
163 },
164 }
165 }
166
167 fn window(&self) -> DomRoot<Window> {
168 match *self {
169 CSSStyleOwner::Null => {
170 unreachable!("Should never try to access window of CSStyleOwner::Null")
171 },
172 CSSStyleOwner::Element(ref el) => el.owner_window(),
173 CSSStyleOwner::CSSRule(ref rule, _) => DomRoot::from_ref(rule.global().as_window()),
174 }
175 }
176
177 fn base_url(&self) -> ServoUrl {
178 match *self {
179 CSSStyleOwner::Null => {
180 unreachable!("Should never try to access base URL of CSStyleOwner::Null")
181 },
182 CSSStyleOwner::Element(ref el) => el.owner_document().base_url(),
183 CSSStyleOwner::CSSRule(ref rule, _) => ServoUrl::from({
184 let guard = rule.shared_lock().read();
185 rule.parent_stylesheet()
186 .style_stylesheet()
187 .contents(&guard)
188 .url_data
189 .0
190 .clone()
191 }),
192 }
193 }
194}
195
196#[derive(MallocSizeOf, PartialEq)]
197pub(crate) enum CSSModificationAccess {
198 ReadWrite,
199 Readonly,
200}
201
202macro_rules! css_properties(
203 ( $([$getter:ident, $setter:ident, $id:expr],)* ) => (
204 $(
205 fn $getter(&self) -> DOMString {
206 debug_assert!(
207 $id.enabled_for_all_content(),
208 "Someone forgot a #[Pref] annotation"
209 );
210 self.get_property_value($id)
211 }
212 fn $setter(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
213 debug_assert!(
214 $id.enabled_for_all_content(),
215 "Someone forgot a #[Pref] annotation"
216 );
217 self.set_property(cx, $id, value, DOMString::new())
218 }
219 )*
220 );
221);
222
223fn remove_property(decls: &mut PropertyDeclarationBlock, id: &PropertyId) -> bool {
224 let first_declaration = decls.first_declaration_to_remove(id);
225 let first_declaration = match first_declaration {
226 Some(i) => i,
227 None => return false,
228 };
229 decls.remove_property(id, first_declaration);
230 true
231}
232
233impl CSSStyleDeclaration {
234 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
235 pub(crate) fn new_inherited(
236 owner: CSSStyleOwner,
237 pseudo: Option<PseudoElement>,
238 modification_access: CSSModificationAccess,
239 ) -> CSSStyleDeclaration {
240 assert!(
243 !matches!(owner, CSSStyleOwner::Null) ||
244 modification_access == CSSModificationAccess::Readonly
245 );
246
247 CSSStyleDeclaration {
248 reflector_: Reflector::new(),
249 owner,
250 readonly: modification_access == CSSModificationAccess::Readonly,
251 pseudo,
252 }
253 }
254
255 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
256 pub(crate) fn new(
257 global: &Window,
258 owner: CSSStyleOwner,
259 pseudo: Option<PseudoElement>,
260 modification_access: CSSModificationAccess,
261 can_gc: CanGc,
262 ) -> DomRoot<CSSStyleDeclaration> {
263 reflect_dom_object(
264 Box::new(CSSStyleDeclaration::new_inherited(
265 owner,
266 pseudo,
267 modification_access,
268 )),
269 global,
270 can_gc,
271 )
272 }
273
274 pub(crate) fn update_property_declaration_block(
275 &self,
276 pdb: &Arc<Locked<PropertyDeclarationBlock>>,
277 ) {
278 if let CSSStyleOwner::CSSRule(_, pdb_cell) = &self.owner {
279 *pdb_cell.borrow_mut() = pdb.clone();
280 } else {
281 panic!("update_rule called on CSSStyleDeclaration with a Element owner");
282 }
283 }
284
285 fn get_computed_style(&self, property: PropertyId) -> DOMString {
286 match self.owner {
287 CSSStyleOwner::CSSRule(..) => {
288 panic!("get_computed_style called on CSSStyleDeclaration with a CSSRule owner")
289 },
290 CSSStyleOwner::Element(ref el) => {
291 let node = el.upcast::<Node>();
292 if !node.is_connected() {
293 return DOMString::new();
294 }
295 let addr = node.to_trusted_node_address();
296 node.owner_window()
297 .resolved_style_query(addr, self.pseudo, property)
298 },
299 CSSStyleOwner::Null => DOMString::new(),
300 }
301 }
302
303 fn get_property_value(&self, id: PropertyId) -> DOMString {
304 if matches!(self.owner, CSSStyleOwner::Null) {
305 return DOMString::new();
306 }
307
308 if self.readonly {
309 return self.get_computed_style(id);
311 }
312
313 let mut string = String::new();
314
315 self.owner.with_block(|pdb| {
316 pdb.property_value_to_css(&id, &mut string).unwrap();
317 });
318
319 DOMString::from(string)
320 }
321
322 fn set_property(
324 &self,
325 cx: &mut JSContext,
326 id: PropertyId,
327 value: DOMString,
328 priority: DOMString,
329 ) -> ErrorResult {
330 self.set_property_inner(cx, PotentiallyParsedPropertyId::Parsed(id), value, priority)
331 }
332
333 fn set_property_inner(
338 &self,
339 cx: &mut JSContext,
340 id: PotentiallyParsedPropertyId,
341 value: DOMString,
342 priority: DOMString,
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(cx, |pdb, changed| {
366 if value.is_empty() {
369 *changed = remove_property(pdb, &id);
370 return Ok(());
371 }
372
373 let importance = match &*priority.str() {
376 "" => Importance::Normal,
377 p if p.eq_ignore_ascii_case("important") => Importance::Important,
378 _ => {
379 *changed = false;
380 return Ok(());
381 },
382 };
383
384 let window = self.owner.window();
386 let quirks_mode = window.Document().quirks_mode();
387 let mut declarations = SourcePropertyDeclaration::default();
388 let result = parse_one_declaration_into(
389 &mut declarations,
390 id,
391 &value.str(),
392 Origin::Author,
393 &base_url,
394 Some(window.css_error_reporter()),
395 ParsingMode::DEFAULT,
396 quirks_mode,
397 CssRuleType::Style,
398 );
399
400 match result {
402 Ok(()) => {},
403 Err(_) => {
404 *changed = false;
405 return Ok(());
406 },
407 }
408
409 let mut updates = Default::default();
410 *changed = pdb.prepare_for_update(&declarations, importance, &mut updates);
411
412 if !*changed {
413 return Ok(());
414 }
415
416 pdb.update(declarations.drain(), importance, &mut updates);
419
420 Ok(())
421 })
422 }
423}
424
425pub(crate) static ENABLED_LONGHAND_PROPERTIES: LazyLock<Vec<LonghandId>> = LazyLock::new(|| {
426 let mut enabled_longhands: Vec<LonghandId> = ShorthandId::All.longhands().collect();
429 if PropertyId::NonCustom(LonghandId::Direction.into()).enabled_for_all_content() {
430 enabled_longhands.push(LonghandId::Direction);
431 }
432 if PropertyId::NonCustom(LonghandId::UnicodeBidi.into()).enabled_for_all_content() {
433 enabled_longhands.push(LonghandId::UnicodeBidi);
434 }
435
436 enabled_longhands.sort_unstable_by(|a, b| {
438 let a = a.name();
439 let b = b.name();
440 let is_a_vendor_prefixed = a.starts_with('-');
441 let is_b_vendor_prefixed = b.starts_with('-');
442 if is_a_vendor_prefixed == is_b_vendor_prefixed {
443 a.partial_cmp(b).unwrap()
444 } else if is_b_vendor_prefixed {
445 Ordering::Less
446 } else {
447 Ordering::Greater
448 }
449 });
450 enabled_longhands
451});
452
453enum PotentiallyParsedPropertyId {
454 Parsed(PropertyId),
455 NotParsed(DOMString),
456}
457
458impl CSSStyleDeclarationMethods<crate::DomTypeHolder> for CSSStyleDeclaration {
459 fn Length(&self) -> u32 {
461 if matches!(self.owner, CSSStyleOwner::Null) {
462 return 0;
463 }
464
465 if self.readonly {
466 return ENABLED_LONGHAND_PROPERTIES.len() as u32;
469 }
470 self.owner.with_block(|pdb| pdb.declarations().len() as u32)
471 }
472
473 fn Item(&self, index: u32) -> DOMString {
475 self.IndexedGetter(index).unwrap_or_default()
476 }
477
478 fn GetPropertyValue(&self, property: DOMString) -> DOMString {
480 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
481 Ok(id) => id,
482 Err(..) => return DOMString::new(),
483 };
484 self.get_property_value(id)
485 }
486
487 fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
489 if self.readonly {
490 return DOMString::new();
492 }
493 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
494 Ok(id) => id,
495 Err(..) => return DOMString::new(),
496 };
497
498 self.owner.with_block(|pdb| {
499 if pdb.property_priority(&id).important() {
500 DOMString::from("important")
501 } else {
502 DOMString::new()
504 }
505 })
506 }
507
508 fn SetProperty(
510 &self,
511 cx: &mut JSContext,
512 property: DOMString,
513 value: DOMString,
514 priority: DOMString,
515 ) -> ErrorResult {
516 self.set_property_inner(
517 cx,
518 PotentiallyParsedPropertyId::NotParsed(property),
519 value,
520 priority,
521 )
522 }
523
524 fn RemoveProperty(&self, cx: &mut JSContext, property: DOMString) -> Fallible<DOMString> {
526 if self.readonly {
528 return Err(Error::NoModificationAllowed(None));
529 }
530
531 let id = match PropertyId::parse_enabled_for_all_content(&property.str()) {
532 Ok(id) => id,
533 Err(..) => return Ok(DOMString::new()),
534 };
535
536 let mut string = String::new();
537 self.owner.mutate_associated_block(cx, |pdb, changed| {
538 pdb.property_value_to_css(&id, &mut string).unwrap();
539 *changed = remove_property(pdb, &id);
540 });
541
542 Ok(DOMString::from(string))
544 }
545
546 fn CssFloat(&self) -> DOMString {
548 self.get_property_value(PropertyId::NonCustom(LonghandId::Float.into()))
549 }
550
551 fn SetCssFloat(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
553 self.set_property(
554 cx,
555 PropertyId::NonCustom(LonghandId::Float.into()),
556 value,
557 DOMString::new(),
558 )
559 }
560
561 fn IndexedGetter(&self, index: u32) -> Option<DOMString> {
563 if matches!(self.owner, CSSStyleOwner::Null) {
564 return None;
565 }
566 if self.readonly {
567 let longhand = ENABLED_LONGHAND_PROPERTIES.get(index as usize)?;
570 return Some(DOMString::from(longhand.name()));
571 }
572 self.owner.with_block(|pdb| {
573 let declaration = pdb.declarations().get(index as usize)?;
574 Some(DOMString::from(declaration.id().name()))
575 })
576 }
577
578 fn CssText(&self) -> DOMString {
580 if self.readonly {
581 return DOMString::new();
583 }
584 self.owner.with_block(|pdb| {
585 let mut serialization = String::new();
586 pdb.to_css(&mut serialization).unwrap();
587 DOMString::from(serialization)
588 })
589 }
590
591 fn SetCssText(&self, cx: &mut JSContext, value: DOMString) -> ErrorResult {
593 let window = self.owner.window();
594
595 if self.readonly {
597 return Err(Error::NoModificationAllowed(None));
598 }
599
600 let quirks_mode = window.Document().quirks_mode();
601 let base_url = UrlExtraData(self.owner.base_url().get_arc());
602 self.owner.mutate_associated_block(cx, |pdb, _changed| {
603 *pdb = parse_style_attribute(
605 &value.str(),
606 &base_url,
607 Some(window.css_error_reporter()),
608 quirks_mode,
609 CssRuleType::Style,
610 );
611 });
612
613 Ok(())
614 }
615
616 style::css_properties_accessors!(css_properties);
618}