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