Skip to main content

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