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 let guard = &self.guard;
65 let guards = StylesheetGuards {
66 author: &guard.read(),
67 ua_or_user: &guard.read(),
68 };
69
70 let root = TDocument::as_node(&&self.nodes[self.root_node_id])
71 .first_element_child()
72 .unwrap()
73 .as_element()
74 .unwrap();
75
76 self.stylist
77 .flush(&guards)
78 .process_style(root, Some(&self.snapshots));
79
80 let mut sets = self.animations.sets.write();
82 for (key, set) in sets.iter_mut() {
83 let node_id = NodeId::from_u64(key.node.id() as u64);
84
85 let in_document = self
92 .nodes
93 .get(node_id)
94 .is_some_and(|node| node.flags.is_in_document());
95 if !in_document {
96 set.animations.clear();
97 set.transitions.clear();
98 continue;
99 }
100
101 self.nodes[node_id].set_restyle_hint(RestyleHint::RESTYLE_SELF);
102
103 for animation in set.animations.iter_mut() {
104 if animation.state == AnimationState::Pending && animation.started_at <= now {
105 animation.state = AnimationState::Running;
106 }
107 animation.iterate_if_necessary(now);
108
109 if animation.state == AnimationState::Running && animation.has_ended(now) {
110 animation.state = AnimationState::Finished;
111 }
112 }
113
114 for transition in set.transitions.iter_mut() {
115 if transition.state == AnimationState::Pending && transition.start_time <= now {
116 transition.state = AnimationState::Running;
117 }
118 if transition.state == AnimationState::Running && transition.has_ended(now) {
119 transition.state = AnimationState::Finished;
120 }
121 }
122 }
123 drop(sets);
124
125 let context = SharedStyleContext {
127 traversal_flags: TraversalFlags::empty(),
128 stylist: &self.stylist,
129 options: GLOBAL_STYLE_DATA.options.clone(),
130 guards,
131 visited_styles_enabled: false,
132 animations: self.animations.clone(),
133 current_time_for_animations: now,
134 snapshot_map: &self.snapshots,
135 registered_speculative_painters: &RegisteredPaintersImpl,
136 };
137
138 let root = self.root_element();
140 let token = RecalcStyle::pre_traverse(root, &context);
142
143 if token.should_traverse() {
144 let traverser = RecalcStyle::new(context);
146 let pool_guard = matches!(self.style_threading, StyleThreading::Parallel)
148 .then(|| STYLE_THREAD_POOL.pool());
149 let rayon_pool = pool_guard.as_ref().and_then(|g| g.as_ref());
150 style::driver::traverse_dom(&traverser, token, rayon_pool);
151 }
152
153 for opaque in self.snapshots.keys() {
154 let id = NodeId::from_u64(opaque.id() as u64);
155 if let Some(node) = self.nodes.get_mut(id) {
156 node.set_has_snapshot(false);
157 }
158 }
159 self.snapshots.clear();
160
161 let mut sets = self.animations.sets.write();
162 for set in sets.values_mut() {
163 set.clear_canceled_animations();
164 for animation in set.animations.iter_mut() {
165 animation.is_new = false;
166 }
167 for transition in set.transitions.iter_mut() {
168 transition.is_new = false;
169 }
170 }
171 sets.retain(|_, state| !state.is_empty());
172 self.has_active_animations = sets.values().any(|state| state.needs_animation_ticks());
173
174 self.stylist.rule_tree().maybe_gc();
176
177 style::thread_state::exit(ThreadState::LAYOUT);
178 }
179}
180
181type BlitzNode<'a> = &'a Node;
186
187impl<'a> TDocument for BlitzNode<'a> {
188 type ConcreteNode = BlitzNode<'a>;
189
190 fn as_node(&self) -> Self::ConcreteNode {
191 self
192 }
193
194 fn is_html_document(&self) -> bool {
195 true
196 }
197
198 fn quirks_mode(&self) -> QuirksMode {
199 QuirksMode::NoQuirks
200 }
201
202 fn shared_lock(&self) -> &SharedRwLock {
203 self.guard()
204 }
205}
206
207impl NodeInfo for BlitzNode<'_> {
208 fn is_element(&self) -> bool {
209 Node::is_element(self)
210 }
211
212 fn is_text_node(&self) -> bool {
213 Node::is_text_node(self)
214 }
215}
216
217impl<'a> TShadowRoot for BlitzNode<'a> {
218 type ConcreteNode = BlitzNode<'a>;
219
220 fn as_node(&self) -> Self::ConcreteNode {
221 self
222 }
223
224 fn host(&self) -> <Self::ConcreteNode as TNode>::ConcreteElement {
225 todo!("Shadow roots not implemented")
226 }
227
228 fn style_data<'b>(&self) -> Option<&'b style::stylist::CascadeData>
229 where
230 Self: 'b,
231 {
232 todo!("Shadow roots not implemented")
233 }
234}
235
236impl<'a> TNode for BlitzNode<'a> {
238 type ConcreteElement = BlitzNode<'a>;
239 type ConcreteDocument = BlitzNode<'a>;
240 type ConcreteShadowRoot = BlitzNode<'a>;
241
242 fn parent_node(&self) -> Option<Self> {
243 self.parent.map(|id| self.with(id))
244 }
245
246 fn first_child(&self) -> Option<Self> {
247 self.children.first().map(|id| self.with(*id))
248 }
249
250 fn last_child(&self) -> Option<Self> {
251 self.children.last().map(|id| self.with(*id))
252 }
253
254 fn prev_sibling(&self) -> Option<Self> {
255 self.backward(1)
256 }
257
258 fn next_sibling(&self) -> Option<Self> {
259 self.forward(1)
260 }
261
262 fn owner_doc(&self) -> Self::ConcreteDocument {
263 let mut node = *self;
265 while let Some(parent_id) = node.parent {
266 node = node.with(parent_id);
267 }
268 node
269 }
270
271 fn is_in_document(&self) -> bool {
272 true
273 }
274
275 fn traversal_parent(&self) -> Option<Self::ConcreteElement> {
280 #[cfg(feature = "shadow-dom")]
284 {
285 if let Some(slot_id) = self.element_data().and_then(|el| el.assigned_slot) {
286 return Some(self.with(slot_id));
287 }
288 let parent = self.parent_node()?;
289 if let Some(shadow_data) = parent.shadow_root_data() {
290 return Some(self.with(shadow_data.host));
291 }
292 parent.as_element()
293 }
294 #[cfg(not(feature = "shadow-dom"))]
295 self.parent_node().and_then(|node| node.as_element())
296 }
297
298 fn opaque(&self) -> OpaqueNode {
299 OpaqueNode(self.id.as_u64() as usize)
300 }
301
302 fn debug_id(self) -> usize {
303 self.id.as_u64() as usize
304 }
305
306 fn as_element(&self) -> Option<Self::ConcreteElement> {
307 match self.data {
308 NodeData::Element { .. } => Some(self),
309 _ => None,
310 }
311 }
312
313 fn as_document(&self) -> Option<Self::ConcreteDocument> {
314 match self.data {
315 NodeData::Document(_) => Some(self),
316 _ => None,
317 }
318 }
319
320 fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> {
321 None
323 }
324}
325
326impl selectors::Element for BlitzNode<'_> {
327 type Impl = SelectorImpl;
328
329 fn opaque(&self) -> selectors::OpaqueElement {
330 let non_null =
338 NonNull::new((self.id.as_u64() as usize).wrapping_add(1) as *mut ()).unwrap();
339 OpaqueElement::from_non_null_ptr(non_null)
340 }
341
342 fn parent_element(&self) -> Option<Self> {
343 TElement::traversal_parent(self)
344 }
345
346 fn parent_node_is_shadow_root(&self) -> bool {
347 false
348 }
349
350 fn containing_shadow_host(&self) -> Option<Self> {
351 None
352 }
353
354 fn is_pseudo_element(&self) -> bool {
355 matches!(self.data, NodeData::AnonymousBlock(_))
356 }
357
358 fn prev_sibling_element(&self) -> Option<Self> {
361 let mut n = 1;
362 while let Some(node) = self.backward(n) {
363 if node.is_element() {
364 return Some(node);
365 }
366 n += 1;
367 }
368
369 None
370 }
371
372 fn next_sibling_element(&self) -> Option<Self> {
373 let mut n = 1;
374 while let Some(node) = self.forward(n) {
375 if node.is_element() {
376 return Some(node);
377 }
378 n += 1;
379 }
380
381 None
382 }
383
384 fn first_element_child(&self) -> Option<Self> {
385 let mut children = self.dom_children();
386 children.find(|child| child.is_element())
387 }
388
389 fn is_html_element_in_html_document(&self) -> bool {
390 true }
392
393 fn has_local_name(&self, local_name: &LocalName) -> bool {
394 self.data.is_element_with_tag_name(local_name)
395 }
396
397 fn has_namespace(&self, ns: &Namespace) -> bool {
398 self.element_data().expect("Not an element").name.ns == *ns
399 }
400
401 fn is_same_type(&self, other: &Self) -> bool {
402 self.local_name() == other.local_name() && self.namespace() == other.namespace()
403 }
404
405 fn attr_matches(
406 &self,
407 _ns: &NamespaceConstraint<&GenericAtomIdent<NamespaceStaticSet>>,
408 local_name: &GenericAtomIdent<LocalNameStaticSet>,
409 operation: &AttrSelectorOperation<&AtomString>,
410 ) -> bool {
411 match self.data.attr(local_name.0.clone()) {
412 None => false,
413 Some(attr_value) => operation.eval_str(attr_value),
414 }
415 }
416
417 fn match_non_ts_pseudo_class(
418 &self,
419 pseudo_class: &<Self::Impl as selectors::SelectorImpl>::NonTSPseudoClass,
420 _context: &mut MatchingContext<Self::Impl>,
421 ) -> bool {
422 match *pseudo_class {
423 NonTSPseudoClass::Active => self.element_state().contains(ElementState::ACTIVE),
424 NonTSPseudoClass::AnyLink => self
425 .data
426 .downcast_element()
427 .map(|elem| {
428 (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
429 && elem.attr(local_name!("href")).is_some()
430 })
431 .unwrap_or(false),
432 NonTSPseudoClass::Checked => self
433 .data
434 .downcast_element()
435 .and_then(|elem| elem.checkbox_input_checked())
436 .unwrap_or(false),
437 NonTSPseudoClass::Valid => false,
438 NonTSPseudoClass::Invalid => false,
439 NonTSPseudoClass::Defined => false,
440 NonTSPseudoClass::Disabled => self.element_state().contains(ElementState::DISABLED),
441 NonTSPseudoClass::Enabled => self.element_state().contains(ElementState::ENABLED),
442 NonTSPseudoClass::Focus => self.element_state().contains(ElementState::FOCUS),
443 NonTSPseudoClass::FocusWithin => false,
444 NonTSPseudoClass::FocusVisible => false,
445 NonTSPseudoClass::Fullscreen => false,
446 NonTSPseudoClass::Hover => self.element_state().contains(ElementState::HOVER),
447 NonTSPseudoClass::Indeterminate => false,
448 NonTSPseudoClass::Lang(_) => false,
449 NonTSPseudoClass::CustomState(_) => false,
450 NonTSPseudoClass::Link => self
451 .data
452 .downcast_element()
453 .map(|elem| {
454 (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
455 && elem.attr(local_name!("href")).is_some()
456 })
457 .unwrap_or(false),
458 NonTSPseudoClass::PlaceholderShown => false,
459 NonTSPseudoClass::ReadWrite => false,
460 NonTSPseudoClass::ReadOnly => false,
461 NonTSPseudoClass::ServoNonZeroBorder => false,
462 NonTSPseudoClass::Target => false,
463 NonTSPseudoClass::Visited => false,
464 NonTSPseudoClass::Autofill => false,
465 NonTSPseudoClass::Default => false,
466
467 NonTSPseudoClass::InRange => false,
468 NonTSPseudoClass::Modal => false,
469 NonTSPseudoClass::Open => false,
470 NonTSPseudoClass::Optional => false,
471 NonTSPseudoClass::OutOfRange => false,
472 NonTSPseudoClass::PopoverOpen => false,
473 NonTSPseudoClass::Required => false,
474 NonTSPseudoClass::UserInvalid => false,
475 NonTSPseudoClass::UserValid => false,
476 NonTSPseudoClass::MozMeterOptimum => false,
477 NonTSPseudoClass::MozMeterSubOptimum => false,
478 NonTSPseudoClass::MozMeterSubSubOptimum => false,
479 }
480 }
481
482 fn match_pseudo_element(
483 &self,
484 pe: &PseudoElement,
485 _context: &mut MatchingContext<Self::Impl>,
486 ) -> bool {
487 let pseudo = match self.stylo_element_data_opt().and_then(|s| s.get()) {
488 Some(el) => el
489 .styles
490 .get_primary()
491 .and_then(|s| s.pseudo())
492 .or(match &self.data {
493 NodeData::AnonymousBlock(_) => Some(PseudoElement::ServoAnonymousBox),
494 _ => None,
495 }),
496 None => None,
497 };
498
499 pseudo.is_some_and(|psuedo| psuedo == *pe)
500 }
501
502 fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
503 let self_flags = flags.for_self();
505 if !self_flags.is_empty() {
506 self.selector_flags()
507 .set(self.selector_flags().get() | self_flags);
508 }
509
510 let parent_flags = flags.for_parent();
512 if !parent_flags.is_empty() {
513 if let Some(parent) = self.parent_node() {
514 parent
515 .selector_flags()
516 .set(parent.selector_flags().get() | parent_flags);
517 }
518 }
519 }
520
521 fn is_link(&self) -> bool {
522 self.data.is_element_with_tag_name(&local_name!("a"))
523 }
524
525 fn is_html_slot_element(&self) -> bool {
526 false
527 }
528
529 fn has_id(
530 &self,
531 id: &<Self::Impl as selectors::SelectorImpl>::Identifier,
532 case_sensitivity: selectors::attr::CaseSensitivity,
533 ) -> bool {
534 self.element_data()
535 .and_then(|data| data.id.as_ref())
536 .map(|id_attr| case_sensitivity.eq_atom(id_attr, id))
537 .unwrap_or(false)
538 }
539
540 fn has_class(
541 &self,
542 search_name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
543 case_sensitivity: selectors::attr::CaseSensitivity,
544 ) -> bool {
545 let class_attr = self.data.attr(local_name!("class"));
546 if let Some(class_attr) = class_attr {
547 for pheme in class_attr.split_ascii_whitespace() {
549 let atom = Atom::from(pheme);
550 if case_sensitivity.eq_atom(&atom, search_name) {
551 return true;
552 }
553 }
554 }
555
556 false
557 }
558
559 fn imported_part(
560 &self,
561 _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
562 ) -> Option<<Self::Impl as selectors::SelectorImpl>::Identifier> {
563 None
564 }
565
566 fn is_part(&self, _name: &<Self::Impl as selectors::SelectorImpl>::Identifier) -> bool {
567 false
568 }
569
570 fn is_empty(&self) -> bool {
571 self.dom_children().next().is_none()
572 }
573
574 fn is_root(&self) -> bool {
575 self.parent_node()
576 .and_then(|parent| parent.parent_node())
577 .is_none()
578 }
579
580 fn has_custom_state(
581 &self,
582 _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
583 ) -> bool {
584 false
585 }
586
587 fn add_element_unique_hashes(&self, filter: &mut selectors::bloom::BloomFilter) -> bool {
588 each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
589 true
590 }
591}
592
593impl<'a> TElement for BlitzNode<'a> {
594 type ConcreteNode = BlitzNode<'a>;
595
596 type TraversalChildrenIterator = Traverser<'a>;
597
598 fn as_node(&self) -> Self::ConcreteNode {
599 self
600 }
601
602 fn implicit_scope_for_sheet_in_shadow_root(
603 _opaque_host: OpaqueElement,
604 _sheet_index: usize,
605 ) -> Option<ImplicitScopeRoot> {
606 todo!();
611 }
612
613 fn traversal_children(&self) -> style::dom::LayoutIterator<Self::TraversalChildrenIterator> {
614 LayoutIterator(Traverser {
615 parent: self,
617 child_index: 0,
618 })
619 }
620
621 fn is_html_element(&self) -> bool {
622 self.is_element()
623 }
624
625 fn is_mathml_element(&self) -> bool {
627 false
628 }
629
630 fn is_svg_element(&self) -> bool {
632 false
633 }
634
635 fn style_attribute(&self) -> Option<ArcBorrow<'_, Locked<PropertyDeclarationBlock>>> {
636 self.element_data()
637 .expect("Not an element")
638 .style_attribute
639 .as_ref()
640 .map(|f| f.borrow_arc())
641 }
642
643 fn state(&self) -> ElementState {
644 *self.element_state()
645 }
646
647 fn has_part_attr(&self) -> bool {
648 false
649 }
650
651 fn exports_any_part(&self) -> bool {
652 false
653 }
654
655 fn id(&self) -> Option<&style::Atom> {
656 self.element_data().and_then(|data| data.id.as_ref())
657 }
658
659 fn each_class<F>(&self, mut callback: F)
660 where
661 F: FnMut(&style::values::AtomIdent),
662 {
663 let class_attr = self.data.attr(local_name!("class"));
664 if let Some(class_attr) = class_attr {
665 for pheme in class_attr.split_ascii_whitespace() {
667 let atom = Atom::from(pheme); callback(AtomIdent::cast(&atom));
669 }
670 }
671 }
672
673 fn each_attr_name<F>(&self, mut callback: F)
674 where
675 F: FnMut(&style::LocalName),
676 {
677 if let Some(attrs) = self.data.attrs() {
678 for attr in attrs.iter() {
679 callback(&GenericAtomIdent(attr.name.local.clone()));
680 }
681 }
682 }
683
684 fn has_dirty_descendants(&self) -> bool {
685 Node::has_dirty_descendants(self)
686 }
687
688 fn has_snapshot(&self) -> bool {
689 Node::has_snapshot(self)
690 }
691
692 fn handled_snapshot(&self) -> bool {
693 self.snapshot_handled().load(Ordering::SeqCst)
694 }
695
696 unsafe fn set_handled_snapshot(&self) {
697 self.snapshot_handled().store(true, Ordering::SeqCst);
698 }
699
700 unsafe fn set_dirty_descendants(&self) {
701 Node::set_dirty_descendants(self);
702 Node::mark_ancestors_dirty(self);
703 }
704
705 unsafe fn unset_dirty_descendants(&self) {
706 Node::unset_dirty_descendants(self);
707 }
708
709 fn store_children_to_process(&self, _n: isize) {
710 unimplemented!()
711 }
712
713 fn did_process_child(&self) -> isize {
714 unimplemented!()
715 }
716
717 unsafe fn ensure_data(&self) -> ElementDataMut<'_> {
718 unsafe { self.stylo_element_data().ensure_init() }
720 }
721
722 unsafe fn clear_data(&self) {}
746
747 fn has_data(&self) -> bool {
748 self.stylo_element_data_opt().is_some_and(|s| s.has_data())
749 }
750
751 fn borrow_data(&self) -> Option<ElementDataRef<'_>> {
752 self.stylo_element_data_opt().and_then(|s| s.get())
753 }
754
755 fn mutate_data(&self) -> Option<ElementDataMut<'_>> {
756 unsafe { self.stylo_element_data().unsafe_stylo_only_mut() }
757 }
758
759 fn skip_item_display_fixup(&self) -> bool {
760 false
761 }
762
763 fn may_have_animations(&self) -> bool {
764 true
765 }
766
767 fn has_animations(&self, context: &SharedStyleContext) -> bool {
768 self.has_css_animations(context, None) || self.has_css_transitions(context, None)
769 }
770
771 fn has_css_animations(
772 &self,
773 context: &SharedStyleContext,
774 pseudo_element: Option<PseudoElement>,
775 ) -> bool {
776 let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
777 context.animations.has_active_animations(&key)
778 }
779
780 fn has_css_transitions(
781 &self,
782 context: &SharedStyleContext,
783 pseudo_element: Option<PseudoElement>,
784 ) -> bool {
785 let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
786 context.animations.has_active_transitions(&key)
787 }
788
789 fn animation_rule(
790 &self,
791 context: &SharedStyleContext,
792 ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
793 let opaque = TNode::opaque(&TElement::as_node(self));
794 context.animations.get_animation_declarations(
795 &AnimationSetKey::new_for_non_pseudo(opaque),
796 context.current_time_for_animations,
797 self.guard(),
798 )
799 }
800
801 fn transition_rule(
802 &self,
803 context: &SharedStyleContext,
804 ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
805 let opaque = TNode::opaque(&TElement::as_node(self));
806 context.animations.get_transition_declarations(
807 &AnimationSetKey::new_for_non_pseudo(opaque),
808 context.current_time_for_animations,
809 self.guard(),
810 )
811 }
812
813 fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
814 None
815 }
816
817 fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
818 None
819 }
820
821 fn get_attr(&self, attr: &style::LocalName, _ns: &style::Namespace) -> Option<String> {
822 self.attr(attr.0.clone()).map(|s| s.to_string())
825 }
826
827 fn lang_attr(&self) -> Option<style::selector_parser::AttrValue> {
828 None
829 }
830
831 fn match_element_lang(
832 &self,
833 _override_lang: Option<Option<style::selector_parser::AttrValue>>,
834 _value: &style::selector_parser::Lang,
835 ) -> bool {
836 false
837 }
838
839 fn is_html_document_body_element(&self) -> bool {
840 let is_body_element = self.data.is_element_with_tag_name(&local_name!("body"));
842
843 if !is_body_element {
845 return false;
846 }
847
848 let root_node = TNode::owner_doc(self);
850 let root_element = TDocument::as_node(&root_node)
851 .first_element_child()
852 .unwrap();
853 root_element.children.contains(&self.id)
854 }
855
856 fn synthesize_presentational_hints_for_legacy_attributes<V>(
857 &self,
858 _visited_handling: VisitedHandlingMode,
859 hints: &mut V,
860 ) where
861 V: Push<style::applicable_declarations::ApplicableDeclarationBlock>,
862 {
863 let Some(elem) = self.data.downcast_element() else {
864 return;
865 };
866
867 let tag = &elem.name.local;
868
869 let mut push_style = |decl: PropertyDeclaration| {
870 hints.push(ApplicableDeclarationBlock::from_declarations(
871 Arc::new(
872 self.guard()
873 .wrap(PropertyDeclarationBlock::with_one(decl, Importance::Normal)),
874 ),
875 CascadeLevel::new(CascadeOrigin::PresHints),
876 LayerOrder::root(),
877 ));
878 };
879
880 fn parse_color_attr(value: &str) -> Option<(u8, u8, u8, f32)> {
881 if !value.starts_with('#') {
882 return None;
883 }
884
885 let value = &value[1..];
886 if value.len() == 3 {
887 let r = u8::from_str_radix(&value[0..1], 16).ok()?;
888 let g = u8::from_str_radix(&value[1..2], 16).ok()?;
889 let b = u8::from_str_radix(&value[2..3], 16).ok()?;
890 return Some((r, g, b, 1.0));
891 }
892
893 if value.len() == 6 {
894 let r = u8::from_str_radix(&value[0..2], 16).ok()?;
895 let g = u8::from_str_radix(&value[2..4], 16).ok()?;
896 let b = u8::from_str_radix(&value[4..6], 16).ok()?;
897 return Some((r, g, b, 1.0));
898 }
899
900 None
901 }
902
903 fn parse_size_attr(
911 value: &str,
912 ignoring_zero: bool,
913 ) -> Option<style::values::specified::LengthPercentage> {
914 use style::servo::attr::{
915 LengthOrPercentageOrAuto, parse_length, parse_nonzero_length,
916 };
917 use style::values::specified::{LengthPercentage, NoCalcLength};
918 let parsed = if ignoring_zero {
919 parse_nonzero_length(value)
920 } else {
921 parse_length(value)
922 };
923 match parsed {
924 LengthOrPercentageOrAuto::Length(length) => Some(LengthPercentage::Length(
925 NoCalcLength::from_px(length.to_f32_px()),
926 )),
927 LengthOrPercentageOrAuto::Percentage(fraction) => Some(
928 LengthPercentage::Percentage(NoCalcPercentage::new(fraction)),
929 ),
930 LengthOrPercentageOrAuto::Auto => None,
931 }
932 }
933
934 fn parse_svg_size_attr(value: &str) -> Option<style::values::specified::LengthPercentage> {
939 use style::values::specified::{LengthPercentage, NoCalcLength};
940 use style_traits::ParsingMode;
941
942 let value = value.trim();
943 if let Some(number) = value.strip_suffix('%') {
944 let val: f32 = number.trim().parse().ok()?;
945 return (val >= 0.0)
946 .then(|| LengthPercentage::Percentage(NoCalcPercentage::new(val / 100.0)));
947 }
948
949 let number_len = value
953 .trim_end_matches(|c: char| c.is_ascii_alphabetic())
954 .len();
955 let (number, unit) = value.split_at(number_len);
956 let val: f32 = number.trim().parse().ok().filter(|v| *v >= 0.0)?;
957 let length = if unit.is_empty() {
958 NoCalcLength::from_px(val)
959 } else {
960 NoCalcLength::parse_dimension_with_flags(ParsingMode::DEFAULT, false, val, unit)
961 .ok()?
962 };
963 Some(LengthPercentage::Length(length))
964 }
965
966 let is_image_input = *tag == local_name!("input")
971 && elem.attrs().iter().any(|attr| {
972 attr.name.local == local_name!("type") && attr.value.eq_ignore_ascii_case("image")
973 });
974
975 for attr in elem.attrs() {
976 let name = &attr.name.local;
977 let value = attr.value.as_str();
978
979 if *name == local_name!("align") {
980 use style::values::specified::TextAlign;
981 let keyword = match value {
982 "left" => Some(StyloTextAlign::MozLeft),
983 "right" => Some(StyloTextAlign::MozRight),
984 "center" => Some(StyloTextAlign::MozCenter),
985 _ => None,
986 };
987
988 if let Some(keyword) = keyword {
989 push_style(PropertyDeclaration::TextAlign(TextAlign::Keyword(keyword)));
990 }
991 }
992
993 let is_width = *name == local_name!("width");
998 let is_height = *name == local_name!("height");
999 let is_embedded = *tag == local_name!("iframe")
1003 || *tag == local_name!("embed")
1004 || *tag == local_name!("video")
1005 || *tag == local_name!("object")
1006 || *tag == local_name!("img")
1007 || *tag == local_name!("marquee")
1008 || is_image_input;
1009 let maps_to_dimension = if is_width {
1010 is_embedded
1011 || *tag == local_name!("table")
1012 || *tag == local_name!("col")
1013 || *tag == local_name!("colgroup")
1014 || *tag == local_name!("tr")
1015 || *tag == local_name!("td")
1016 || *tag == local_name!("th")
1017 || *tag == local_name!("hr")
1018 } else if is_height {
1019 is_embedded
1020 || *tag == local_name!("table")
1021 || *tag == local_name!("thead")
1022 || *tag == local_name!("tbody")
1023 || *tag == local_name!("tfoot")
1024 || *tag == local_name!("tr")
1025 || *tag == local_name!("td")
1026 || *tag == local_name!("th")
1027 } else {
1028 false
1029 };
1030 if maps_to_dimension {
1031 let is_cell = *tag == local_name!("td") || *tag == local_name!("th");
1035 let ignoring_zero = is_cell || (is_width && *tag == local_name!("table"));
1036 if let Some(size) = parse_size_attr(value, ignoring_zero) {
1037 use style::values::generics::{NonNegative, length::Size};
1038 let size = Size::LengthPercentage(NonNegative(size));
1039 push_style(if is_width {
1040 PropertyDeclaration::Width(size)
1041 } else {
1042 PropertyDeclaration::Height(size)
1043 });
1044 }
1045 }
1046
1047 let takes_spacing = *tag == local_name!("embed")
1053 || *tag == local_name!("img")
1054 || *tag == local_name!("object")
1055 || *tag == local_name!("marquee")
1056 || is_image_input;
1057 if takes_spacing {
1058 let is_hspace = *name == local_name!("hspace");
1059 let is_vspace = *name == local_name!("vspace");
1060 if is_hspace || is_vspace {
1061 if let Some(size) = parse_size_attr(value, false) {
1062 use style::values::generics::length::GenericMargin;
1063 let margin = GenericMargin::LengthPercentage(size);
1064 if is_hspace {
1065 push_style(PropertyDeclaration::MarginLeft(margin.clone()));
1066 push_style(PropertyDeclaration::MarginRight(margin));
1067 } else {
1068 push_style(PropertyDeclaration::MarginTop(margin.clone()));
1069 push_style(PropertyDeclaration::MarginBottom(margin));
1070 }
1071 }
1072 }
1073 }
1074
1075 if *tag == local_name!("svg")
1081 && (*name == local_name!("width") || *name == local_name!("height"))
1082 {
1083 if let Some(size) = parse_svg_size_attr(value) {
1084 use style::values::generics::{NonNegative, length::Size};
1085 let size = Size::LengthPercentage(NonNegative(size));
1086 push_style(if *name == local_name!("width") {
1087 PropertyDeclaration::Width(size)
1088 } else {
1089 PropertyDeclaration::Height(size)
1090 });
1091 }
1092 }
1093
1094 if *name == local_name!("border")
1101 && (*tag == local_name!("img") || *tag == local_name!("object") || is_image_input)
1102 {
1103 if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1104 use style::values::specified::{BorderSideWidth, BorderStyle};
1105 let width = BorderSideWidth::from_px(px as f32);
1106 push_style(PropertyDeclaration::BorderTopWidth(width.clone()));
1107 push_style(PropertyDeclaration::BorderRightWidth(width.clone()));
1108 push_style(PropertyDeclaration::BorderBottomWidth(width.clone()));
1109 push_style(PropertyDeclaration::BorderLeftWidth(width));
1110 push_style(PropertyDeclaration::BorderTopStyle(BorderStyle::Solid));
1111 push_style(PropertyDeclaration::BorderRightStyle(BorderStyle::Solid));
1112 push_style(PropertyDeclaration::BorderBottomStyle(BorderStyle::Solid));
1113 push_style(PropertyDeclaration::BorderLeftStyle(BorderStyle::Solid));
1114 }
1115 }
1116
1117 if *tag == local_name!("body") {
1127 let sides: &[u8] = match &**name {
1130 "marginwidth" => b"lr",
1131 "marginheight" => b"tb",
1132 "leftmargin" => b"l",
1133 "topmargin" => b"t",
1134 _ => b"",
1135 };
1136 if !sides.is_empty() {
1137 if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1138 use style::values::generics::length::GenericMargin;
1139 use style::values::specified::{LengthPercentage, NoCalcLength};
1140 let margin = GenericMargin::LengthPercentage(LengthPercentage::Length(
1141 NoCalcLength::from_px(px as f32),
1142 ));
1143 for side in sides {
1144 push_style(match side {
1145 b'l' => PropertyDeclaration::MarginLeft(margin.clone()),
1146 b'r' => PropertyDeclaration::MarginRight(margin.clone()),
1147 b't' => PropertyDeclaration::MarginTop(margin.clone()),
1148 b'b' => PropertyDeclaration::MarginBottom(margin.clone()),
1149 _ => unreachable!("side table above only yields lrtb"),
1150 });
1151 }
1152 }
1153 }
1154 }
1155
1156 if *name == local_name!("bgcolor") {
1157 use style::values::specified::Color;
1158 if let Some((r, g, b, a)) = parse_color_attr(value) {
1159 push_style(PropertyDeclaration::BackgroundColor(
1160 Color::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)),
1161 ));
1162 }
1163 }
1164
1165 if *name == local_name!("hidden") {
1166 use style::values::specified::Display;
1167 push_style(PropertyDeclaration::Display(Display::None));
1168 }
1169 }
1170 }
1171
1172 fn local_name(&self) -> &LocalName {
1173 &self.element_data().expect("Not an element").name.local
1174 }
1175
1176 fn namespace(&self) -> &Namespace {
1177 &self.element_data().expect("Not an element").name.ns
1178 }
1179
1180 fn query_container_size(
1181 &self,
1182 _display: &style::values::specified::Display,
1183 ) -> euclid::default::Size2D<Option<app_units::Au>> {
1184 Default::default()
1186 }
1187
1188 fn each_custom_state<F>(&self, _callback: F)
1189 where
1190 F: FnMut(&AtomIdent),
1191 {
1192 todo!()
1193 }
1194
1195 fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
1196 self.selector_flags().get().contains(flags)
1197 }
1198
1199 fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
1200 let flags = self.selector_flags().get();
1201 if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
1202 {
1203 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING
1204 } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
1205 {
1206 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
1207 } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) {
1208 ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
1209 } else {
1210 ElementSelectorFlags::empty()
1211 }
1212 }
1213
1214 fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
1215 compute_layout_damage(old, new)
1216 }
1218
1219 }
1239
1240pub struct Traverser<'a> {
1241 parent: BlitzNode<'a>,
1243 child_index: usize,
1244}
1245
1246impl<'a> Iterator for Traverser<'a> {
1247 type Item = BlitzNode<'a>;
1248
1249 fn next(&mut self) -> Option<Self::Item> {
1250 let node_id = self.parent.layout_dom_children().get(self.child_index)?;
1254 let node = self.parent.with(*node_id);
1255
1256 self.child_index += 1;
1257
1258 Some(node)
1259 }
1260}
1261
1262impl std::hash::Hash for BlitzNode<'_> {
1263 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1264 state.write_u64(self.id.as_u64())
1265 }
1266}
1267
1268pub struct RegisteredPaintersImpl;
1272impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
1273 fn get(&self, _name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
1274 None
1275 }
1276}
1277
1278use style::traversal::recalc_style_at;
1279
1280pub struct RecalcStyle<'a> {
1281 context: SharedStyleContext<'a>,
1282}
1283
1284impl<'a> RecalcStyle<'a> {
1285 pub fn new(context: SharedStyleContext<'a>) -> Self {
1286 RecalcStyle { context }
1287 }
1288}
1289
1290#[allow(unsafe_code)]
1291impl<E> DomTraversal<E> for RecalcStyle<'_>
1292where
1293 E: TElement,
1294{
1295 fn process_preorder<F: FnMut(E::ConcreteNode)>(
1296 &self,
1297 traversal_data: &PerLevelTraversalData,
1298 context: &mut StyleContext<E>,
1299 node: E::ConcreteNode,
1300 note_child: F,
1301 ) {
1302 if let Some(el) = node.as_element() {
1303 let mut data = unsafe { el.ensure_data() };
1305 recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
1306
1307 unsafe { el.unset_dirty_descendants() }
1309 }
1310 }
1311
1312 #[inline]
1313 fn needs_postorder_traversal() -> bool {
1314 false
1315 }
1316
1317 fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
1318 panic!("this should never be called")
1319 }
1320
1321 #[inline]
1322 fn shared_context(&self) -> &SharedStyleContext<'_> {
1323 &self.context
1324 }
1325}
1326
1327#[test]
1328fn assert_size_of_equals() {
1329 }
1345
1346#[test]
1347fn parse_inline() {
1348 }