1use blitz_traits::node_id::NodeId;
5use std::ptr::NonNull;
6use std::sync::Mutex;
7use std::sync::atomic::Ordering;
8
9use crate::StyleThreading;
10use crate::layout::damage::compute_layout_damage;
11use crate::node::Node;
12use crate::node::NodeData;
13use markup5ever::{LocalName, LocalNameStaticSet, Namespace, NamespaceStaticSet, local_name, ns};
14use selectors::bloom::BLOOM_HASH_MASK;
15use selectors::{
16 Element, OpaqueElement,
17 attr::{AttrSelectorOperation, NamespaceConstraint},
18 matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode},
19 sink::Push,
20};
21use style::CaseSensitivityExt;
22use style::animation::AnimationSetKey;
23use style::animation::AnimationState;
24use style::applicable_declarations::ApplicableDeclarationBlock;
25use style::bloom::each_relevant_element_hash;
26use style::color::AbsoluteColor;
27use style::data::{ElementDataMut, ElementDataRef};
28use style::global_style_data::STYLE_THREAD_POOL;
29use style::invalidation::element::restyle_hints::RestyleHint;
30use style::properties::ComputedValues;
31use style::properties::{Importance, PropertyDeclaration};
32use style::rule_tree::CascadeLevel;
33use style::rule_tree::CascadeOrigin;
34use style::selector_parser::PseudoElement;
35use style::selector_parser::RestyleDamage;
36use style::stylesheets::layer_rule::LayerOrder;
37use style::stylesheets::scope_rule::ImplicitScopeRoot;
38use style::values::AtomString;
39use style::values::specified::{NoCalcPercentage, XLang};
40use style::{
41 Atom,
42 context::{
43 QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters,
44 SharedStyleContext, StyleContext, ThreadLocalStyleContext,
45 },
46 dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot},
47 global_style_data::GLOBAL_STYLE_DATA,
48 properties::PropertyDeclarationBlock,
49 selector_parser::{NonTSPseudoClass, SelectorImpl},
50 servo_arc::{Arc, ArcBorrow},
51 shared_lock::{Locked, SharedRwLock, StylesheetGuards},
52 stylist::RuleInclusion,
53 thread_state::ThreadState,
54 traversal::{DomTraversal, resolve_style},
55 traversal_flags::TraversalFlags,
56 values::{AtomIdent, GenericAtomIdent},
57};
58use style_dom::ElementState;
59
60use style::values::computed::text::TextAlign as StyloTextAlign;
61
62impl crate::document::BaseDocument {
63 pub fn resolve_stylist(&mut self, now: f64) {
64 style::thread_state::enter(ThreadState::LAYOUT);
65
66 let guard = &self.guard;
67 let guards = StylesheetGuards {
68 author: &guard.read(),
69 ua_or_user: &guard.read(),
70 };
71
72 let root = TDocument::as_node(&&self.nodes[self.root_node_id])
73 .first_element_child()
74 .unwrap()
75 .as_element()
76 .unwrap();
77
78 self.stylist
79 .flush(&guards)
80 .process_style(root, Some(&self.snapshots));
81
82 let mut sets = self.animations.sets.write();
84 for (key, set) in sets.iter_mut() {
85 let node_id = NodeId::from_u64(key.node.id() as u64);
86
87 let in_document = self
94 .nodes
95 .get(node_id)
96 .is_some_and(|node| node.flags.is_in_document());
97 if !in_document {
98 set.animations.clear();
99 set.transitions.clear();
100 continue;
101 }
102
103 self.nodes[node_id].set_restyle_hint(RestyleHint::RESTYLE_SELF);
104
105 for animation in set.animations.iter_mut() {
106 if animation.state == AnimationState::Pending && animation.started_at <= now {
107 animation.state = AnimationState::Running;
108 }
109 animation.iterate_if_necessary(now);
110
111 if animation.state == AnimationState::Running && animation.has_ended(now) {
112 animation.state = AnimationState::Finished;
113 }
114 }
115
116 for transition in set.transitions.iter_mut() {
117 if transition.state == AnimationState::Pending && transition.start_time <= now {
118 transition.state = AnimationState::Running;
119 }
120 if transition.state == AnimationState::Running && transition.has_ended(now) {
121 transition.state = AnimationState::Finished;
122 }
123 }
124 }
125 drop(sets);
126
127 let context = SharedStyleContext {
129 traversal_flags: TraversalFlags::empty(),
130 stylist: &self.stylist,
131 options: GLOBAL_STYLE_DATA.options.clone(),
132 guards,
133 visited_styles_enabled: false,
134 animations: self.animations.clone(),
135 current_time_for_animations: now,
136 snapshot_map: &self.snapshots,
137 registered_speculative_painters: &RegisteredPaintersImpl,
138 };
139
140 let root = self.root_element();
142 let token = RecalcStyle::pre_traverse(root, &context);
144
145 let mut nodes_needing_style_image_flush = Vec::new();
146 if token.should_traverse() {
147 let mut traverser = RecalcStyle::new(context);
149 let pool_guard = matches!(self.style_threading, StyleThreading::Parallel)
151 .then(|| STYLE_THREAD_POOL.pool());
152 let rayon_pool = pool_guard.as_ref().and_then(|g| g.as_ref());
153 style::driver::traverse_dom(&traverser, token, rayon_pool);
154 nodes_needing_style_image_flush =
155 std::mem::take(traverser.nodes_needing_style_image_flush.get_mut().unwrap());
156 }
157 self.pending_style_image_nodes
158 .extend(nodes_needing_style_image_flush);
159
160 for opaque in self.snapshots.keys() {
161 let id = NodeId::from_u64(opaque.id() as u64);
162 if let Some(node) = self.nodes.get_mut(id) {
163 node.set_has_snapshot(false);
164 }
165 }
166 self.snapshots.clear();
167
168 let mut sets = self.animations.sets.write();
169 for set in sets.values_mut() {
170 set.clear_canceled_animations();
171 for animation in set.animations.iter_mut() {
172 animation.is_new = false;
173 }
174 for transition in set.transitions.iter_mut() {
175 transition.is_new = false;
176 }
177 }
178 sets.retain(|_, state| !state.is_empty());
179 self.has_active_animations = sets.values().any(|state| state.needs_animation_ticks());
180
181 self.stylist.rule_tree().maybe_gc();
183
184 style::thread_state::exit(ThreadState::LAYOUT);
185 }
186
187 pub fn resolve_undisplayed_style(&self, node_id: NodeId) -> Option<Arc<ComputedValues>> {
196 let node = self.nodes.get(node_id)?;
197 if !node.is_element() || !node.flags.is_in_document() {
198 return None;
199 }
200
201 style::thread_state::enter(ThreadState::LAYOUT);
202
203 let guard = &self.guard;
204 let guards = StylesheetGuards {
205 author: &guard.read(),
206 ua_or_user: &guard.read(),
207 };
208 let shared = SharedStyleContext {
209 traversal_flags: TraversalFlags::empty(),
210 stylist: &self.stylist,
211 options: GLOBAL_STYLE_DATA.options.clone(),
212 guards,
213 visited_styles_enabled: false,
214 animations: self.animations.clone(),
215 current_time_for_animations: 0.0,
216 snapshot_map: &self.snapshots,
217 registered_speculative_painters: &RegisteredPaintersImpl,
218 };
219 let mut thread_local = ThreadLocalStyleContext::new();
220 let mut context = StyleContext {
221 shared: &shared,
222 thread_local: &mut thread_local,
223 };
224 let styles = resolve_style(&mut context, node, RuleInclusion::All, None, None);
225
226 style::thread_state::exit(ThreadState::LAYOUT);
227
228 Some(styles.primary().clone())
229 }
230}
231
232type BlitzNode<'a> = &'a Node;
237
238impl<'a> TDocument for BlitzNode<'a> {
239 type ConcreteNode = BlitzNode<'a>;
240
241 fn as_node(&self) -> Self::ConcreteNode {
242 self
243 }
244
245 fn is_html_document(&self) -> bool {
246 true
247 }
248
249 fn quirks_mode(&self) -> QuirksMode {
250 QuirksMode::NoQuirks
251 }
252
253 fn shared_lock(&self) -> &SharedRwLock {
254 self.guard()
255 }
256}
257
258impl NodeInfo for BlitzNode<'_> {
259 fn is_element(&self) -> bool {
260 Node::is_element(self)
261 }
262
263 fn is_text_node(&self) -> bool {
264 Node::is_text_node(self)
265 }
266}
267
268impl<'a> TShadowRoot for BlitzNode<'a> {
269 type ConcreteNode = BlitzNode<'a>;
270
271 fn as_node(&self) -> Self::ConcreteNode {
272 self
273 }
274
275 fn host(&self) -> <Self::ConcreteNode as TNode>::ConcreteElement {
276 todo!("Shadow roots not implemented")
277 }
278
279 fn style_data<'b>(&self) -> Option<&'b style::stylist::CascadeData>
280 where
281 Self: 'b,
282 {
283 todo!("Shadow roots not implemented")
284 }
285}
286
287impl<'a> TNode for BlitzNode<'a> {
289 type ConcreteElement = BlitzNode<'a>;
290 type ConcreteDocument = BlitzNode<'a>;
291 type ConcreteShadowRoot = BlitzNode<'a>;
292
293 fn parent_node(&self) -> Option<Self> {
294 self.parent.map(|id| self.with(id))
295 }
296
297 fn first_child(&self) -> Option<Self> {
298 self.children.first().map(|id| self.with(*id))
299 }
300
301 fn last_child(&self) -> Option<Self> {
302 self.children.last().map(|id| self.with(*id))
303 }
304
305 fn prev_sibling(&self) -> Option<Self> {
306 self.backward(1)
307 }
308
309 fn next_sibling(&self) -> Option<Self> {
310 self.forward(1)
311 }
312
313 fn owner_doc(&self) -> Self::ConcreteDocument {
314 let mut node = *self;
316 while let Some(parent_id) = node.parent {
317 node = node.with(parent_id);
318 }
319 node
320 }
321
322 fn is_in_document(&self) -> bool {
323 true
324 }
325
326 fn traversal_parent(&self) -> Option<Self::ConcreteElement> {
331 self.parent_node().and_then(|node| node.as_element())
332 }
333
334 fn opaque(&self) -> OpaqueNode {
335 OpaqueNode(self.id.as_u64() as usize)
336 }
337
338 fn debug_id(self) -> usize {
339 self.id.as_u64() as usize
340 }
341
342 fn as_element(&self) -> Option<Self::ConcreteElement> {
343 match self.data {
344 NodeData::Element { .. } => Some(self),
345 _ => None,
346 }
347 }
348
349 fn as_document(&self) -> Option<Self::ConcreteDocument> {
350 match self.data {
351 NodeData::Document(_) => Some(self),
352 _ => None,
353 }
354 }
355
356 fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> {
357 None
359 }
360}
361
362impl selectors::Element for BlitzNode<'_> {
363 type Impl = SelectorImpl;
364
365 fn opaque(&self) -> selectors::OpaqueElement {
366 let non_null =
374 NonNull::new((self.id.as_u64() as usize).wrapping_add(1) as *mut ()).unwrap();
375 OpaqueElement::from_non_null_ptr(non_null)
376 }
377
378 fn parent_element(&self) -> Option<Self> {
379 TElement::traversal_parent(self)
380 }
381
382 fn parent_node_is_shadow_root(&self) -> bool {
383 false
384 }
385
386 fn containing_shadow_host(&self) -> Option<Self> {
387 None
388 }
389
390 fn is_pseudo_element(&self) -> bool {
391 matches!(self.data, NodeData::AnonymousBlock(_))
392 }
393
394 fn prev_sibling_element(&self) -> Option<Self> {
397 let mut n = 1;
398 while let Some(node) = self.backward(n) {
399 if node.is_element() {
400 return Some(node);
401 }
402 n += 1;
403 }
404
405 None
406 }
407
408 fn next_sibling_element(&self) -> Option<Self> {
409 let mut n = 1;
410 while let Some(node) = self.forward(n) {
411 if node.is_element() {
412 return Some(node);
413 }
414 n += 1;
415 }
416
417 None
418 }
419
420 fn first_element_child(&self) -> Option<Self> {
421 let mut children = self.dom_children();
422 children.find(|child| child.is_element())
423 }
424
425 fn is_html_element_in_html_document(&self) -> bool {
426 true }
428
429 fn has_local_name(&self, local_name: &LocalName) -> bool {
430 self.data.is_element_with_tag_name(local_name)
431 }
432
433 fn has_namespace(&self, ns: &Namespace) -> bool {
434 self.element_data().expect("Not an element").name.ns == *ns
435 }
436
437 fn is_same_type(&self, other: &Self) -> bool {
438 self.local_name() == other.local_name() && self.namespace() == other.namespace()
439 }
440
441 fn attr_matches(
442 &self,
443 ns: &NamespaceConstraint<&GenericAtomIdent<NamespaceStaticSet>>,
444 local_name: &GenericAtomIdent<LocalNameStaticSet>,
445 operation: &AttrSelectorOperation<&AtomString>,
446 ) -> bool {
447 let Some(attrs) = self.data.attrs() else {
448 return false;
449 };
450 attrs.iter().any(|attr| {
451 attr.name.local == local_name.0
452 && match ns {
453 NamespaceConstraint::Any => true,
454 NamespaceConstraint::Specific(ns) => attr.name.ns == ns.0,
455 }
456 && operation.eval_str(&attr.value)
457 })
458 }
459
460 fn match_non_ts_pseudo_class(
461 &self,
462 pseudo_class: &<Self::Impl as selectors::SelectorImpl>::NonTSPseudoClass,
463 _context: &mut MatchingContext<Self::Impl>,
464 ) -> bool {
465 match *pseudo_class {
466 NonTSPseudoClass::Active => self.element_state().contains(ElementState::ACTIVE),
467 NonTSPseudoClass::AnyLink => self
468 .element_state()
469 .intersects(ElementState::VISITED_OR_UNVISITED),
470 NonTSPseudoClass::Checked => self.element_state().contains(ElementState::CHECKED),
471 NonTSPseudoClass::Valid => false,
472 NonTSPseudoClass::Invalid => false,
473 NonTSPseudoClass::Defined => false,
474 NonTSPseudoClass::Disabled => self.element_state().contains(ElementState::DISABLED),
475 NonTSPseudoClass::Enabled => self.element_state().contains(ElementState::ENABLED),
476 NonTSPseudoClass::Focus => self.element_state().contains(ElementState::FOCUS),
477 NonTSPseudoClass::FocusWithin => false,
478 NonTSPseudoClass::FocusVisible => false,
479 NonTSPseudoClass::Fullscreen => false,
480 NonTSPseudoClass::Hover => self.element_state().contains(ElementState::HOVER),
481 NonTSPseudoClass::Indeterminate => false,
482 NonTSPseudoClass::Lang(_) => false,
483 NonTSPseudoClass::CustomState(_) => false,
484 NonTSPseudoClass::Link => self.element_state().contains(ElementState::UNVISITED),
485 NonTSPseudoClass::PlaceholderShown => false,
486 NonTSPseudoClass::ReadWrite => false,
487 NonTSPseudoClass::ReadOnly => false,
488 NonTSPseudoClass::ServoNonZeroBorder => false,
489 NonTSPseudoClass::Target => false,
490 NonTSPseudoClass::Visited => false,
491 NonTSPseudoClass::Autofill => false,
492 NonTSPseudoClass::Default => false,
493
494 NonTSPseudoClass::InRange => false,
495 NonTSPseudoClass::Modal => false,
496 NonTSPseudoClass::Open => false,
497 NonTSPseudoClass::Optional => false,
498 NonTSPseudoClass::OutOfRange => false,
499 NonTSPseudoClass::PopoverOpen => false,
500 NonTSPseudoClass::Required => false,
501 NonTSPseudoClass::UserInvalid => false,
502 NonTSPseudoClass::UserValid => false,
503 NonTSPseudoClass::MozMeterOptimum => false,
504 NonTSPseudoClass::MozMeterSubOptimum => false,
505 NonTSPseudoClass::MozMeterSubSubOptimum => false,
506 }
507 }
508
509 fn match_pseudo_element(
510 &self,
511 pe: &PseudoElement,
512 _context: &mut MatchingContext<Self::Impl>,
513 ) -> bool {
514 let pseudo = match self.try_stylo_element_data().and_then(|s| s.get()) {
515 Some(el) => el
516 .styles
517 .get_primary()
518 .and_then(|s| s.pseudo())
519 .or(match &self.data {
520 NodeData::AnonymousBlock(_) => Some(PseudoElement::ServoAnonymousBox),
521 _ => None,
522 }),
523 None => None,
524 };
525
526 pseudo.is_some_and(|psuedo| psuedo == *pe)
527 }
528
529 fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
530 let self_flags = flags.for_self();
532 if !self_flags.is_empty() {
533 self.selector_flags()
534 .set(self.selector_flags().get() | self_flags);
535 }
536
537 let parent_flags = flags.for_parent();
539 if !parent_flags.is_empty() {
540 if let Some(parent) = self.parent_node() {
541 parent
542 .selector_flags()
543 .set(parent.selector_flags().get() | parent_flags);
544 }
545 }
546 }
547
548 fn is_link(&self) -> bool {
549 self.data.is_element_with_tag_name(&local_name!("a"))
550 }
551
552 fn is_html_slot_element(&self) -> bool {
553 false
554 }
555
556 fn has_id(
557 &self,
558 id: &<Self::Impl as selectors::SelectorImpl>::Identifier,
559 case_sensitivity: selectors::attr::CaseSensitivity,
560 ) -> bool {
561 self.element_data()
562 .and_then(|data| data.id.as_ref())
563 .map(|id_attr| case_sensitivity.eq_atom(id_attr, id))
564 .unwrap_or(false)
565 }
566
567 fn has_class(
568 &self,
569 search_name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
570 case_sensitivity: selectors::attr::CaseSensitivity,
571 ) -> bool {
572 let class_attr = self.data.attr(local_name!("class"));
573 if let Some(class_attr) = class_attr {
574 for pheme in class_attr.split_ascii_whitespace() {
576 let atom = Atom::from(pheme);
577 if case_sensitivity.eq_atom(&atom, search_name) {
578 return true;
579 }
580 }
581 }
582
583 false
584 }
585
586 fn imported_part(
587 &self,
588 _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
589 ) -> Option<<Self::Impl as selectors::SelectorImpl>::Identifier> {
590 None
591 }
592
593 fn is_part(&self, _name: &<Self::Impl as selectors::SelectorImpl>::Identifier) -> bool {
594 false
595 }
596
597 fn is_empty(&self) -> bool {
598 self.dom_children().next().is_none()
599 }
600
601 fn is_root(&self) -> bool {
602 self.parent_node()
603 .and_then(|parent| parent.parent_node())
604 .is_none()
605 }
606
607 fn has_custom_state(
608 &self,
609 _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
610 ) -> bool {
611 false
612 }
613
614 fn add_element_unique_hashes(&self, filter: &mut selectors::bloom::BloomFilter) -> bool {
615 each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
616 true
617 }
618}
619
620impl<'a> TElement for BlitzNode<'a> {
621 type ConcreteNode = BlitzNode<'a>;
622
623 type TraversalChildrenIterator = Traverser<'a>;
624
625 fn as_node(&self) -> Self::ConcreteNode {
626 self
627 }
628
629 fn implicit_scope_for_sheet_in_shadow_root(
630 _opaque_host: OpaqueElement,
631 _sheet_index: usize,
632 ) -> Option<ImplicitScopeRoot> {
633 todo!();
638 }
639
640 fn traversal_children(&self) -> style::dom::LayoutIterator<Self::TraversalChildrenIterator> {
641 LayoutIterator(Traverser {
642 parent: self,
644 child_index: 0,
645 })
646 }
647
648 fn is_html_element(&self) -> bool {
649 self.is_element()
650 }
651
652 fn is_mathml_element(&self) -> bool {
654 false
655 }
656
657 fn is_svg_element(&self) -> bool {
659 false
660 }
661
662 fn style_attribute(&self) -> Option<ArcBorrow<'_, Locked<PropertyDeclarationBlock>>> {
663 self.element_data()
664 .expect("Not an element")
665 .style_attribute
666 .as_ref()
667 .map(|f| f.borrow_arc())
668 }
669
670 fn state(&self) -> ElementState {
671 *self.element_state()
672 }
673
674 fn has_part_attr(&self) -> bool {
675 false
676 }
677
678 fn exports_any_part(&self) -> bool {
679 false
680 }
681
682 fn id(&self) -> Option<&style::Atom> {
683 self.element_data().and_then(|data| data.id.as_ref())
684 }
685
686 fn each_class<F>(&self, mut callback: F)
687 where
688 F: FnMut(&style::values::AtomIdent),
689 {
690 let class_attr = self.data.attr(local_name!("class"));
691 if let Some(class_attr) = class_attr {
692 for pheme in class_attr.split_ascii_whitespace() {
694 let atom = Atom::from(pheme); callback(AtomIdent::cast(&atom));
696 }
697 }
698 }
699
700 fn each_attr_name<F>(&self, mut callback: F)
701 where
702 F: FnMut(&style::LocalName),
703 {
704 if let Some(attrs) = self.data.attrs() {
705 for attr in attrs.iter() {
706 callback(&GenericAtomIdent(attr.name.local.clone()));
707 }
708 }
709 }
710
711 fn has_dirty_descendants(&self) -> bool {
712 Node::has_dirty_descendants(self)
713 }
714
715 fn has_snapshot(&self) -> bool {
716 Node::has_snapshot(self)
717 }
718
719 fn handled_snapshot(&self) -> bool {
720 self.snapshot_handled().load(Ordering::SeqCst)
721 }
722
723 unsafe fn set_handled_snapshot(&self) {
724 self.snapshot_handled().store(true, Ordering::SeqCst);
725 }
726
727 unsafe fn set_dirty_descendants(&self) {
734 Node::set_dirty_descendants(self);
735 }
736
737 unsafe fn unset_dirty_descendants(&self) {
738 Node::unset_dirty_descendants(self);
739 }
740
741 fn store_children_to_process(&self, _n: isize) {
742 unimplemented!()
743 }
744
745 fn did_process_child(&self) -> isize {
746 unimplemented!()
747 }
748
749 unsafe fn ensure_data(&self) -> ElementDataMut<'_> {
750 unsafe { self.stylo_element_data().ensure_init() }
752 }
753
754 unsafe fn clear_data(&self) {
755 unsafe { self.stylo_element_data().clear() }
757 }
758
759 fn has_data(&self) -> bool {
760 self.try_stylo_element_data().is_some_and(|s| s.has_data())
761 }
762
763 fn borrow_data(&self) -> Option<ElementDataRef<'_>> {
764 self.try_stylo_element_data().and_then(|s| s.get())
765 }
766
767 fn mutate_data(&self) -> Option<ElementDataMut<'_>> {
768 unsafe { self.stylo_element_data().unsafe_stylo_only_mut() }
769 }
770
771 fn skip_item_display_fixup(&self) -> bool {
772 false
773 }
774
775 fn may_have_animations(&self) -> bool {
776 true
777 }
778
779 fn has_animations(&self, context: &SharedStyleContext) -> bool {
780 self.has_css_animations(context, None) || self.has_css_transitions(context, None)
781 }
782
783 fn has_css_animations(
784 &self,
785 context: &SharedStyleContext,
786 pseudo_element: Option<PseudoElement>,
787 ) -> bool {
788 let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
789 context.animations.has_active_animations(&key)
790 }
791
792 fn has_css_transitions(
793 &self,
794 context: &SharedStyleContext,
795 pseudo_element: Option<PseudoElement>,
796 ) -> bool {
797 let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
798 context.animations.has_active_transitions(&key)
799 }
800
801 fn animation_rule(
802 &self,
803 context: &SharedStyleContext,
804 ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
805 let opaque = TNode::opaque(&TElement::as_node(self));
806 context.animations.get_animation_declarations(
807 &AnimationSetKey::new_for_non_pseudo(opaque),
808 context.current_time_for_animations,
809 self.guard(),
810 )
811 }
812
813 fn transition_rule(
814 &self,
815 context: &SharedStyleContext,
816 ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
817 let opaque = TNode::opaque(&TElement::as_node(self));
818 context.animations.get_transition_declarations(
819 &AnimationSetKey::new_for_non_pseudo(opaque),
820 context.current_time_for_animations,
821 self.guard(),
822 )
823 }
824
825 fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
826 None
827 }
828
829 fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
830 None
831 }
832
833 fn get_attr(&self, attr: &style::LocalName, ns: &style::Namespace) -> Option<String> {
834 self.attrs()?
836 .iter()
837 .find(|a| a.name.local == attr.0 && a.name.ns == ns.0)
838 .map(|a| a.value.clone())
839 }
840
841 fn lang_attr(&self) -> Option<style::selector_parser::AttrValue> {
842 None
843 }
844
845 fn match_element_lang(
846 &self,
847 _override_lang: Option<Option<style::selector_parser::AttrValue>>,
848 _value: &style::selector_parser::Lang,
849 ) -> bool {
850 false
851 }
852
853 fn is_html_document_body_element(&self) -> bool {
854 let is_body_element = self.data.is_element_with_tag_name(&local_name!("body"));
856
857 if !is_body_element {
859 return false;
860 }
861
862 let root_node = TNode::owner_doc(self);
864 let root_element = TDocument::as_node(&root_node)
865 .first_element_child()
866 .unwrap();
867 root_element.children.contains(&self.id)
868 }
869
870 fn synthesize_presentational_hints_for_legacy_attributes<V>(
871 &self,
872 _visited_handling: VisitedHandlingMode,
873 hints: &mut V,
874 ) where
875 V: Push<style::applicable_declarations::ApplicableDeclarationBlock>,
876 {
877 let Some(elem) = self.data.downcast_element() else {
878 return;
879 };
880
881 let tag = &elem.name.local;
882
883 let mut push_style = |decl: PropertyDeclaration| {
884 hints.push(ApplicableDeclarationBlock::from_declarations(
885 Arc::new(
886 self.guard()
887 .wrap(PropertyDeclarationBlock::with_one(decl, Importance::Normal)),
888 ),
889 CascadeLevel::new(CascadeOrigin::PresHints),
890 LayerOrder::root(),
891 ));
892 };
893
894 fn parse_color_attr(value: &str) -> Option<(u8, u8, u8, f32)> {
895 if !value.starts_with('#') {
896 return None;
897 }
898
899 let value = &value[1..];
900 if value.len() == 3 {
901 let r = u8::from_str_radix(&value[0..1], 16).ok()?;
902 let g = u8::from_str_radix(&value[1..2], 16).ok()?;
903 let b = u8::from_str_radix(&value[2..3], 16).ok()?;
904 return Some((r, g, b, 1.0));
905 }
906
907 if value.len() == 6 {
908 let r = u8::from_str_radix(&value[0..2], 16).ok()?;
909 let g = u8::from_str_radix(&value[2..4], 16).ok()?;
910 let b = u8::from_str_radix(&value[4..6], 16).ok()?;
911 return Some((r, g, b, 1.0));
912 }
913
914 None
915 }
916
917 fn parse_size_attr(
925 value: &str,
926 ignoring_zero: bool,
927 ) -> Option<style::values::specified::LengthPercentage> {
928 use style::servo::attr::{
929 LengthOrPercentageOrAuto, parse_length, parse_nonzero_length,
930 };
931 use style::values::specified::{LengthPercentage, NoCalcLength};
932 let parsed = if ignoring_zero {
933 parse_nonzero_length(value)
934 } else {
935 parse_length(value)
936 };
937 match parsed {
938 LengthOrPercentageOrAuto::Length(length) => Some(LengthPercentage::Length(
939 NoCalcLength::from_px(length.to_f32_px()),
940 )),
941 LengthOrPercentageOrAuto::Percentage(fraction) => Some(
942 LengthPercentage::Percentage(NoCalcPercentage::new(fraction)),
943 ),
944 LengthOrPercentageOrAuto::Auto => None,
945 }
946 }
947
948 fn parse_svg_size_attr(value: &str) -> Option<style::values::specified::LengthPercentage> {
953 use style::values::specified::{LengthPercentage, NoCalcLength};
954 use style_traits::ParsingMode;
955
956 let value = value.trim();
957 if let Some(number) = value.strip_suffix('%') {
958 let val: f32 = number.trim().parse().ok()?;
959 return (val >= 0.0)
960 .then(|| LengthPercentage::Percentage(NoCalcPercentage::new(val / 100.0)));
961 }
962
963 let number_len = value
967 .trim_end_matches(|c: char| c.is_ascii_alphabetic())
968 .len();
969 let (number, unit) = value.split_at(number_len);
970 let val: f32 = number.trim().parse().ok().filter(|v| *v >= 0.0)?;
971 let length = if unit.is_empty() {
972 NoCalcLength::from_px(val)
973 } else {
974 NoCalcLength::parse_dimension_with_flags(ParsingMode::DEFAULT, false, val, unit)
975 .ok()?
976 };
977 Some(LengthPercentage::Length(length))
978 }
979
980 let is_image_input = *tag == local_name!("input")
985 && elem.attrs().iter().any(|attr| {
986 attr.name.local == local_name!("type") && attr.value.eq_ignore_ascii_case("image")
987 });
988
989 let accepts_lang = elem.name.ns == ns!(html) || elem.name.ns == ns!(svg);
991 let mut lang = None;
992
993 for attr in elem.attrs() {
994 let name = &attr.name.local;
995 let value = attr.value.as_str();
996
997 if *name == local_name!("lang") {
998 if attr.name.ns == ns!(xml) {
999 lang = Some(value);
1000 } else if attr.name.ns == ns!() && accepts_lang {
1001 lang = lang.or(Some(value));
1002 }
1003 }
1004
1005 if *name == local_name!("align") {
1006 use style::values::specified::TextAlign;
1007 let keyword = match value {
1008 "left" => Some(StyloTextAlign::MozLeft),
1009 "right" => Some(StyloTextAlign::MozRight),
1010 "center" => Some(StyloTextAlign::MozCenter),
1011 _ => None,
1012 };
1013
1014 if let Some(keyword) = keyword {
1015 push_style(PropertyDeclaration::TextAlign(TextAlign::Keyword(keyword)));
1016 }
1017 }
1018
1019 let is_width = *name == local_name!("width");
1024 let is_height = *name == local_name!("height");
1025 let is_embedded = *tag == local_name!("iframe")
1029 || *tag == local_name!("embed")
1030 || *tag == local_name!("video")
1031 || *tag == local_name!("object")
1032 || *tag == local_name!("img")
1033 || *tag == local_name!("marquee")
1034 || is_image_input;
1035 let maps_to_dimension = if is_width {
1036 is_embedded
1037 || *tag == local_name!("table")
1038 || *tag == local_name!("col")
1039 || *tag == local_name!("colgroup")
1040 || *tag == local_name!("tr")
1041 || *tag == local_name!("td")
1042 || *tag == local_name!("th")
1043 || *tag == local_name!("hr")
1044 } else if is_height {
1045 is_embedded
1046 || *tag == local_name!("table")
1047 || *tag == local_name!("thead")
1048 || *tag == local_name!("tbody")
1049 || *tag == local_name!("tfoot")
1050 || *tag == local_name!("tr")
1051 || *tag == local_name!("td")
1052 || *tag == local_name!("th")
1053 } else {
1054 false
1055 };
1056 if maps_to_dimension {
1057 let is_cell = *tag == local_name!("td") || *tag == local_name!("th");
1061 let ignoring_zero = is_cell || (is_width && *tag == local_name!("table"));
1062 if let Some(size) = parse_size_attr(value, ignoring_zero) {
1063 use style::values::generics::{NonNegative, length::Size};
1064 let size = Size::LengthPercentage(NonNegative(size));
1065 push_style(if is_width {
1066 PropertyDeclaration::Width(size)
1067 } else {
1068 PropertyDeclaration::Height(size)
1069 });
1070 }
1071 }
1072
1073 let takes_spacing = *tag == local_name!("embed")
1079 || *tag == local_name!("img")
1080 || *tag == local_name!("object")
1081 || *tag == local_name!("marquee")
1082 || is_image_input;
1083 if takes_spacing {
1084 let is_hspace = *name == local_name!("hspace");
1085 let is_vspace = *name == local_name!("vspace");
1086 if is_hspace || is_vspace {
1087 if let Some(size) = parse_size_attr(value, false) {
1088 use style::values::generics::length::GenericMargin;
1089 let margin = GenericMargin::LengthPercentage(size);
1090 if is_hspace {
1091 push_style(PropertyDeclaration::MarginLeft(margin.clone()));
1092 push_style(PropertyDeclaration::MarginRight(margin));
1093 } else {
1094 push_style(PropertyDeclaration::MarginTop(margin.clone()));
1095 push_style(PropertyDeclaration::MarginBottom(margin));
1096 }
1097 }
1098 }
1099 }
1100
1101 if *tag == local_name!("svg")
1107 && (*name == local_name!("width") || *name == local_name!("height"))
1108 {
1109 if let Some(size) = parse_svg_size_attr(value) {
1110 use style::values::generics::{NonNegative, length::Size};
1111 let size = Size::LengthPercentage(NonNegative(size));
1112 push_style(if *name == local_name!("width") {
1113 PropertyDeclaration::Width(size)
1114 } else {
1115 PropertyDeclaration::Height(size)
1116 });
1117 }
1118 }
1119
1120 if *name == local_name!("border")
1127 && (*tag == local_name!("img") || *tag == local_name!("object") || is_image_input)
1128 {
1129 if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1130 use style::values::specified::{BorderSideWidth, BorderStyle};
1131 let width = BorderSideWidth::from_px(px as f32);
1132 push_style(PropertyDeclaration::BorderTopWidth(width.clone()));
1133 push_style(PropertyDeclaration::BorderRightWidth(width.clone()));
1134 push_style(PropertyDeclaration::BorderBottomWidth(width.clone()));
1135 push_style(PropertyDeclaration::BorderLeftWidth(width));
1136 push_style(PropertyDeclaration::BorderTopStyle(BorderStyle::Solid));
1137 push_style(PropertyDeclaration::BorderRightStyle(BorderStyle::Solid));
1138 push_style(PropertyDeclaration::BorderBottomStyle(BorderStyle::Solid));
1139 push_style(PropertyDeclaration::BorderLeftStyle(BorderStyle::Solid));
1140 }
1141 }
1142
1143 if *tag == local_name!("body") {
1153 let sides: &[u8] = match &**name {
1156 "marginwidth" => b"lr",
1157 "marginheight" => b"tb",
1158 "leftmargin" => b"l",
1159 "topmargin" => b"t",
1160 _ => b"",
1161 };
1162 if !sides.is_empty() {
1163 if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1164 use style::values::generics::length::GenericMargin;
1165 use style::values::specified::{LengthPercentage, NoCalcLength};
1166 let margin = GenericMargin::LengthPercentage(LengthPercentage::Length(
1167 NoCalcLength::from_px(px as f32),
1168 ));
1169 for side in sides {
1170 push_style(match side {
1171 b'l' => PropertyDeclaration::MarginLeft(margin.clone()),
1172 b'r' => PropertyDeclaration::MarginRight(margin.clone()),
1173 b't' => PropertyDeclaration::MarginTop(margin.clone()),
1174 b'b' => PropertyDeclaration::MarginBottom(margin.clone()),
1175 _ => unreachable!("side table above only yields lrtb"),
1176 });
1177 }
1178 }
1179 }
1180 }
1181
1182 if *name == local_name!("bgcolor") {
1183 use style::values::specified::Color;
1184 if let Some((r, g, b, a)) = parse_color_attr(value) {
1185 push_style(PropertyDeclaration::BackgroundColor(
1186 Color::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)),
1187 ));
1188 }
1189 }
1190
1191 if *name == local_name!("hidden") {
1192 use style::values::specified::Display;
1193 push_style(PropertyDeclaration::Display(Display::None));
1194 }
1195 }
1196
1197 if let Some(lang) = lang {
1198 push_style(PropertyDeclaration::XLang(XLang(Atom::from(lang))));
1199 }
1200 }
1201
1202 fn local_name(&self) -> &LocalName {
1203 &self.element_data().expect("Not an element").name.local
1204 }
1205
1206 fn namespace(&self) -> &Namespace {
1207 &self.element_data().expect("Not an element").name.ns
1208 }
1209
1210 fn query_container_size(
1211 &self,
1212 _display: &style::values::specified::Display,
1213 ) -> euclid::default::Size2D<Option<app_units::Au>> {
1214 Default::default()
1216 }
1217
1218 fn each_custom_state<F>(&self, _callback: F)
1219 where
1220 F: FnMut(&AtomIdent),
1221 {
1222 todo!()
1223 }
1224
1225 fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
1226 self.selector_flags().get().contains(flags)
1227 }
1228
1229 fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
1230 let flags = self.selector_flags().get();
1231 if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
1232 {
1233 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING
1234 } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
1235 {
1236 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
1237 } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) {
1238 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
1239 } else {
1240 ElementSelectorFlags::empty()
1241 }
1242 }
1243
1244 fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
1245 compute_layout_damage(old, new)
1246 }
1248
1249 }
1269
1270pub struct Traverser<'a> {
1271 parent: BlitzNode<'a>,
1273 child_index: usize,
1274}
1275
1276impl<'a> Iterator for Traverser<'a> {
1277 type Item = BlitzNode<'a>;
1278
1279 fn next(&mut self) -> Option<Self::Item> {
1280 let node_id = self.parent.children.get(self.child_index)?;
1281 let node = self.parent.with(*node_id);
1282
1283 self.child_index += 1;
1284
1285 Some(node)
1286 }
1287}
1288
1289impl std::hash::Hash for BlitzNode<'_> {
1290 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1291 state.write_u64(self.id.as_u64())
1292 }
1293}
1294
1295pub struct RegisteredPaintersImpl;
1299impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
1300 fn get(&self, _name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
1301 None
1302 }
1303}
1304
1305use style::traversal::recalc_style_at;
1306
1307pub struct RecalcStyle<'a> {
1308 context: SharedStyleContext<'a>,
1309 nodes_needing_style_image_flush: Mutex<Vec<NodeId>>,
1313}
1314
1315impl<'a> RecalcStyle<'a> {
1316 pub fn new(context: SharedStyleContext<'a>) -> Self {
1317 RecalcStyle {
1318 context,
1319 nodes_needing_style_image_flush: Mutex::new(Vec::new()),
1320 }
1321 }
1322}
1323
1324#[allow(unsafe_code)]
1325impl<'dom> DomTraversal<BlitzNode<'dom>> for RecalcStyle<'_> {
1326 fn process_preorder<F: FnMut(BlitzNode<'dom>)>(
1327 &self,
1328 context: &mut StyleContext<BlitzNode<'dom>>,
1329 node: BlitzNode<'dom>,
1330 note_child: F,
1331 ) {
1332 if let Some(el) = node.as_element() {
1333 let mut data = unsafe { el.ensure_data() };
1335 recalc_style_at(self, context, el, &mut data, note_child);
1336
1337 sync_pseudo_element_styles(el, &data, &self.nodes_needing_style_image_flush);
1338
1339 if !data.damage.is_empty() {
1340 el.mark_damaged();
1343
1344 if data
1345 .styles
1346 .get_primary()
1347 .is_some_and(|style| needs_style_image_flush(el, style))
1348 {
1349 self.nodes_needing_style_image_flush
1350 .lock()
1351 .unwrap()
1352 .push(el.id);
1353 }
1354 }
1355
1356 el.unset_dirty_descendants();
1358 }
1359 }
1360
1361 #[inline]
1362 fn needs_postorder_traversal() -> bool {
1363 false
1364 }
1365
1366 fn process_postorder(
1367 &self,
1368 _style_context: &mut StyleContext<BlitzNode<'dom>>,
1369 _node: BlitzNode<'dom>,
1370 ) {
1371 panic!("this should never be called")
1372 }
1373
1374 #[inline]
1375 fn shared_context(&self) -> &SharedStyleContext<'_> {
1376 &self.context
1377 }
1378}
1379
1380#[allow(unsafe_code)]
1402fn sync_pseudo_element_styles(
1403 el: &Node,
1404 data: &style::data::ElementData,
1405 nodes_needing_style_image_flush: &Mutex<Vec<NodeId>>,
1406) {
1407 let before_node_id = el.before();
1408 let after_node_id = el.after();
1409 if before_node_id.is_none() && after_node_id.is_none() {
1410 return;
1411 }
1412
1413 let pseudos = data.styles.pseudos.as_array();
1415 let before_style = pseudos[1].clone();
1416 let after_style = pseudos[0].clone();
1417
1418 for (pe_node_id, pe_style) in [(before_node_id, before_style), (after_node_id, after_style)] {
1422 let (Some(pe_node_id), Some(pe_style)) = (pe_node_id, pe_style) else {
1423 continue;
1424 };
1425 let pe_node = el.with(pe_node_id);
1426 let Some(stylo_data) = pe_node.try_stylo_element_data() else {
1427 continue;
1428 };
1429 let mut pe_data = match unsafe { stylo_data.unsafe_stylo_only_mut() } {
1430 Some(data) => data,
1431 None => continue,
1432 };
1433 let Some(old_style) = pe_data.styles.primary.clone() else {
1434 continue;
1435 };
1436 if std::ptr::eq(&*old_style, &*pe_style) {
1437 continue;
1438 }
1439
1440 let diff = RestyleDamage::compute_style_difference::<&Node>(&old_style, &pe_style);
1441 if !diff.damage.is_empty() {
1442 pe_data.damage.insert(diff.damage);
1443 pe_node.mark_damaged();
1444
1445 if needs_style_image_flush(pe_node, &pe_style) {
1446 nodes_needing_style_image_flush
1447 .lock()
1448 .unwrap()
1449 .push(pe_node_id);
1450 }
1451 }
1452 pe_data.styles.primary = Some(pe_style);
1453 pe_data.set_restyled();
1454 }
1455}
1456
1457fn needs_style_image_flush(node: &Node, style: &ComputedValues) -> bool {
1461 use crate::node::ImageResourceData;
1462 use style::url::ComputedUrl;
1463 use style::values::computed::image::Image;
1464
1465 fn out_of_sync(style_images: &[Image], stored: &[Option<ImageResourceData>]) -> bool {
1466 if style_images.len() != stored.len() {
1467 return style_images
1470 .iter()
1471 .any(|image| matches!(image, Image::Url(_)))
1472 || stored.iter().any(Option::is_some);
1473 }
1474 std::iter::zip(style_images, stored).any(|(style_image, stored)| {
1475 match (style_image, stored) {
1476 (Image::Url(ComputedUrl::Valid(url)), Some(data)) => **url != *data.url,
1477 (Image::Url(ComputedUrl::Valid(_)), None) => true,
1478 (_, Some(_)) => true,
1479 (_, None) => false,
1480 }
1481 })
1482 }
1483
1484 node.data.downcast_element().is_some_and(|el| {
1485 out_of_sync(
1486 &style.get_background().background_image.0,
1487 &el.background_images,
1488 ) || out_of_sync(&style.get_svg().mask_image.0, &el.mask_images)
1489 })
1490}
1491
1492#[test]
1493fn assert_size_of_equals() {
1494 }
1510
1511#[test]
1512fn parse_inline() {
1513 }