Skip to main content

blitz_dom/
stylo.rs

1//! Enable the dom to participate in styling by servo
2//!
3
4use std::ptr::NonNull;
5use std::sync::atomic::Ordering;
6
7use crate::StyleThreading;
8use crate::layout::damage::compute_layout_damage;
9use crate::node::Node;
10use crate::node::NodeData;
11use markup5ever::{LocalName, LocalNameStaticSet, Namespace, NamespaceStaticSet, local_name};
12use selectors::bloom::BLOOM_HASH_MASK;
13use selectors::{
14    Element, OpaqueElement,
15    attr::{AttrSelectorOperation, NamespaceConstraint},
16    matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode},
17    sink::Push,
18};
19use style::CaseSensitivityExt;
20use style::animation::AnimationSetKey;
21use style::animation::AnimationState;
22use style::applicable_declarations::ApplicableDeclarationBlock;
23use style::bloom::each_relevant_element_hash;
24use style::color::AbsoluteColor;
25use style::data::{ElementDataMut, ElementDataRef};
26use style::dom::AttributeProvider;
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[0])
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        // Mark actively animating nodes as dirty
81        let mut sets = self.animations.sets.write();
82        for (key, set) in sets.iter_mut() {
83            let node_id = key.node.id();
84
85            // Drop animations belonging to nodes that are no longer in the
86            // document. A removed element is never restyled, so it would never
87            // get a chance to cancel its own animations; an infinite animation
88            // would then keep `has_active_animations` set forever and force a
89            // redraw every frame. Emptying the set here lets the `retain` below
90            // discard it so the flag can clear on this same pass.
91            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        // Build the style context used by the style traversal
126        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        // components/layout_2020/lib.rs:983
139        let root = self.root_element();
140        // dbg!(root);
141        let token = RecalcStyle::pre_traverse(root, &context);
142
143        if token.should_traverse() {
144            // Style the elements, resolving their data
145            let traverser = RecalcStyle::new(context);
146            // `Sequential` bypasses Stylo's global pool. See `StyleThreading`.
147            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 = opaque.id();
155            if let Some(node) = self.nodes.get_mut(id) {
156                node.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        // Maybe run garbage collection. Stylo has internal to determine whether to run or not.
175        self.stylist.rule_tree().maybe_gc();
176
177        style::thread_state::exit(ThreadState::LAYOUT);
178    }
179}
180
181/// A handle to a node that Servo's style traits are implemented against
182///
183/// Since BlitzNodes are not persistent (IE we don't keep the pointers around between frames), we choose to just implement
184/// the tree structure in the nodes themselves, and temporarily give out pointers during the layout phase.
185type 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
236// components/styleaapper.rs:
237impl<'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        self.with(1)
264    }
265
266    fn is_in_document(&self) -> bool {
267        true
268    }
269
270    // I think this is the same as parent_node only in the cases when the direct parent is not a real element, forcing us
271    // to travel upwards
272    //
273    // For the sake of this demo, we're just going to return the parent node ann
274    fn traversal_parent(&self) -> Option<Self::ConcreteElement> {
275        self.parent_node().and_then(|node| node.as_element())
276    }
277
278    fn opaque(&self) -> OpaqueNode {
279        OpaqueNode(self.id)
280    }
281
282    fn debug_id(self) -> usize {
283        self.id
284    }
285
286    fn as_element(&self) -> Option<Self::ConcreteElement> {
287        match self.data {
288            NodeData::Element { .. } => Some(self),
289            _ => None,
290        }
291    }
292
293    fn as_document(&self) -> Option<Self::ConcreteDocument> {
294        match self.data {
295            NodeData::Document => Some(self),
296            _ => None,
297        }
298    }
299
300    fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> {
301        // TODO: implement shadow DOM
302        None
303    }
304}
305
306impl AttributeProvider for BlitzNode<'_> {
307    fn get_attr(&self, attr: &style::LocalName, _ns: &style::Namespace) -> Option<String> {
308        // TODO: filter by namespace
309        self.attr(attr.0.clone()).map(|s| s.to_string())
310    }
311}
312
313impl selectors::Element for BlitzNode<'_> {
314    type Impl = SelectorImpl;
315
316    fn opaque(&self) -> selectors::OpaqueElement {
317        // This correctly uses a unique id for the OpaqueElement (unlike using a pointer to the "slot")
318        // However, it makes it impossible for us to "rehydrate" the OpaqueElement back into an actual Element
319        // which is required to implement the `implicit_scope_for_sheet_in_shadow_root` method below
320        //
321        // We should see if selectors will accept a PR that allows us to use 128bits for the OpaqueElement. Or
322        // find some other solution that will enable "rehydration". This is required to enable and use the
323        // Shadow DOM functionality in Stylo.
324        let non_null = NonNull::new((self.id + 1) as *mut ()).unwrap();
325        OpaqueElement::from_non_null_ptr(non_null)
326    }
327
328    fn parent_element(&self) -> Option<Self> {
329        TElement::traversal_parent(self)
330    }
331
332    fn parent_node_is_shadow_root(&self) -> bool {
333        false
334    }
335
336    fn containing_shadow_host(&self) -> Option<Self> {
337        None
338    }
339
340    fn is_pseudo_element(&self) -> bool {
341        matches!(self.data, NodeData::AnonymousBlock(_))
342    }
343
344    // These methods are implemented naively since we only threaded real nodes and not fake nodes
345    // we should try and use `find` instead of this foward/backward stuff since its ugly and slow
346    fn prev_sibling_element(&self) -> Option<Self> {
347        let mut n = 1;
348        while let Some(node) = self.backward(n) {
349            if node.is_element() {
350                return Some(node);
351            }
352            n += 1;
353        }
354
355        None
356    }
357
358    fn next_sibling_element(&self) -> Option<Self> {
359        let mut n = 1;
360        while let Some(node) = self.forward(n) {
361            if node.is_element() {
362                return Some(node);
363            }
364            n += 1;
365        }
366
367        None
368    }
369
370    fn first_element_child(&self) -> Option<Self> {
371        let mut children = self.dom_children();
372        children.find(|child| child.is_element())
373    }
374
375    fn is_html_element_in_html_document(&self) -> bool {
376        true // self.has_namespace(ns!(html))
377    }
378
379    fn has_local_name(&self, local_name: &LocalName) -> bool {
380        self.data.is_element_with_tag_name(local_name)
381    }
382
383    fn has_namespace(&self, ns: &Namespace) -> bool {
384        self.element_data().expect("Not an element").name.ns == *ns
385    }
386
387    fn is_same_type(&self, other: &Self) -> bool {
388        self.local_name() == other.local_name() && self.namespace() == other.namespace()
389    }
390
391    fn attr_matches(
392        &self,
393        _ns: &NamespaceConstraint<&GenericAtomIdent<NamespaceStaticSet>>,
394        local_name: &GenericAtomIdent<LocalNameStaticSet>,
395        operation: &AttrSelectorOperation<&AtomString>,
396    ) -> bool {
397        match self.data.attr(local_name.0.clone()) {
398            None => false,
399            Some(attr_value) => operation.eval_str(attr_value),
400        }
401    }
402
403    fn match_non_ts_pseudo_class(
404        &self,
405        pseudo_class: &<Self::Impl as selectors::SelectorImpl>::NonTSPseudoClass,
406        _context: &mut MatchingContext<Self::Impl>,
407    ) -> bool {
408        match *pseudo_class {
409            NonTSPseudoClass::Active => self.element_state.contains(ElementState::ACTIVE),
410            NonTSPseudoClass::AnyLink => self
411                .data
412                .downcast_element()
413                .map(|elem| {
414                    (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
415                        && elem.attr(local_name!("href")).is_some()
416                })
417                .unwrap_or(false),
418            NonTSPseudoClass::Checked => self
419                .data
420                .downcast_element()
421                .and_then(|elem| elem.checkbox_input_checked())
422                .unwrap_or(false),
423            NonTSPseudoClass::Valid => false,
424            NonTSPseudoClass::Invalid => false,
425            NonTSPseudoClass::Defined => false,
426            NonTSPseudoClass::Disabled => self.element_state.contains(ElementState::DISABLED),
427            NonTSPseudoClass::Enabled => self.element_state.contains(ElementState::ENABLED),
428            NonTSPseudoClass::Focus => self.element_state.contains(ElementState::FOCUS),
429            NonTSPseudoClass::FocusWithin => false,
430            NonTSPseudoClass::FocusVisible => false,
431            NonTSPseudoClass::Fullscreen => false,
432            NonTSPseudoClass::Hover => self.element_state.contains(ElementState::HOVER),
433            NonTSPseudoClass::Indeterminate => false,
434            NonTSPseudoClass::Lang(_) => false,
435            NonTSPseudoClass::CustomState(_) => false,
436            NonTSPseudoClass::Link => self
437                .data
438                .downcast_element()
439                .map(|elem| {
440                    (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
441                        && elem.attr(local_name!("href")).is_some()
442                })
443                .unwrap_or(false),
444            NonTSPseudoClass::PlaceholderShown => false,
445            NonTSPseudoClass::ReadWrite => false,
446            NonTSPseudoClass::ReadOnly => false,
447            NonTSPseudoClass::ServoNonZeroBorder => false,
448            NonTSPseudoClass::Target => false,
449            NonTSPseudoClass::Visited => false,
450            NonTSPseudoClass::Autofill => false,
451            NonTSPseudoClass::Default => false,
452
453            NonTSPseudoClass::InRange => false,
454            NonTSPseudoClass::Modal => false,
455            NonTSPseudoClass::Open => false,
456            NonTSPseudoClass::Optional => false,
457            NonTSPseudoClass::OutOfRange => false,
458            NonTSPseudoClass::PopoverOpen => false,
459            NonTSPseudoClass::Required => false,
460            NonTSPseudoClass::UserInvalid => false,
461            NonTSPseudoClass::UserValid => false,
462            NonTSPseudoClass::MozMeterOptimum => false,
463            NonTSPseudoClass::MozMeterSubOptimum => false,
464            NonTSPseudoClass::MozMeterSubSubOptimum => false,
465        }
466    }
467
468    fn match_pseudo_element(
469        &self,
470        pe: &PseudoElement,
471        _context: &mut MatchingContext<Self::Impl>,
472    ) -> bool {
473        let pseudo = match self.stylo_element_data.get() {
474            Some(el) => el.styles.primary().pseudo().or(match &self.data {
475                NodeData::AnonymousBlock(_) => Some(PseudoElement::ServoAnonymousBox),
476                _ => None,
477            }),
478            None => None,
479        };
480
481        pseudo.is_some_and(|psuedo| psuedo == *pe)
482    }
483
484    fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
485        // Handle flags that apply to the element.
486        let self_flags = flags.for_self();
487        if !self_flags.is_empty() {
488            self.selector_flags
489                .set(self.selector_flags.get() | self_flags);
490        }
491
492        // Handle flags that apply to the parent.
493        let parent_flags = flags.for_parent();
494        if !parent_flags.is_empty() {
495            if let Some(parent) = self.parent_node() {
496                parent
497                    .selector_flags
498                    .set(parent.selector_flags.get() | parent_flags);
499            }
500        }
501    }
502
503    fn is_link(&self) -> bool {
504        self.data.is_element_with_tag_name(&local_name!("a"))
505    }
506
507    fn is_html_slot_element(&self) -> bool {
508        false
509    }
510
511    fn has_id(
512        &self,
513        id: &<Self::Impl as selectors::SelectorImpl>::Identifier,
514        case_sensitivity: selectors::attr::CaseSensitivity,
515    ) -> bool {
516        self.element_data()
517            .and_then(|data| data.id.as_ref())
518            .map(|id_attr| case_sensitivity.eq_atom(id_attr, id))
519            .unwrap_or(false)
520    }
521
522    fn has_class(
523        &self,
524        search_name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
525        case_sensitivity: selectors::attr::CaseSensitivity,
526    ) -> bool {
527        let class_attr = self.data.attr(local_name!("class"));
528        if let Some(class_attr) = class_attr {
529            // split the class attribute
530            for pheme in class_attr.split_ascii_whitespace() {
531                let atom = Atom::from(pheme);
532                if case_sensitivity.eq_atom(&atom, search_name) {
533                    return true;
534                }
535            }
536        }
537
538        false
539    }
540
541    fn imported_part(
542        &self,
543        _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
544    ) -> Option<<Self::Impl as selectors::SelectorImpl>::Identifier> {
545        None
546    }
547
548    fn is_part(&self, _name: &<Self::Impl as selectors::SelectorImpl>::Identifier) -> bool {
549        false
550    }
551
552    fn is_empty(&self) -> bool {
553        self.dom_children().next().is_none()
554    }
555
556    fn is_root(&self) -> bool {
557        self.parent_node()
558            .and_then(|parent| parent.parent_node())
559            .is_none()
560    }
561
562    fn has_custom_state(
563        &self,
564        _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
565    ) -> bool {
566        false
567    }
568
569    fn add_element_unique_hashes(&self, filter: &mut selectors::bloom::BloomFilter) -> bool {
570        each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
571        true
572    }
573}
574
575impl<'a> TElement for BlitzNode<'a> {
576    type ConcreteNode = BlitzNode<'a>;
577
578    type TraversalChildrenIterator = Traverser<'a>;
579
580    fn as_node(&self) -> Self::ConcreteNode {
581        self
582    }
583
584    fn implicit_scope_for_sheet_in_shadow_root(
585        _opaque_host: OpaqueElement,
586        _sheet_index: usize,
587    ) -> Option<ImplicitScopeRoot> {
588        // We cannot currently implement this as we are using the NodeId as the OpaqueElement,
589        // and need a reference to the Slab to convert it back into an Element
590        //
591        // Luckily it is only needed for shadow dom.
592        todo!();
593    }
594
595    fn traversal_children(&self) -> style::dom::LayoutIterator<Self::TraversalChildrenIterator> {
596        LayoutIterator(Traverser {
597            // dom: self.tree(),
598            parent: self,
599            child_index: 0,
600        })
601    }
602
603    fn is_html_element(&self) -> bool {
604        self.is_element()
605    }
606
607    // not implemented.....
608    fn is_mathml_element(&self) -> bool {
609        false
610    }
611
612    // need to check the namespace
613    fn is_svg_element(&self) -> bool {
614        false
615    }
616
617    fn style_attribute(&self) -> Option<ArcBorrow<'_, Locked<PropertyDeclarationBlock>>> {
618        self.element_data()
619            .expect("Not an element")
620            .style_attribute
621            .as_ref()
622            .map(|f| f.borrow_arc())
623    }
624
625    fn state(&self) -> ElementState {
626        self.element_state
627    }
628
629    fn has_part_attr(&self) -> bool {
630        false
631    }
632
633    fn exports_any_part(&self) -> bool {
634        false
635    }
636
637    fn id(&self) -> Option<&style::Atom> {
638        self.element_data().and_then(|data| data.id.as_ref())
639    }
640
641    fn each_class<F>(&self, mut callback: F)
642    where
643        F: FnMut(&style::values::AtomIdent),
644    {
645        let class_attr = self.data.attr(local_name!("class"));
646        if let Some(class_attr) = class_attr {
647            // split the class attribute
648            for pheme in class_attr.split_ascii_whitespace() {
649                let atom = Atom::from(pheme); // interns the string
650                callback(AtomIdent::cast(&atom));
651            }
652        }
653    }
654
655    fn each_attr_name<F>(&self, mut callback: F)
656    where
657        F: FnMut(&style::LocalName),
658    {
659        if let Some(attrs) = self.data.attrs() {
660            for attr in attrs.iter() {
661                callback(&GenericAtomIdent(attr.name.local.clone()));
662            }
663        }
664    }
665
666    fn has_dirty_descendants(&self) -> bool {
667        Node::has_dirty_descendants(self)
668    }
669
670    fn has_snapshot(&self) -> bool {
671        self.has_snapshot
672    }
673
674    fn handled_snapshot(&self) -> bool {
675        self.snapshot_handled.load(Ordering::SeqCst)
676    }
677
678    unsafe fn set_handled_snapshot(&self) {
679        self.snapshot_handled.store(true, Ordering::SeqCst);
680    }
681
682    unsafe fn set_dirty_descendants(&self) {
683        Node::set_dirty_descendants(self);
684        Node::mark_ancestors_dirty(self);
685    }
686
687    unsafe fn unset_dirty_descendants(&self) {
688        Node::unset_dirty_descendants(self);
689    }
690
691    fn store_children_to_process(&self, _n: isize) {
692        unimplemented!()
693    }
694
695    fn did_process_child(&self) -> isize {
696        unimplemented!()
697    }
698
699    unsafe fn ensure_data(&self) -> ElementDataMut<'_> {
700        // SAFETY: stylo traversal has exclusive access to nodes
701        unsafe { self.stylo_element_data.ensure_init() }
702    }
703
704    unsafe fn clear_data(&self) {
705        // SAFETY: stylo traversal has exclusive access to nodes
706        unsafe { self.stylo_element_data.clear() }
707    }
708
709    fn has_data(&self) -> bool {
710        self.stylo_element_data.has_data()
711    }
712
713    fn borrow_data(&self) -> Option<ElementDataRef<'_>> {
714        self.stylo_element_data.get()
715    }
716
717    fn mutate_data(&self) -> Option<ElementDataMut<'_>> {
718        unsafe { self.stylo_element_data.unsafe_stylo_only_mut() }
719    }
720
721    fn skip_item_display_fixup(&self) -> bool {
722        false
723    }
724
725    fn may_have_animations(&self) -> bool {
726        true
727    }
728
729    fn has_animations(&self, context: &SharedStyleContext) -> bool {
730        self.has_css_animations(context, None) || self.has_css_transitions(context, None)
731    }
732
733    fn has_css_animations(
734        &self,
735        context: &SharedStyleContext,
736        pseudo_element: Option<PseudoElement>,
737    ) -> bool {
738        let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
739        context.animations.has_active_animations(&key)
740    }
741
742    fn has_css_transitions(
743        &self,
744        context: &SharedStyleContext,
745        pseudo_element: Option<PseudoElement>,
746    ) -> bool {
747        let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
748        context.animations.has_active_transitions(&key)
749    }
750
751    fn animation_rule(
752        &self,
753        context: &SharedStyleContext,
754    ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
755        let opaque = TNode::opaque(&TElement::as_node(self));
756        context.animations.get_animation_declarations(
757            &AnimationSetKey::new_for_non_pseudo(opaque),
758            context.current_time_for_animations,
759            &self.guard,
760        )
761    }
762
763    fn transition_rule(
764        &self,
765        context: &SharedStyleContext,
766    ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
767        let opaque = TNode::opaque(&TElement::as_node(self));
768        context.animations.get_transition_declarations(
769            &AnimationSetKey::new_for_non_pseudo(opaque),
770            context.current_time_for_animations,
771            &self.guard,
772        )
773    }
774
775    fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
776        None
777    }
778
779    fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
780        None
781    }
782
783    fn lang_attr(&self) -> Option<style::selector_parser::AttrValue> {
784        None
785    }
786
787    fn match_element_lang(
788        &self,
789        _override_lang: Option<Option<style::selector_parser::AttrValue>>,
790        _value: &style::selector_parser::Lang,
791    ) -> bool {
792        false
793    }
794
795    fn is_html_document_body_element(&self) -> bool {
796        // Check node is a <body> element
797        let is_body_element = self.data.is_element_with_tag_name(&local_name!("body"));
798
799        // If it isn't then return early
800        if !is_body_element {
801            return false;
802        }
803
804        // If it is then check if it is a child of the root (<html>) element
805        let root_node = &self.tree()[0];
806        let root_element = TDocument::as_node(&root_node)
807            .first_element_child()
808            .unwrap();
809        root_element.children.contains(&self.id)
810    }
811
812    fn synthesize_presentational_hints_for_legacy_attributes<V>(
813        &self,
814        _visited_handling: VisitedHandlingMode,
815        hints: &mut V,
816    ) where
817        V: Push<style::applicable_declarations::ApplicableDeclarationBlock>,
818    {
819        let Some(elem) = self.data.downcast_element() else {
820            return;
821        };
822
823        let tag = &elem.name.local;
824
825        let mut push_style = |decl: PropertyDeclaration| {
826            hints.push(ApplicableDeclarationBlock::from_declarations(
827                Arc::new(
828                    self.guard
829                        .wrap(PropertyDeclarationBlock::with_one(decl, Importance::Normal)),
830                ),
831                CascadeLevel::new(CascadeOrigin::PresHints),
832                LayerOrder::root(),
833            ));
834        };
835
836        fn parse_color_attr(value: &str) -> Option<(u8, u8, u8, f32)> {
837            if !value.starts_with('#') {
838                return None;
839            }
840
841            let value = &value[1..];
842            if value.len() == 3 {
843                let r = u8::from_str_radix(&value[0..1], 16).ok()?;
844                let g = u8::from_str_radix(&value[1..2], 16).ok()?;
845                let b = u8::from_str_radix(&value[2..3], 16).ok()?;
846                return Some((r, g, b, 1.0));
847            }
848
849            if value.len() == 6 {
850                let r = u8::from_str_radix(&value[0..2], 16).ok()?;
851                let g = u8::from_str_radix(&value[2..4], 16).ok()?;
852                let b = u8::from_str_radix(&value[4..6], 16).ok()?;
853                return Some((r, g, b, 1.0));
854            }
855
856            None
857        }
858
859        fn parse_size_attr(
860            value: &str,
861            filter_fn: impl FnOnce(&f32) -> bool,
862        ) -> Option<style::values::specified::LengthPercentage> {
863            use style::values::specified::{LengthPercentage, NoCalcLength};
864            if let Some(value) = value.strip_suffix("px") {
865                let val: f32 = value.parse().ok()?;
866                return Some(LengthPercentage::Length(NoCalcLength::from_px(val)));
867            }
868
869            if let Some(value) = value.strip_suffix("%") {
870                let val: f32 = value.parse().ok()?;
871                return Some(LengthPercentage::Percentage(NoCalcPercentage::new(
872                    val / 100.0,
873                )));
874            }
875
876            let val: f32 = value.parse().ok().filter(filter_fn)?;
877            Some(LengthPercentage::Length(NoCalcLength::from_px(val)))
878        }
879
880        /// Parse the value of an SVG `width`/`height` presentation attribute.
881        /// Unlike the legacy HTML dimension attributes, these accept any CSS
882        /// <length-percentage> (e.g. `1em`), and a unitless number means user
883        /// units, which map to CSS px.
884        fn parse_svg_size_attr(value: &str) -> Option<style::values::specified::LengthPercentage> {
885            use style::values::specified::{LengthPercentage, NoCalcLength};
886            use style_traits::ParsingMode;
887
888            let value = value.trim();
889            if let Some(number) = value.strip_suffix('%') {
890                let val: f32 = number.trim().parse().ok()?;
891                return (val >= 0.0)
892                    .then(|| LengthPercentage::Percentage(NoCalcPercentage::new(val / 100.0)));
893            }
894
895            // Split into number and unit: the unit is the trailing run of
896            // ASCII alphabetic characters (this never eats into a scientific
897            // exponent such as `1e3`, which ends in a digit).
898            let number_len = value
899                .trim_end_matches(|c: char| c.is_ascii_alphabetic())
900                .len();
901            let (number, unit) = value.split_at(number_len);
902            let val: f32 = number.trim().parse().ok().filter(|v| *v >= 0.0)?;
903            let length = if unit.is_empty() {
904                NoCalcLength::from_px(val)
905            } else {
906                NoCalcLength::parse_dimension_with_flags(ParsingMode::DEFAULT, false, val, unit)
907                    .ok()?
908            };
909            Some(LengthPercentage::Length(length))
910        }
911
912        for attr in elem.attrs() {
913            let name = &attr.name.local;
914            let value = attr.value.as_str();
915
916            if *name == local_name!("align") {
917                use style::values::specified::TextAlign;
918                let keyword = match value {
919                    "left" => Some(StyloTextAlign::MozLeft),
920                    "right" => Some(StyloTextAlign::MozRight),
921                    "center" => Some(StyloTextAlign::MozCenter),
922                    _ => None,
923                };
924
925                if let Some(keyword) = keyword {
926                    push_style(PropertyDeclaration::TextAlign(TextAlign::Keyword(keyword)));
927                }
928            }
929
930            // https://html.spec.whatwg.org/multipage/rendering.html#dimRendering
931            if *name == local_name!("width")
932                && (*tag == local_name!("table")
933                    || *tag == local_name!("col")
934                    || *tag == local_name!("tr")
935                    || *tag == local_name!("td")
936                    || *tag == local_name!("th")
937                    || *tag == local_name!("hr"))
938            {
939                let is_table = *tag == local_name!("table");
940                if let Some(width) = parse_size_attr(value, |v| !is_table || *v != 0.0) {
941                    use style::values::generics::{NonNegative, length::Size};
942
943                    push_style(PropertyDeclaration::Width(Size::LengthPercentage(
944                        NonNegative(width),
945                    )));
946                }
947            }
948
949            if *name == local_name!("height")
950                && (*tag == local_name!("table")
951                    || *tag == local_name!("thead")
952                    || *tag == local_name!("tbody")
953                    || *tag == local_name!("tfoot"))
954            {
955                if let Some(height) = parse_size_attr(value, |_| true) {
956                    use style::values::generics::{NonNegative, length::Size};
957                    push_style(PropertyDeclaration::Height(Size::LengthPercentage(
958                        NonNegative(height),
959                    )));
960                }
961            }
962
963            // https://svgwg.org/svg2-draft/geometry.html#Sizing
964            // The `width` and `height` attributes on an `<svg>` element are
965            // presentation attributes that map to the CSS `width`/`height`
966            // properties, so e.g. `width="1em"` must resolve against the
967            // element's font-size like any other CSS length.
968            if *tag == local_name!("svg")
969                && (*name == local_name!("width") || *name == local_name!("height"))
970            {
971                if let Some(size) = parse_svg_size_attr(value) {
972                    use style::values::generics::{NonNegative, length::Size};
973                    let size = Size::LengthPercentage(NonNegative(size));
974                    push_style(if *name == local_name!("width") {
975                        PropertyDeclaration::Width(size)
976                    } else {
977                        PropertyDeclaration::Height(size)
978                    });
979                }
980            }
981
982            if *name == local_name!("bgcolor") {
983                use style::values::specified::Color;
984                if let Some((r, g, b, a)) = parse_color_attr(value) {
985                    push_style(PropertyDeclaration::BackgroundColor(
986                        Color::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)),
987                    ));
988                }
989            }
990
991            if *name == local_name!("hidden") {
992                use style::values::specified::Display;
993                push_style(PropertyDeclaration::Display(Display::None));
994            }
995        }
996    }
997
998    fn local_name(&self) -> &LocalName {
999        &self.element_data().expect("Not an element").name.local
1000    }
1001
1002    fn namespace(&self) -> &Namespace {
1003        &self.element_data().expect("Not an element").name.ns
1004    }
1005
1006    fn query_container_size(
1007        &self,
1008        _display: &style::values::specified::Display,
1009    ) -> euclid::default::Size2D<Option<app_units::Au>> {
1010        // FIXME: Implement container queries. For now this effectively disables them without panicking.
1011        Default::default()
1012    }
1013
1014    fn each_custom_state<F>(&self, _callback: F)
1015    where
1016        F: FnMut(&AtomIdent),
1017    {
1018        todo!()
1019    }
1020
1021    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
1022        self.selector_flags.get().contains(flags)
1023    }
1024
1025    fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
1026        let flags = self.selector_flags.get();
1027        if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
1028        {
1029            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING
1030        } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
1031        {
1032            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
1033        } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) {
1034            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
1035        } else {
1036            ElementSelectorFlags::empty()
1037        }
1038    }
1039
1040    fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
1041        compute_layout_damage(old, new)
1042        // ALL_DAMAGE
1043    }
1044
1045    // fn update_animations(
1046    //     &self,
1047    //     before_change_style: Option<Arc<ComputedValues>>,
1048    //     tasks: style::context::UpdateAnimationsTasks,
1049    // ) {
1050    //     todo!()
1051    // }
1052
1053    // fn process_post_animation(&self, tasks: style::context::PostAnimationTasks) {
1054    //     todo!()
1055    // }
1056
1057    // fn needs_transitions_update(
1058    //     &self,
1059    //     before_change_style: &ComputedValues,
1060    //     after_change_style: &ComputedValues,
1061    // ) -> bool {
1062    //     todo!()
1063    // }
1064}
1065
1066pub struct Traverser<'a> {
1067    // dom: &'a Slab<Node>,
1068    parent: BlitzNode<'a>,
1069    child_index: usize,
1070}
1071
1072impl<'a> Iterator for Traverser<'a> {
1073    type Item = BlitzNode<'a>;
1074
1075    fn next(&mut self) -> Option<Self::Item> {
1076        let node_id = self.parent.children.get(self.child_index)?;
1077        let node = self.parent.with(*node_id);
1078
1079        self.child_index += 1;
1080
1081        Some(node)
1082    }
1083}
1084
1085impl std::hash::Hash for BlitzNode<'_> {
1086    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1087        state.write_usize(self.id)
1088    }
1089}
1090
1091/// Handle custom painters like images for layouting
1092///
1093/// todo: actually implement this
1094pub struct RegisteredPaintersImpl;
1095impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
1096    fn get(&self, _name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
1097        None
1098    }
1099}
1100
1101use style::traversal::recalc_style_at;
1102
1103pub struct RecalcStyle<'a> {
1104    context: SharedStyleContext<'a>,
1105}
1106
1107impl<'a> RecalcStyle<'a> {
1108    pub fn new(context: SharedStyleContext<'a>) -> Self {
1109        RecalcStyle { context }
1110    }
1111}
1112
1113#[allow(unsafe_code)]
1114impl<E> DomTraversal<E> for RecalcStyle<'_>
1115where
1116    E: TElement,
1117{
1118    fn process_preorder<F: FnMut(E::ConcreteNode)>(
1119        &self,
1120        traversal_data: &PerLevelTraversalData,
1121        context: &mut StyleContext<E>,
1122        node: E::ConcreteNode,
1123        note_child: F,
1124    ) {
1125        if let Some(el) = node.as_element() {
1126            // let mut data = el.mutate_data().unwrap();
1127            let mut data = unsafe { el.ensure_data() };
1128            recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
1129
1130            // Gets set later on
1131            unsafe { el.unset_dirty_descendants() }
1132        }
1133    }
1134
1135    #[inline]
1136    fn needs_postorder_traversal() -> bool {
1137        false
1138    }
1139
1140    fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
1141        panic!("this should never be called")
1142    }
1143
1144    #[inline]
1145    fn shared_context(&self) -> &SharedStyleContext<'_> {
1146        &self.context
1147    }
1148}
1149
1150#[test]
1151fn assert_size_of_equals() {
1152    // use std::mem;
1153
1154    // fn assert_layout<E>() {
1155    //     assert_eq!(
1156    //         mem::size_of::<SharingCache<E>>(),
1157    //         mem::size_of::<TypelessSharingCache>()
1158    //     );
1159    //     assert_eq!(
1160    //         mem::align_of::<SharingCache<E>>(),
1161    //         mem::align_of::<TypelessSharingCache>()
1162    //     );
1163    // }
1164
1165    // let size = mem::size_of::<StyleSharingCandidate<BlitzNode>>();
1166    // dbg!(size);
1167}
1168
1169#[test]
1170fn parse_inline() {
1171    // let attrs = style::attr::AttrValue::from_serialized_tokenlist(
1172    //     r#"visibility: hidden; left: 1306.5px; top: 50px; display: none;"#.to_string(),
1173    // );
1174
1175    // let val = CSSInlineStyleDeclaration();
1176}