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