1use blitz_traits::node_id::NodeId;
5use std::ptr::NonNull;
6use std::sync::atomic::Ordering;
7
8use crate::StyleThreading;
9use crate::layout::damage::compute_layout_damage;
10use crate::node::Node;
11use crate::node::NodeData;
12use markup5ever::{LocalName, LocalNameStaticSet, Namespace, NamespaceStaticSet, local_name};
13use selectors::bloom::BLOOM_HASH_MASK;
14use selectors::{
15 Element, OpaqueElement,
16 attr::{AttrSelectorOperation, NamespaceConstraint},
17 matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode},
18 sink::Push,
19};
20use style::CaseSensitivityExt;
21use style::animation::AnimationSetKey;
22use style::animation::AnimationState;
23use style::applicable_declarations::ApplicableDeclarationBlock;
24use style::bloom::each_relevant_element_hash;
25use style::color::AbsoluteColor;
26use style::data::{ElementDataMut, ElementDataRef};
27use style::global_style_data::STYLE_THREAD_POOL;
28use style::invalidation::element::restyle_hints::RestyleHint;
29use style::properties::ComputedValues;
30use style::properties::{Importance, PropertyDeclaration};
31use style::rule_tree::CascadeLevel;
32use style::rule_tree::CascadeOrigin;
33use style::selector_parser::PseudoElement;
34use style::selector_parser::RestyleDamage;
35use style::stylesheets::layer_rule::LayerOrder;
36use style::stylesheets::scope_rule::ImplicitScopeRoot;
37use style::values::AtomString;
38use style::values::specified::NoCalcPercentage;
39use style::{
40 Atom,
41 context::{
42 QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters,
43 SharedStyleContext, StyleContext,
44 },
45 dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot},
46 global_style_data::GLOBAL_STYLE_DATA,
47 properties::PropertyDeclarationBlock,
48 selector_parser::{NonTSPseudoClass, SelectorImpl},
49 servo_arc::{Arc, ArcBorrow},
50 shared_lock::{Locked, SharedRwLock, StylesheetGuards},
51 thread_state::ThreadState,
52 traversal::{DomTraversal, PerLevelTraversalData},
53 traversal_flags::TraversalFlags,
54 values::{AtomIdent, GenericAtomIdent},
55};
56use style_dom::ElementState;
57
58use style::values::computed::text::TextAlign as StyloTextAlign;
59
60impl crate::document::BaseDocument {
61 pub fn resolve_stylist(&mut self, now: f64) {
62 style::thread_state::enter(ThreadState::LAYOUT);
63
64 #[cfg(feature = "shadow-dom")]
65 {
66 self.compute_flattened_trees();
69 self.flush_shadow_styles();
70 }
71
72 let guard = &self.guard;
73 let guards = StylesheetGuards {
74 author: &guard.read(),
75 ua_or_user: &guard.read(),
76 };
77
78 let root = TDocument::as_node(&&self.nodes[self.root_node_id])
79 .first_element_child()
80 .unwrap()
81 .as_element()
82 .unwrap();
83
84 self.stylist
85 .flush(&guards)
86 .process_style(root, Some(&self.snapshots));
87
88 let mut sets = self.animations.sets.write();
90 for (key, set) in sets.iter_mut() {
91 let node_id = NodeId::from_u64(key.node.id() as u64);
92
93 let in_document = self
100 .nodes
101 .get(node_id)
102 .is_some_and(|node| node.flags.is_in_document());
103 if !in_document {
104 set.animations.clear();
105 set.transitions.clear();
106 continue;
107 }
108
109 self.nodes[node_id].set_restyle_hint(RestyleHint::RESTYLE_SELF);
110
111 for animation in set.animations.iter_mut() {
112 if animation.state == AnimationState::Pending && animation.started_at <= now {
113 animation.state = AnimationState::Running;
114 }
115 animation.iterate_if_necessary(now);
116
117 if animation.state == AnimationState::Running && animation.has_ended(now) {
118 animation.state = AnimationState::Finished;
119 }
120 }
121
122 for transition in set.transitions.iter_mut() {
123 if transition.state == AnimationState::Pending && transition.start_time <= now {
124 transition.state = AnimationState::Running;
125 }
126 if transition.state == AnimationState::Running && transition.has_ended(now) {
127 transition.state = AnimationState::Finished;
128 }
129 }
130 }
131 drop(sets);
132
133 let context = SharedStyleContext {
135 traversal_flags: TraversalFlags::empty(),
136 stylist: &self.stylist,
137 options: GLOBAL_STYLE_DATA.options.clone(),
138 guards,
139 visited_styles_enabled: false,
140 animations: self.animations.clone(),
141 current_time_for_animations: now,
142 snapshot_map: &self.snapshots,
143 registered_speculative_painters: &RegisteredPaintersImpl,
144 };
145
146 let root = self.root_element();
148 let token = RecalcStyle::pre_traverse(root, &context);
150
151 if token.should_traverse() {
152 let traverser = RecalcStyle::new(context);
154 let pool_guard = matches!(self.style_threading, StyleThreading::Parallel)
156 .then(|| STYLE_THREAD_POOL.pool());
157 let rayon_pool = pool_guard.as_ref().and_then(|g| g.as_ref());
158 style::driver::traverse_dom(&traverser, token, rayon_pool);
159 }
160
161 for opaque in self.snapshots.keys() {
162 let id = NodeId::from_u64(opaque.id() as u64);
163 if let Some(node) = self.nodes.get_mut(id) {
164 node.set_has_snapshot(false);
165 }
166 }
167 self.snapshots.clear();
168
169 let mut sets = self.animations.sets.write();
170 for set in sets.values_mut() {
171 set.clear_canceled_animations();
172 for animation in set.animations.iter_mut() {
173 animation.is_new = false;
174 }
175 for transition in set.transitions.iter_mut() {
176 transition.is_new = false;
177 }
178 }
179 sets.retain(|_, state| !state.is_empty());
180 self.has_active_animations = sets.values().any(|state| state.needs_animation_ticks());
181
182 self.stylist.rule_tree().maybe_gc();
184
185 style::thread_state::exit(ThreadState::LAYOUT);
186 }
187}
188
189type BlitzNode<'a> = &'a Node;
194
195impl<'a> TDocument for BlitzNode<'a> {
196 type ConcreteNode = BlitzNode<'a>;
197
198 fn as_node(&self) -> Self::ConcreteNode {
199 self
200 }
201
202 fn is_html_document(&self) -> bool {
203 Node::is_html_document(self)
204 }
205
206 fn quirks_mode(&self) -> QuirksMode {
207 match &self.data {
208 NodeData::Document(data) if data.quirks_mode == 1 => QuirksMode::LimitedQuirks,
209 NodeData::Document(data) if data.quirks_mode == 2 => QuirksMode::Quirks,
210 _ => QuirksMode::NoQuirks,
211 }
212 }
213
214 fn shared_lock(&self) -> &SharedRwLock {
215 self.guard()
216 }
217}
218
219impl NodeInfo for BlitzNode<'_> {
220 fn is_element(&self) -> bool {
221 Node::is_element(self)
222 }
223
224 fn is_text_node(&self) -> bool {
225 Node::is_text_node(self)
226 }
227}
228
229impl<'a> TShadowRoot for BlitzNode<'a> {
230 type ConcreteNode = BlitzNode<'a>;
231
232 fn as_node(&self) -> Self::ConcreteNode {
233 self
234 }
235
236 fn host(&self) -> <Self::ConcreteNode as TNode>::ConcreteElement {
237 self.with(
238 self.shadow_root_data()
239 .expect("TShadowRoot requires a shadow root")
240 .host,
241 )
242 }
243
244 fn style_data<'b>(&self) -> Option<&'b style::stylist::CascadeData>
245 where
246 Self: 'b,
247 {
248 self.shadow_root_data()
249 .and_then(|root| root.cascade_data.as_deref())
250 }
251}
252
253impl<'a> TNode for BlitzNode<'a> {
255 type ConcreteElement = BlitzNode<'a>;
256 type ConcreteDocument = BlitzNode<'a>;
257 type ConcreteShadowRoot = BlitzNode<'a>;
258
259 fn parent_node(&self) -> Option<Self> {
260 self.parent.map(|id| self.with(id))
261 }
262
263 fn first_child(&self) -> Option<Self> {
264 self.layout_dom_children().first().map(|id| self.with(*id))
265 }
266
267 fn last_child(&self) -> Option<Self> {
268 self.layout_dom_children().last().map(|id| self.with(*id))
269 }
270
271 fn prev_sibling(&self) -> Option<Self> {
272 let parent = self.flattened_parent()?;
273 let children = parent.layout_dom_children();
274 let index = children.iter().position(|id| *id == self.id)?;
275 children.get(index.checked_sub(1)?).map(|id| self.with(*id))
276 }
277
278 fn next_sibling(&self) -> Option<Self> {
279 let parent = self.flattened_parent()?;
280 let children = parent.layout_dom_children();
281 let index = children.iter().position(|id| *id == self.id)?;
282 children.get(index + 1).map(|id| self.with(*id))
283 }
284
285 fn owner_doc(&self) -> Self::ConcreteDocument {
286 if let Some(owner) = self.owner_document {
287 return self.with(owner);
288 }
289 let mut node = *self;
291 while let Some(parent_id) = node.parent {
292 node = node.with(parent_id);
293 }
294 node
295 }
296
297 fn is_in_document(&self) -> bool {
298 true
299 }
300
301 fn traversal_parent(&self) -> Option<Self::ConcreteElement> {
302 self.flattened_parent().and_then(|node| node.as_element())
303 }
304
305 fn opaque(&self) -> OpaqueNode {
306 OpaqueNode(self.id.as_u64() as usize)
307 }
308
309 fn debug_id(self) -> usize {
310 self.id.as_u64() as usize
311 }
312
313 fn as_element(&self) -> Option<Self::ConcreteElement> {
314 match self.data {
315 NodeData::Element { .. } => Some(self),
316 _ => None,
317 }
318 }
319
320 fn as_document(&self) -> Option<Self::ConcreteDocument> {
321 match self.data {
322 NodeData::Document(_) => Some(self),
323 _ => None,
324 }
325 }
326
327 fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> {
328 self.is_shadow_root().then_some(*self)
329 }
330}
331
332impl selectors::Element for BlitzNode<'_> {
333 type Impl = SelectorImpl;
334
335 fn opaque(&self) -> selectors::OpaqueElement {
336 let non_null =
344 NonNull::new((self.id.as_u64() as usize).wrapping_add(1) as *mut ()).unwrap();
345 OpaqueElement::from_non_null_ptr(non_null)
346 }
347
348 fn parent_element(&self) -> Option<Self> {
349 self.parent_node().and_then(|parent| parent.as_element())
352 }
353
354 fn parent_node_is_shadow_root(&self) -> bool {
355 self.parent_node()
356 .is_some_and(|parent| parent.is_shadow_root())
357 }
358
359 fn containing_shadow_host(&self) -> Option<Self> {
360 let mut node = *self;
361 while let Some(parent) = node.parent_node() {
362 if let Some(root) = parent.shadow_root_data() {
363 return Some(self.with(root.host));
364 }
365 node = parent;
366 }
367 None
368 }
369
370 fn is_pseudo_element(&self) -> bool {
371 matches!(self.data, NodeData::AnonymousBlock(_))
372 }
373
374 fn prev_sibling_element(&self) -> Option<Self> {
377 let mut n = 1;
378 while let Some(node) = self.backward(n) {
379 if node.is_element() {
380 return Some(node);
381 }
382 n += 1;
383 }
384
385 None
386 }
387
388 fn next_sibling_element(&self) -> Option<Self> {
389 let mut n = 1;
390 while let Some(node) = self.forward(n) {
391 if node.is_element() {
392 return Some(node);
393 }
394 n += 1;
395 }
396
397 None
398 }
399
400 fn first_element_child(&self) -> Option<Self> {
401 self.children
404 .iter()
405 .find_map(|id| self.with(*id).as_element())
406 }
407
408 fn is_html_element_in_html_document(&self) -> bool {
409 Node::is_html_document(self)
410 && self
411 .element_data()
412 .is_some_and(|element| element.name.ns == markup5ever::ns!(html))
413 }
414
415 fn has_local_name(&self, local_name: &LocalName) -> bool {
416 self.data.is_element_with_tag_name(local_name)
417 }
418
419 fn has_namespace(&self, ns: &Namespace) -> bool {
420 self.element_data().expect("Not an element").name.ns == *ns
421 }
422
423 fn is_same_type(&self, other: &Self) -> bool {
424 self.local_name() == other.local_name() && self.namespace() == other.namespace()
425 }
426
427 fn attr_matches(
428 &self,
429 _ns: &NamespaceConstraint<&GenericAtomIdent<NamespaceStaticSet>>,
430 local_name: &GenericAtomIdent<LocalNameStaticSet>,
431 operation: &AttrSelectorOperation<&AtomString>,
432 ) -> bool {
433 match self.data.attr(local_name.0.clone()) {
434 None => false,
435 Some(attr_value) => operation.eval_str(attr_value),
436 }
437 }
438
439 fn match_non_ts_pseudo_class(
440 &self,
441 pseudo_class: &<Self::Impl as selectors::SelectorImpl>::NonTSPseudoClass,
442 _context: &mut MatchingContext<Self::Impl>,
443 ) -> bool {
444 match *pseudo_class {
445 NonTSPseudoClass::Active => self.element_state().contains(ElementState::ACTIVE),
446 NonTSPseudoClass::AnyLink => self
447 .data
448 .downcast_element()
449 .map(|elem| {
450 (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
451 && elem.attr(local_name!("href")).is_some()
452 })
453 .unwrap_or(false),
454 NonTSPseudoClass::Checked => self
455 .data
456 .downcast_element()
457 .and_then(|elem| elem.checkbox_input_checked())
458 .unwrap_or(false),
459 NonTSPseudoClass::Valid => false,
460 NonTSPseudoClass::Invalid => false,
461 NonTSPseudoClass::Defined => false,
462 NonTSPseudoClass::Disabled => self.element_state().contains(ElementState::DISABLED),
463 NonTSPseudoClass::Enabled => self.element_state().contains(ElementState::ENABLED),
464 NonTSPseudoClass::Focus => self.element_state().contains(ElementState::FOCUS),
465 NonTSPseudoClass::FocusWithin => false,
466 NonTSPseudoClass::FocusVisible => false,
467 NonTSPseudoClass::Fullscreen => false,
468 NonTSPseudoClass::Hover => self.element_state().contains(ElementState::HOVER),
469 NonTSPseudoClass::Indeterminate => false,
470 NonTSPseudoClass::Lang(_) => false,
471 NonTSPseudoClass::CustomState(_) => false,
472 NonTSPseudoClass::Link => self
473 .data
474 .downcast_element()
475 .map(|elem| {
476 (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
477 && elem.attr(local_name!("href")).is_some()
478 })
479 .unwrap_or(false),
480 NonTSPseudoClass::PlaceholderShown => false,
481 NonTSPseudoClass::ReadWrite => false,
482 NonTSPseudoClass::ReadOnly => false,
483 NonTSPseudoClass::ServoNonZeroBorder => false,
484 NonTSPseudoClass::Target => false,
485 NonTSPseudoClass::Visited => false,
486 NonTSPseudoClass::Autofill => false,
487 NonTSPseudoClass::Default => false,
488
489 NonTSPseudoClass::InRange => false,
490 NonTSPseudoClass::Modal => false,
491 NonTSPseudoClass::Open => false,
492 NonTSPseudoClass::Optional => false,
493 NonTSPseudoClass::OutOfRange => false,
494 NonTSPseudoClass::PopoverOpen => false,
495 NonTSPseudoClass::Required => false,
496 NonTSPseudoClass::UserInvalid => false,
497 NonTSPseudoClass::UserValid => false,
498 NonTSPseudoClass::MozMeterOptimum => false,
499 NonTSPseudoClass::MozMeterSubOptimum => false,
500 NonTSPseudoClass::MozMeterSubSubOptimum => false,
501 }
502 }
503
504 fn match_pseudo_element(
505 &self,
506 pe: &PseudoElement,
507 _context: &mut MatchingContext<Self::Impl>,
508 ) -> bool {
509 let pseudo = match self.stylo_element_data_opt().and_then(|s| s.get()) {
510 Some(el) => el
511 .styles
512 .get_primary()
513 .and_then(|s| s.pseudo())
514 .or(match &self.data {
515 NodeData::AnonymousBlock(_) => Some(PseudoElement::ServoAnonymousBox),
516 _ => None,
517 }),
518 None => None,
519 };
520
521 pseudo.is_some_and(|psuedo| psuedo == *pe)
522 }
523
524 fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
525 let self_flags = flags.for_self();
527 if !self_flags.is_empty() {
528 self.selector_flags()
529 .set(self.selector_flags().get() | self_flags);
530 }
531
532 let parent_flags = flags.for_parent();
534 if !parent_flags.is_empty() {
535 if let Some(parent) = self.parent_node() {
536 parent
537 .selector_flags()
538 .set(parent.selector_flags().get() | parent_flags);
539 }
540 }
541 }
542
543 fn is_link(&self) -> bool {
544 self.data.is_element_with_tag_name(&local_name!("a"))
545 }
546
547 fn is_html_slot_element(&self) -> bool {
548 self.element_data().is_some_and(|element| {
549 element.name.ns == markup5ever::ns!(html) && element.name.local == local_name!("slot")
550 })
551 }
552
553 fn assigned_slot(&self) -> Option<Self> {
554 #[cfg(feature = "shadow-dom")]
555 {
556 self.assigned_slot.map(|id| self.with(id))
557 }
558 #[cfg(not(feature = "shadow-dom"))]
559 {
560 None
561 }
562 }
563
564 fn has_id(
565 &self,
566 id: &<Self::Impl as selectors::SelectorImpl>::Identifier,
567 case_sensitivity: selectors::attr::CaseSensitivity,
568 ) -> bool {
569 self.element_data()
570 .and_then(|data| data.id.as_ref())
571 .map(|id_attr| case_sensitivity.eq_atom(id_attr, id))
572 .unwrap_or(false)
573 }
574
575 fn has_class(
576 &self,
577 search_name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
578 case_sensitivity: selectors::attr::CaseSensitivity,
579 ) -> bool {
580 let class_attr = self.data.attr(local_name!("class"));
581 if let Some(class_attr) = class_attr {
582 for pheme in class_attr.split_ascii_whitespace() {
584 let atom = Atom::from(pheme);
585 if case_sensitivity.eq_atom(&atom, search_name) {
586 return true;
587 }
588 }
589 }
590
591 false
592 }
593
594 fn imported_part(
595 &self,
596 name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
597 ) -> Option<<Self::Impl as selectors::SelectorImpl>::Identifier> {
598 self.attr(markup5ever::LocalName::from("exportparts"))?
599 .split(',')
600 .find_map(|mapping| {
601 let mut names = mapping.trim().split(':');
602 let inner = names.next()?.trim();
603 let outer = names.next().map(str::trim).unwrap_or(inner);
604 if names.next().is_none() && outer == name.0.as_ref() {
605 Some(GenericAtomIdent(Atom::from(inner)))
606 } else {
607 None
608 }
609 })
610 }
611
612 fn is_part(&self, name: &<Self::Impl as selectors::SelectorImpl>::Identifier) -> bool {
613 self.attr(markup5ever::LocalName::from("part"))
614 .is_some_and(|parts| {
615 parts
616 .split_ascii_whitespace()
617 .any(|part| part == name.0.as_ref())
618 })
619 }
620
621 fn is_empty(&self) -> bool {
622 self.children.is_empty()
623 }
624
625 fn is_root(&self) -> bool {
626 self.parent_node()
627 .is_some_and(|parent| parent.as_document().is_some())
628 }
629
630 fn has_custom_state(
631 &self,
632 _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
633 ) -> bool {
634 false
635 }
636
637 fn add_element_unique_hashes(&self, filter: &mut selectors::bloom::BloomFilter) -> bool {
638 each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
639 true
640 }
641}
642
643impl<'a> TElement for BlitzNode<'a> {
644 type ConcreteNode = BlitzNode<'a>;
645
646 type TraversalChildrenIterator = Traverser<'a>;
647
648 fn as_node(&self) -> Self::ConcreteNode {
649 self
650 }
651
652 fn implicit_scope_for_sheet_in_shadow_root(
653 _opaque_host: OpaqueElement,
654 _sheet_index: usize,
655 ) -> Option<ImplicitScopeRoot> {
656 None
659 }
660
661 fn traversal_children(&self) -> style::dom::LayoutIterator<Self::TraversalChildrenIterator> {
662 LayoutIterator(Traverser {
663 parent: self,
665 child_index: 0,
666 })
667 }
668
669 fn inheritance_parent(&self) -> Option<Self> {
670 TNode::traversal_parent(self)
673 }
674
675 fn is_html_element(&self) -> bool {
676 self.is_element()
677 }
678
679 fn is_mathml_element(&self) -> bool {
681 false
682 }
683
684 fn is_svg_element(&self) -> bool {
686 false
687 }
688
689 fn style_attribute(&self) -> Option<ArcBorrow<'_, Locked<PropertyDeclarationBlock>>> {
690 self.element_data()
691 .expect("Not an element")
692 .style_attribute
693 .as_ref()
694 .map(|f| f.borrow_arc())
695 }
696
697 fn state(&self) -> ElementState {
698 *self.element_state()
699 }
700
701 fn has_part_attr(&self) -> bool {
702 self.attr(markup5ever::LocalName::from("part")).is_some()
703 }
704
705 fn exports_any_part(&self) -> bool {
706 self.attr(markup5ever::LocalName::from("exportparts"))
707 .is_some()
708 }
709
710 fn each_part<F>(&self, mut callback: F)
711 where
712 F: FnMut(&AtomIdent),
713 {
714 if let Some(parts) = self.attr(markup5ever::LocalName::from("part")) {
715 for part in parts.split_ascii_whitespace() {
716 callback(&GenericAtomIdent(Atom::from(part)));
717 }
718 }
719 }
720
721 fn each_exported_part<F>(&self, name: &AtomIdent, mut callback: F)
722 where
723 F: FnMut(&AtomIdent),
724 {
725 if let Some(parts) = self.attr(markup5ever::LocalName::from("exportparts")) {
726 for mapping in parts.split(',') {
727 let mut names = mapping.trim().split(':');
728 let Some(inner) = names.next().map(str::trim) else {
729 continue;
730 };
731 let outer = names.next().map(str::trim).unwrap_or(inner);
732 if names.next().is_none() && inner == name.0.as_ref() && !outer.is_empty() {
733 callback(&GenericAtomIdent(Atom::from(outer)));
734 }
735 }
736 }
737 }
738
739 fn id(&self) -> Option<&style::Atom> {
740 self.element_data().and_then(|data| data.id.as_ref())
741 }
742
743 fn each_class<F>(&self, mut callback: F)
744 where
745 F: FnMut(&style::values::AtomIdent),
746 {
747 let class_attr = self.data.attr(local_name!("class"));
748 if let Some(class_attr) = class_attr {
749 for pheme in class_attr.split_ascii_whitespace() {
751 let atom = Atom::from(pheme); callback(AtomIdent::cast(&atom));
753 }
754 }
755 }
756
757 fn each_attr_name<F>(&self, mut callback: F)
758 where
759 F: FnMut(&style::LocalName),
760 {
761 if let Some(attrs) = self.data.attrs() {
762 for attr in attrs.iter() {
763 callback(&GenericAtomIdent(attr.name.local.clone()));
764 }
765 }
766 }
767
768 fn has_dirty_descendants(&self) -> bool {
769 Node::has_dirty_descendants(self)
770 }
771
772 fn has_snapshot(&self) -> bool {
773 Node::has_snapshot(self)
774 }
775
776 fn handled_snapshot(&self) -> bool {
777 self.snapshot_handled().load(Ordering::SeqCst)
778 }
779
780 unsafe fn set_handled_snapshot(&self) {
781 self.snapshot_handled().store(true, Ordering::SeqCst);
782 }
783
784 unsafe fn set_dirty_descendants(&self) {
785 Node::set_dirty_descendants(self);
786 Node::mark_ancestors_dirty(self);
787 }
788
789 unsafe fn unset_dirty_descendants(&self) {
790 Node::unset_dirty_descendants(self);
791 }
792
793 fn store_children_to_process(&self, n: isize) {
794 self.children_to_process.store(n, Ordering::Relaxed);
795 }
796
797 fn did_process_child(&self) -> isize {
798 self.children_to_process.fetch_sub(1, Ordering::AcqRel) - 1
799 }
800
801 unsafe fn ensure_data(&self) -> ElementDataMut<'_> {
802 unsafe { self.stylo_element_data().ensure_init() }
804 }
805
806 unsafe fn clear_data(&self) {}
830
831 fn has_data(&self) -> bool {
832 self.stylo_element_data_opt().is_some_and(|s| s.has_data())
833 }
834
835 fn borrow_data(&self) -> Option<ElementDataRef<'_>> {
836 self.stylo_element_data_opt().and_then(|s| s.get())
837 }
838
839 fn mutate_data(&self) -> Option<ElementDataMut<'_>> {
840 unsafe { self.stylo_element_data().unsafe_stylo_only_mut() }
841 }
842
843 fn skip_item_display_fixup(&self) -> bool {
844 false
845 }
846
847 fn may_have_animations(&self) -> bool {
848 true
849 }
850
851 fn has_animations(&self, context: &SharedStyleContext) -> bool {
852 self.has_css_animations(context, None) || self.has_css_transitions(context, None)
853 }
854
855 fn has_css_animations(
856 &self,
857 context: &SharedStyleContext,
858 pseudo_element: Option<PseudoElement>,
859 ) -> bool {
860 let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
861 context.animations.has_active_animations(&key)
862 }
863
864 fn has_css_transitions(
865 &self,
866 context: &SharedStyleContext,
867 pseudo_element: Option<PseudoElement>,
868 ) -> bool {
869 let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
870 context.animations.has_active_transitions(&key)
871 }
872
873 fn animation_rule(
874 &self,
875 context: &SharedStyleContext,
876 ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
877 let opaque = TNode::opaque(&TElement::as_node(self));
878 context.animations.get_animation_declarations(
879 &AnimationSetKey::new_for_non_pseudo(opaque),
880 context.current_time_for_animations,
881 self.guard(),
882 )
883 }
884
885 fn transition_rule(
886 &self,
887 context: &SharedStyleContext,
888 ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
889 let opaque = TNode::opaque(&TElement::as_node(self));
890 context.animations.get_transition_declarations(
891 &AnimationSetKey::new_for_non_pseudo(opaque),
892 context.current_time_for_animations,
893 self.guard(),
894 )
895 }
896
897 fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
898 self.shadow_root_id().map(|id| self.with(id))
899 }
900
901 fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
902 let mut node = *self;
903 while let Some(parent) = node.parent_node() {
904 if parent.is_shadow_root() {
905 return Some(parent);
906 }
907 node = parent;
908 }
909 None
910 }
911
912 fn get_attr(&self, attr: &style::LocalName, _ns: &style::Namespace) -> Option<String> {
913 self.attr(attr.0.clone()).map(|s| s.to_string())
916 }
917
918 fn lang_attr(&self) -> Option<style::selector_parser::AttrValue> {
919 None
920 }
921
922 fn match_element_lang(
923 &self,
924 _override_lang: Option<Option<style::selector_parser::AttrValue>>,
925 _value: &style::selector_parser::Lang,
926 ) -> bool {
927 false
928 }
929
930 fn is_html_document_body_element(&self) -> bool {
931 let is_body_element = self.data.is_element_with_tag_name(&local_name!("body"));
933
934 if !is_body_element {
936 return false;
937 }
938
939 let root_node = TNode::owner_doc(self);
941 let root_element = TDocument::as_node(&root_node)
942 .first_element_child()
943 .unwrap();
944 root_element.children.contains(&self.id)
945 }
946
947 fn synthesize_presentational_hints_for_legacy_attributes<V>(
948 &self,
949 _visited_handling: VisitedHandlingMode,
950 hints: &mut V,
951 ) where
952 V: Push<style::applicable_declarations::ApplicableDeclarationBlock>,
953 {
954 let Some(elem) = self.data.downcast_element() else {
955 return;
956 };
957
958 let tag = &elem.name.local;
959
960 let mut push_style = |decl: PropertyDeclaration| {
961 hints.push(ApplicableDeclarationBlock::from_declarations(
962 Arc::new(
963 self.guard()
964 .wrap(PropertyDeclarationBlock::with_one(decl, Importance::Normal)),
965 ),
966 CascadeLevel::new(CascadeOrigin::PresHints),
967 LayerOrder::root(),
968 ));
969 };
970
971 if elem.name.ns.as_ref() == "http://www.w3.org/2000/svg"
974 && let Some(value) = elem.attr(markup5ever::LocalName::from("pointer-events"))
975 {
976 use style::computed_values::pointer_events::T as PointerEvents;
977 let value = match value.trim() {
978 "none" => Some(PointerEvents::None),
979 "auto" => Some(PointerEvents::Auto),
980 _ => None,
981 };
982 if let Some(value) = value {
983 push_style(PropertyDeclaration::PointerEvents(value));
984 }
985 }
986
987 fn parse_color_attr(value: &str) -> Option<(u8, u8, u8, f32)> {
988 if !value.starts_with('#') {
989 return None;
990 }
991
992 let value = &value[1..];
993 if value.len() == 3 {
994 let r = u8::from_str_radix(&value[0..1], 16).ok()?;
995 let g = u8::from_str_radix(&value[1..2], 16).ok()?;
996 let b = u8::from_str_radix(&value[2..3], 16).ok()?;
997 return Some((r, g, b, 1.0));
998 }
999
1000 if value.len() == 6 {
1001 let r = u8::from_str_radix(&value[0..2], 16).ok()?;
1002 let g = u8::from_str_radix(&value[2..4], 16).ok()?;
1003 let b = u8::from_str_radix(&value[4..6], 16).ok()?;
1004 return Some((r, g, b, 1.0));
1005 }
1006
1007 None
1008 }
1009
1010 fn parse_size_attr(
1018 value: &str,
1019 ignoring_zero: bool,
1020 ) -> Option<style::values::specified::LengthPercentage> {
1021 use style::servo::attr::{
1022 LengthOrPercentageOrAuto, parse_length, parse_nonzero_length,
1023 };
1024 use style::values::specified::{LengthPercentage, NoCalcLength};
1025 let parsed = if ignoring_zero {
1026 parse_nonzero_length(value)
1027 } else {
1028 parse_length(value)
1029 };
1030 match parsed {
1031 LengthOrPercentageOrAuto::Length(length) => Some(LengthPercentage::Length(
1032 NoCalcLength::from_px(length.to_f32_px()),
1033 )),
1034 LengthOrPercentageOrAuto::Percentage(fraction) => Some(
1035 LengthPercentage::Percentage(NoCalcPercentage::new(fraction)),
1036 ),
1037 LengthOrPercentageOrAuto::Auto => None,
1038 }
1039 }
1040
1041 fn parse_svg_size_attr(value: &str) -> Option<style::values::specified::LengthPercentage> {
1046 use style::values::specified::{LengthPercentage, NoCalcLength};
1047 use style_traits::ParsingMode;
1048
1049 let value = value.trim();
1050 if let Some(number) = value.strip_suffix('%') {
1051 let val: f32 = number.trim().parse().ok()?;
1052 return (val >= 0.0)
1053 .then(|| LengthPercentage::Percentage(NoCalcPercentage::new(val / 100.0)));
1054 }
1055
1056 let number_len = value
1060 .trim_end_matches(|c: char| c.is_ascii_alphabetic())
1061 .len();
1062 let (number, unit) = value.split_at(number_len);
1063 let val: f32 = number.trim().parse().ok().filter(|v| *v >= 0.0)?;
1064 let length = if unit.is_empty() {
1065 NoCalcLength::from_px(val)
1066 } else {
1067 NoCalcLength::parse_dimension_with_flags(ParsingMode::DEFAULT, false, val, unit)
1068 .ok()?
1069 };
1070 Some(LengthPercentage::Length(length))
1071 }
1072
1073 let is_image_input = *tag == local_name!("input")
1078 && elem.attrs().iter().any(|attr| {
1079 attr.name.local == local_name!("type")
1080 && attr.value.as_ref().eq_ignore_ascii_case("image")
1081 });
1082
1083 for attr in elem.attrs() {
1084 let name = &attr.name.local;
1085 let value: &str = attr.value.as_ref();
1086
1087 if *name == local_name!("align") {
1088 use style::values::specified::TextAlign;
1089 let keyword = match value {
1090 "left" => Some(StyloTextAlign::MozLeft),
1091 "right" => Some(StyloTextAlign::MozRight),
1092 "center" => Some(StyloTextAlign::MozCenter),
1093 _ => None,
1094 };
1095
1096 if let Some(keyword) = keyword {
1097 push_style(PropertyDeclaration::TextAlign(TextAlign::Keyword(keyword)));
1098 }
1099 }
1100
1101 let is_width = *name == local_name!("width");
1106 let is_height = *name == local_name!("height");
1107 let is_embedded = *tag == local_name!("iframe")
1111 || *tag == local_name!("embed")
1112 || *tag == local_name!("video")
1113 || *tag == local_name!("object")
1114 || *tag == local_name!("img")
1115 || *tag == local_name!("marquee")
1116 || is_image_input;
1117 let maps_to_dimension = if is_width {
1118 is_embedded
1119 || *tag == local_name!("table")
1120 || *tag == local_name!("col")
1121 || *tag == local_name!("colgroup")
1122 || *tag == local_name!("tr")
1123 || *tag == local_name!("td")
1124 || *tag == local_name!("th")
1125 || *tag == local_name!("hr")
1126 } else if is_height {
1127 is_embedded
1128 || *tag == local_name!("table")
1129 || *tag == local_name!("thead")
1130 || *tag == local_name!("tbody")
1131 || *tag == local_name!("tfoot")
1132 || *tag == local_name!("tr")
1133 || *tag == local_name!("td")
1134 || *tag == local_name!("th")
1135 } else {
1136 false
1137 };
1138 if maps_to_dimension {
1139 let is_cell = *tag == local_name!("td") || *tag == local_name!("th");
1143 let ignoring_zero = is_cell || (is_width && *tag == local_name!("table"));
1144 if let Some(size) = parse_size_attr(value, ignoring_zero) {
1145 use style::values::generics::{NonNegative, length::Size};
1146 let size = Size::LengthPercentage(NonNegative(size));
1147 push_style(if is_width {
1148 PropertyDeclaration::Width(size)
1149 } else {
1150 PropertyDeclaration::Height(size)
1151 });
1152 }
1153 }
1154
1155 let takes_spacing = *tag == local_name!("embed")
1161 || *tag == local_name!("img")
1162 || *tag == local_name!("object")
1163 || *tag == local_name!("marquee")
1164 || is_image_input;
1165 if takes_spacing {
1166 let is_hspace = *name == local_name!("hspace");
1167 let is_vspace = *name == local_name!("vspace");
1168 if is_hspace || is_vspace {
1169 if let Some(size) = parse_size_attr(value, false) {
1170 use style::values::generics::length::GenericMargin;
1171 let margin = GenericMargin::LengthPercentage(size);
1172 if is_hspace {
1173 push_style(PropertyDeclaration::MarginLeft(margin.clone()));
1174 push_style(PropertyDeclaration::MarginRight(margin));
1175 } else {
1176 push_style(PropertyDeclaration::MarginTop(margin.clone()));
1177 push_style(PropertyDeclaration::MarginBottom(margin));
1178 }
1179 }
1180 }
1181 }
1182
1183 if *tag == local_name!("svg")
1189 && (*name == local_name!("width") || *name == local_name!("height"))
1190 {
1191 if let Some(size) = parse_svg_size_attr(value) {
1192 use style::values::generics::{NonNegative, length::Size};
1193 let size = Size::LengthPercentage(NonNegative(size));
1194 push_style(if *name == local_name!("width") {
1195 PropertyDeclaration::Width(size)
1196 } else {
1197 PropertyDeclaration::Height(size)
1198 });
1199 }
1200 }
1201
1202 if *name == local_name!("border")
1209 && (*tag == local_name!("img") || *tag == local_name!("object") || is_image_input)
1210 {
1211 if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1212 use style::values::specified::{BorderSideWidth, BorderStyle};
1213 let width = BorderSideWidth::from_px(px as f32);
1214 push_style(PropertyDeclaration::BorderTopWidth(width.clone()));
1215 push_style(PropertyDeclaration::BorderRightWidth(width.clone()));
1216 push_style(PropertyDeclaration::BorderBottomWidth(width.clone()));
1217 push_style(PropertyDeclaration::BorderLeftWidth(width));
1218 push_style(PropertyDeclaration::BorderTopStyle(BorderStyle::Solid));
1219 push_style(PropertyDeclaration::BorderRightStyle(BorderStyle::Solid));
1220 push_style(PropertyDeclaration::BorderBottomStyle(BorderStyle::Solid));
1221 push_style(PropertyDeclaration::BorderLeftStyle(BorderStyle::Solid));
1222 }
1223 }
1224
1225 if *tag == local_name!("body") {
1235 let sides: &[u8] = match &**name {
1238 "marginwidth" => b"lr",
1239 "marginheight" => b"tb",
1240 "leftmargin" => b"l",
1241 "topmargin" => b"t",
1242 _ => b"",
1243 };
1244 if !sides.is_empty() {
1245 if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1246 use style::values::generics::length::GenericMargin;
1247 use style::values::specified::{LengthPercentage, NoCalcLength};
1248 let margin = GenericMargin::LengthPercentage(LengthPercentage::Length(
1249 NoCalcLength::from_px(px as f32),
1250 ));
1251 for side in sides {
1252 push_style(match side {
1253 b'l' => PropertyDeclaration::MarginLeft(margin.clone()),
1254 b'r' => PropertyDeclaration::MarginRight(margin.clone()),
1255 b't' => PropertyDeclaration::MarginTop(margin.clone()),
1256 b'b' => PropertyDeclaration::MarginBottom(margin.clone()),
1257 _ => unreachable!("side table above only yields lrtb"),
1258 });
1259 }
1260 }
1261 }
1262 }
1263
1264 if *name == local_name!("bgcolor") {
1265 use style::values::specified::Color;
1266 if let Some((r, g, b, a)) = parse_color_attr(value) {
1267 push_style(PropertyDeclaration::BackgroundColor(
1268 Color::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)),
1269 ));
1270 }
1271 }
1272
1273 if *name == local_name!("hidden") {
1274 use style::values::specified::Display;
1275 push_style(PropertyDeclaration::Display(Display::None));
1276 }
1277 }
1278 }
1279
1280 fn local_name(&self) -> &LocalName {
1281 &self.element_data().expect("Not an element").name.local
1282 }
1283
1284 fn namespace(&self) -> &Namespace {
1285 &self.element_data().expect("Not an element").name.ns
1286 }
1287
1288 fn query_container_size(
1289 &self,
1290 _display: &style::values::specified::Display,
1291 ) -> euclid::default::Size2D<Option<app_units::Au>> {
1292 Default::default()
1294 }
1295
1296 fn each_custom_state<F>(&self, _callback: F)
1297 where
1298 F: FnMut(&AtomIdent),
1299 {
1300 }
1301
1302 fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
1303 self.selector_flags().get().contains(flags)
1304 }
1305
1306 fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
1307 let flags = self.selector_flags().get();
1308 if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
1309 {
1310 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING
1311 } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
1312 {
1313 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
1314 } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) {
1315 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
1316 } else {
1317 ElementSelectorFlags::empty()
1318 }
1319 }
1320
1321 fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
1322 compute_layout_damage(old, new)
1323 }
1325
1326 }
1346
1347pub struct Traverser<'a> {
1348 parent: BlitzNode<'a>,
1350 child_index: usize,
1351}
1352
1353impl<'a> Iterator for Traverser<'a> {
1354 type Item = BlitzNode<'a>;
1355
1356 fn next(&mut self) -> Option<Self::Item> {
1357 let node_id = self.parent.layout_dom_children().get(self.child_index)?;
1361 let node = self.parent.with(*node_id);
1362
1363 self.child_index += 1;
1364
1365 Some(node)
1366 }
1367}
1368
1369impl std::hash::Hash for BlitzNode<'_> {
1370 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1371 state.write_u64(self.id.as_u64())
1372 }
1373}
1374
1375pub struct RegisteredPaintersImpl;
1379impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
1380 fn get(&self, _name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
1381 None
1382 }
1383}
1384
1385use style::traversal::recalc_style_at;
1386
1387pub struct RecalcStyle<'a> {
1388 context: SharedStyleContext<'a>,
1389}
1390
1391impl<'a> RecalcStyle<'a> {
1392 pub fn new(context: SharedStyleContext<'a>) -> Self {
1393 RecalcStyle { context }
1394 }
1395}
1396
1397#[allow(unsafe_code)]
1398impl<E> DomTraversal<E> for RecalcStyle<'_>
1399where
1400 E: TElement,
1401{
1402 fn process_preorder<F: FnMut(E::ConcreteNode)>(
1403 &self,
1404 traversal_data: &PerLevelTraversalData,
1405 context: &mut StyleContext<E>,
1406 node: E::ConcreteNode,
1407 note_child: F,
1408 ) {
1409 if let Some(el) = node.as_element() {
1410 let mut data = unsafe { el.ensure_data() };
1412 recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
1413
1414 unsafe { el.unset_dirty_descendants() }
1416 }
1417 }
1418
1419 #[inline]
1420 fn needs_postorder_traversal() -> bool {
1421 false
1422 }
1423
1424 fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
1425 panic!("this should never be called")
1426 }
1427
1428 #[inline]
1429 fn shared_context(&self) -> &SharedStyleContext<'_> {
1430 &self.context
1431 }
1432}
1433
1434#[test]
1435fn assert_size_of_equals() {
1436 }
1452
1453#[test]
1454fn parse_inline() {
1455 }