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