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