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