Skip to main content

blitz_dom/
stylo.rs

1//! Enable the dom to participate in styling by servo
2//!
3
4use blitz_traits::node_id::NodeId;
5use std::ptr::NonNull;
6use std::sync::atomic::Ordering;
7
8use crate::StyleThreading;
9use crate::layout::damage::compute_layout_damage;
10use crate::node::Node;
11use crate::node::NodeData;
12use markup5ever::{LocalName, LocalNameStaticSet, Namespace, NamespaceStaticSet, local_name};
13use selectors::bloom::BLOOM_HASH_MASK;
14use selectors::{
15    Element, OpaqueElement,
16    attr::{AttrSelectorOperation, NamespaceConstraint},
17    matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode},
18    sink::Push,
19};
20use style::CaseSensitivityExt;
21use style::animation::AnimationSetKey;
22use style::animation::AnimationState;
23use style::applicable_declarations::ApplicableDeclarationBlock;
24use style::bloom::each_relevant_element_hash;
25use style::color::AbsoluteColor;
26use style::data::{ElementDataMut, ElementDataRef};
27use style::global_style_data::STYLE_THREAD_POOL;
28use style::invalidation::element::restyle_hints::RestyleHint;
29use style::properties::ComputedValues;
30use style::properties::{Importance, PropertyDeclaration};
31use style::rule_tree::CascadeLevel;
32use style::rule_tree::CascadeOrigin;
33use style::selector_parser::PseudoElement;
34use style::selector_parser::RestyleDamage;
35use style::stylesheets::layer_rule::LayerOrder;
36use style::stylesheets::scope_rule::ImplicitScopeRoot;
37use style::values::AtomString;
38use style::values::specified::NoCalcPercentage;
39use style::{
40    Atom,
41    context::{
42        QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters,
43        SharedStyleContext, StyleContext,
44    },
45    dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot},
46    global_style_data::GLOBAL_STYLE_DATA,
47    properties::PropertyDeclarationBlock,
48    selector_parser::{NonTSPseudoClass, SelectorImpl},
49    servo_arc::{Arc, ArcBorrow},
50    shared_lock::{Locked, SharedRwLock, StylesheetGuards},
51    thread_state::ThreadState,
52    traversal::{DomTraversal, PerLevelTraversalData},
53    traversal_flags::TraversalFlags,
54    values::{AtomIdent, GenericAtomIdent},
55};
56use style_dom::ElementState;
57
58use style::values::computed::text::TextAlign as StyloTextAlign;
59
60impl crate::document::BaseDocument {
61    pub fn resolve_stylist(&mut self, now: f64) {
62        style::thread_state::enter(ThreadState::LAYOUT);
63
64        #[cfg(feature = "shadow-dom")]
65        {
66            // Keep this entry point safe for callers that resolve styles
67            // without going through BaseDocument::resolve.
68            self.compute_flattened_trees();
69            self.flush_shadow_styles();
70        }
71
72        let guard = &self.guard;
73        let guards = StylesheetGuards {
74            author: &guard.read(),
75            ua_or_user: &guard.read(),
76        };
77
78        let root = TDocument::as_node(&&self.nodes[self.root_node_id])
79            .first_element_child()
80            .unwrap()
81            .as_element()
82            .unwrap();
83
84        self.stylist
85            .flush(&guards)
86            .process_style(root, Some(&self.snapshots));
87
88        // Mark actively animating nodes as dirty
89        let mut sets = self.animations.sets.write();
90        for (key, set) in sets.iter_mut() {
91            let node_id = NodeId::from_u64(key.node.id() as u64);
92
93            // Drop animations belonging to nodes that are no longer in the
94            // document. A removed element is never restyled, so it would never
95            // get a chance to cancel its own animations; an infinite animation
96            // would then keep `has_active_animations` set forever and force a
97            // redraw every frame. Emptying the set here lets the `retain` below
98            // discard it so the flag can clear on this same pass.
99            let in_document = self
100                .nodes
101                .get(node_id)
102                .is_some_and(|node| node.flags.is_in_document());
103            if !in_document {
104                set.animations.clear();
105                set.transitions.clear();
106                continue;
107            }
108
109            self.nodes[node_id].set_restyle_hint(RestyleHint::RESTYLE_SELF);
110
111            for animation in set.animations.iter_mut() {
112                if animation.state == AnimationState::Pending && animation.started_at <= now {
113                    animation.state = AnimationState::Running;
114                }
115                animation.iterate_if_necessary(now);
116
117                if animation.state == AnimationState::Running && animation.has_ended(now) {
118                    animation.state = AnimationState::Finished;
119                }
120            }
121
122            for transition in set.transitions.iter_mut() {
123                if transition.state == AnimationState::Pending && transition.start_time <= now {
124                    transition.state = AnimationState::Running;
125                }
126                if transition.state == AnimationState::Running && transition.has_ended(now) {
127                    transition.state = AnimationState::Finished;
128                }
129            }
130        }
131        drop(sets);
132
133        // Build the style context used by the style traversal
134        let context = SharedStyleContext {
135            traversal_flags: TraversalFlags::empty(),
136            stylist: &self.stylist,
137            options: GLOBAL_STYLE_DATA.options.clone(),
138            guards,
139            visited_styles_enabled: false,
140            animations: self.animations.clone(),
141            current_time_for_animations: now,
142            snapshot_map: &self.snapshots,
143            registered_speculative_painters: &RegisteredPaintersImpl,
144        };
145
146        // components/layout_2020/lib.rs:983
147        let root = self.root_element();
148        // dbg!(root);
149        let token = RecalcStyle::pre_traverse(root, &context);
150
151        if token.should_traverse() {
152            // Style the elements, resolving their data
153            let traverser = RecalcStyle::new(context);
154            // `Sequential` bypasses Stylo's global pool. See `StyleThreading`.
155            let pool_guard = matches!(self.style_threading, StyleThreading::Parallel)
156                .then(|| STYLE_THREAD_POOL.pool());
157            let rayon_pool = pool_guard.as_ref().and_then(|g| g.as_ref());
158            style::driver::traverse_dom(&traverser, token, rayon_pool);
159        }
160
161        for opaque in self.snapshots.keys() {
162            let id = NodeId::from_u64(opaque.id() as u64);
163            if let Some(node) = self.nodes.get_mut(id) {
164                node.set_has_snapshot(false);
165            }
166        }
167        self.snapshots.clear();
168
169        let mut sets = self.animations.sets.write();
170        for set in sets.values_mut() {
171            set.clear_canceled_animations();
172            for animation in set.animations.iter_mut() {
173                animation.is_new = false;
174            }
175            for transition in set.transitions.iter_mut() {
176                transition.is_new = false;
177            }
178        }
179        sets.retain(|_, state| !state.is_empty());
180        self.has_active_animations = sets.values().any(|state| state.needs_animation_ticks());
181
182        // Maybe run garbage collection. Stylo has internal to determine whether to run or not.
183        self.stylist.rule_tree().maybe_gc();
184
185        style::thread_state::exit(ThreadState::LAYOUT);
186    }
187}
188
189/// A handle to a node that Servo's style traits are implemented against
190///
191/// Since BlitzNodes are not persistent (IE we don't keep the pointers around between frames), we choose to just implement
192/// the tree structure in the nodes themselves, and temporarily give out pointers during the layout phase.
193type BlitzNode<'a> = &'a Node;
194
195impl<'a> TDocument for BlitzNode<'a> {
196    type ConcreteNode = BlitzNode<'a>;
197
198    fn as_node(&self) -> Self::ConcreteNode {
199        self
200    }
201
202    fn is_html_document(&self) -> bool {
203        Node::is_html_document(self)
204    }
205
206    fn quirks_mode(&self) -> QuirksMode {
207        match &self.data {
208            NodeData::Document(data) if data.quirks_mode == 1 => QuirksMode::LimitedQuirks,
209            NodeData::Document(data) if data.quirks_mode == 2 => QuirksMode::Quirks,
210            _ => QuirksMode::NoQuirks,
211        }
212    }
213
214    fn shared_lock(&self) -> &SharedRwLock {
215        self.guard()
216    }
217}
218
219impl NodeInfo for BlitzNode<'_> {
220    fn is_element(&self) -> bool {
221        Node::is_element(self)
222    }
223
224    fn is_text_node(&self) -> bool {
225        Node::is_text_node(self)
226    }
227}
228
229impl<'a> TShadowRoot for BlitzNode<'a> {
230    type ConcreteNode = BlitzNode<'a>;
231
232    fn as_node(&self) -> Self::ConcreteNode {
233        self
234    }
235
236    fn host(&self) -> <Self::ConcreteNode as TNode>::ConcreteElement {
237        self.with(
238            self.shadow_root_data()
239                .expect("TShadowRoot requires a shadow root")
240                .host,
241        )
242    }
243
244    fn style_data<'b>(&self) -> Option<&'b style::stylist::CascadeData>
245    where
246        Self: 'b,
247    {
248        self.shadow_root_data()
249            .and_then(|root| root.cascade_data.as_deref())
250    }
251}
252
253// components/styleaapper.rs:
254impl<'a> TNode for BlitzNode<'a> {
255    type ConcreteElement = BlitzNode<'a>;
256    type ConcreteDocument = BlitzNode<'a>;
257    type ConcreteShadowRoot = BlitzNode<'a>;
258
259    fn parent_node(&self) -> Option<Self> {
260        self.parent.map(|id| self.with(id))
261    }
262
263    fn first_child(&self) -> Option<Self> {
264        self.layout_dom_children().first().map(|id| self.with(*id))
265    }
266
267    fn last_child(&self) -> Option<Self> {
268        self.layout_dom_children().last().map(|id| self.with(*id))
269    }
270
271    fn prev_sibling(&self) -> Option<Self> {
272        let parent = self.flattened_parent()?;
273        let children = parent.layout_dom_children();
274        let index = children.iter().position(|id| *id == self.id)?;
275        children.get(index.checked_sub(1)?).map(|id| self.with(*id))
276    }
277
278    fn next_sibling(&self) -> Option<Self> {
279        let parent = self.flattened_parent()?;
280        let children = parent.layout_dom_children();
281        let index = children.iter().position(|id| *id == self.id)?;
282        children.get(index + 1).map(|id| self.with(*id))
283    }
284
285    fn owner_doc(&self) -> Self::ConcreteDocument {
286        if let Some(owner) = self.owner_document {
287            return self.with(owner);
288        }
289        // Document nodes have no owner.
290        let mut node = *self;
291        while let Some(parent_id) = node.parent {
292            node = node.with(parent_id);
293        }
294        node
295    }
296
297    fn is_in_document(&self) -> bool {
298        true
299    }
300
301    fn traversal_parent(&self) -> Option<Self::ConcreteElement> {
302        self.flattened_parent().and_then(|node| node.as_element())
303    }
304
305    fn opaque(&self) -> OpaqueNode {
306        OpaqueNode(self.id.as_u64() as usize)
307    }
308
309    fn debug_id(self) -> usize {
310        self.id.as_u64() as usize
311    }
312
313    fn as_element(&self) -> Option<Self::ConcreteElement> {
314        match self.data {
315            NodeData::Element { .. } => Some(self),
316            _ => None,
317        }
318    }
319
320    fn as_document(&self) -> Option<Self::ConcreteDocument> {
321        match self.data {
322            NodeData::Document(_) => Some(self),
323            _ => None,
324        }
325    }
326
327    fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> {
328        self.is_shadow_root().then_some(*self)
329    }
330}
331
332impl selectors::Element for BlitzNode<'_> {
333    type Impl = SelectorImpl;
334
335    fn opaque(&self) -> selectors::OpaqueElement {
336        // This correctly uses a unique id for the OpaqueElement (unlike using a pointer to the "slot")
337        // However, it makes it impossible for us to "rehydrate" the OpaqueElement back into an actual Element
338        // which is required to implement the `implicit_scope_for_sheet_in_shadow_root` method below
339        //
340        // We should see if selectors will accept a PR that allows us to use 128bits for the OpaqueElement. Or
341        // find some other solution that will enable "rehydration". This is required to enable and use the
342        // Shadow DOM functionality in Stylo.
343        let non_null =
344            NonNull::new((self.id.as_u64() as usize).wrapping_add(1) as *mut ()).unwrap();
345        OpaqueElement::from_non_null_ptr(non_null)
346    }
347
348    fn parent_element(&self) -> Option<Self> {
349        // Selector relationships follow the DOM tree. Inheritance and style
350        // traversal use traversal_parent instead.
351        self.parent_node().and_then(|parent| parent.as_element())
352    }
353
354    fn parent_node_is_shadow_root(&self) -> bool {
355        self.parent_node()
356            .is_some_and(|parent| parent.is_shadow_root())
357    }
358
359    fn containing_shadow_host(&self) -> Option<Self> {
360        let mut node = *self;
361        while let Some(parent) = node.parent_node() {
362            if let Some(root) = parent.shadow_root_data() {
363                return Some(self.with(root.host));
364            }
365            node = parent;
366        }
367        None
368    }
369
370    fn is_pseudo_element(&self) -> bool {
371        matches!(self.data, NodeData::AnonymousBlock(_))
372    }
373
374    // These methods are implemented naively since we only threaded real nodes and not fake nodes
375    // we should try and use `find` instead of this foward/backward stuff since its ugly and slow
376    fn prev_sibling_element(&self) -> Option<Self> {
377        let mut n = 1;
378        while let Some(node) = self.backward(n) {
379            if node.is_element() {
380                return Some(node);
381            }
382            n += 1;
383        }
384
385        None
386    }
387
388    fn next_sibling_element(&self) -> Option<Self> {
389        let mut n = 1;
390        while let Some(node) = self.forward(n) {
391            if node.is_element() {
392                return Some(node);
393            }
394            n += 1;
395        }
396
397        None
398    }
399
400    fn first_element_child(&self) -> Option<Self> {
401        // Selector relationships use DOM children, independently of Stylo's
402        // flattened traversal links.
403        self.children
404            .iter()
405            .find_map(|id| self.with(*id).as_element())
406    }
407
408    fn is_html_element_in_html_document(&self) -> bool {
409        Node::is_html_document(self)
410            && self
411                .element_data()
412                .is_some_and(|element| element.name.ns == markup5ever::ns!(html))
413    }
414
415    fn has_local_name(&self, local_name: &LocalName) -> bool {
416        self.data.is_element_with_tag_name(local_name)
417    }
418
419    fn has_namespace(&self, ns: &Namespace) -> bool {
420        self.element_data().expect("Not an element").name.ns == *ns
421    }
422
423    fn is_same_type(&self, other: &Self) -> bool {
424        self.local_name() == other.local_name() && self.namespace() == other.namespace()
425    }
426
427    fn attr_matches(
428        &self,
429        _ns: &NamespaceConstraint<&GenericAtomIdent<NamespaceStaticSet>>,
430        local_name: &GenericAtomIdent<LocalNameStaticSet>,
431        operation: &AttrSelectorOperation<&AtomString>,
432    ) -> bool {
433        match self.data.attr(local_name.0.clone()) {
434            None => false,
435            Some(attr_value) => operation.eval_str(attr_value),
436        }
437    }
438
439    fn match_non_ts_pseudo_class(
440        &self,
441        pseudo_class: &<Self::Impl as selectors::SelectorImpl>::NonTSPseudoClass,
442        _context: &mut MatchingContext<Self::Impl>,
443    ) -> bool {
444        match *pseudo_class {
445            NonTSPseudoClass::Active => self.element_state().contains(ElementState::ACTIVE),
446            NonTSPseudoClass::AnyLink => self
447                .data
448                .downcast_element()
449                .map(|elem| {
450                    (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
451                        && elem.attr(local_name!("href")).is_some()
452                })
453                .unwrap_or(false),
454            NonTSPseudoClass::Checked => self
455                .data
456                .downcast_element()
457                .and_then(|elem| elem.checkbox_input_checked())
458                .unwrap_or(false),
459            NonTSPseudoClass::Valid => false,
460            NonTSPseudoClass::Invalid => false,
461            NonTSPseudoClass::Defined => false,
462            NonTSPseudoClass::Disabled => self.element_state().contains(ElementState::DISABLED),
463            NonTSPseudoClass::Enabled => self.element_state().contains(ElementState::ENABLED),
464            NonTSPseudoClass::Focus => self.element_state().contains(ElementState::FOCUS),
465            NonTSPseudoClass::FocusWithin => false,
466            NonTSPseudoClass::FocusVisible => false,
467            NonTSPseudoClass::Fullscreen => false,
468            NonTSPseudoClass::Hover => self.element_state().contains(ElementState::HOVER),
469            NonTSPseudoClass::Indeterminate => false,
470            NonTSPseudoClass::Lang(_) => false,
471            NonTSPseudoClass::CustomState(_) => false,
472            NonTSPseudoClass::Link => self
473                .data
474                .downcast_element()
475                .map(|elem| {
476                    (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
477                        && elem.attr(local_name!("href")).is_some()
478                })
479                .unwrap_or(false),
480            NonTSPseudoClass::PlaceholderShown => false,
481            NonTSPseudoClass::ReadWrite => false,
482            NonTSPseudoClass::ReadOnly => false,
483            NonTSPseudoClass::ServoNonZeroBorder => false,
484            NonTSPseudoClass::Target => false,
485            NonTSPseudoClass::Visited => false,
486            NonTSPseudoClass::Autofill => false,
487            NonTSPseudoClass::Default => false,
488
489            NonTSPseudoClass::InRange => false,
490            NonTSPseudoClass::Modal => false,
491            NonTSPseudoClass::Open => false,
492            NonTSPseudoClass::Optional => false,
493            NonTSPseudoClass::OutOfRange => false,
494            NonTSPseudoClass::PopoverOpen => false,
495            NonTSPseudoClass::Required => false,
496            NonTSPseudoClass::UserInvalid => false,
497            NonTSPseudoClass::UserValid => false,
498            NonTSPseudoClass::MozMeterOptimum => false,
499            NonTSPseudoClass::MozMeterSubOptimum => false,
500            NonTSPseudoClass::MozMeterSubSubOptimum => false,
501        }
502    }
503
504    fn match_pseudo_element(
505        &self,
506        pe: &PseudoElement,
507        _context: &mut MatchingContext<Self::Impl>,
508    ) -> bool {
509        let pseudo = match self.stylo_element_data_opt().and_then(|s| s.get()) {
510            Some(el) => el
511                .styles
512                .get_primary()
513                .and_then(|s| s.pseudo())
514                .or(match &self.data {
515                    NodeData::AnonymousBlock(_) => Some(PseudoElement::ServoAnonymousBox),
516                    _ => None,
517                }),
518            None => None,
519        };
520
521        pseudo.is_some_and(|psuedo| psuedo == *pe)
522    }
523
524    fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
525        // Handle flags that apply to the element.
526        let self_flags = flags.for_self();
527        if !self_flags.is_empty() {
528            self.selector_flags()
529                .set(self.selector_flags().get() | self_flags);
530        }
531
532        // Handle flags that apply to the parent.
533        let parent_flags = flags.for_parent();
534        if !parent_flags.is_empty() {
535            if let Some(parent) = self.parent_node() {
536                parent
537                    .selector_flags()
538                    .set(parent.selector_flags().get() | parent_flags);
539            }
540        }
541    }
542
543    fn is_link(&self) -> bool {
544        self.data.is_element_with_tag_name(&local_name!("a"))
545    }
546
547    fn is_html_slot_element(&self) -> bool {
548        self.element_data().is_some_and(|element| {
549            element.name.ns == markup5ever::ns!(html) && element.name.local == local_name!("slot")
550        })
551    }
552
553    fn assigned_slot(&self) -> Option<Self> {
554        #[cfg(feature = "shadow-dom")]
555        {
556            self.assigned_slot.map(|id| self.with(id))
557        }
558        #[cfg(not(feature = "shadow-dom"))]
559        {
560            None
561        }
562    }
563
564    fn has_id(
565        &self,
566        id: &<Self::Impl as selectors::SelectorImpl>::Identifier,
567        case_sensitivity: selectors::attr::CaseSensitivity,
568    ) -> bool {
569        self.element_data()
570            .and_then(|data| data.id.as_ref())
571            .map(|id_attr| case_sensitivity.eq_atom(id_attr, id))
572            .unwrap_or(false)
573    }
574
575    fn has_class(
576        &self,
577        search_name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
578        case_sensitivity: selectors::attr::CaseSensitivity,
579    ) -> bool {
580        let class_attr = self.data.attr(local_name!("class"));
581        if let Some(class_attr) = class_attr {
582            // split the class attribute
583            for pheme in class_attr.split_ascii_whitespace() {
584                let atom = Atom::from(pheme);
585                if case_sensitivity.eq_atom(&atom, search_name) {
586                    return true;
587                }
588            }
589        }
590
591        false
592    }
593
594    fn imported_part(
595        &self,
596        name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
597    ) -> Option<<Self::Impl as selectors::SelectorImpl>::Identifier> {
598        self.attr(markup5ever::LocalName::from("exportparts"))?
599            .split(',')
600            .find_map(|mapping| {
601                let mut names = mapping.trim().split(':');
602                let inner = names.next()?.trim();
603                let outer = names.next().map(str::trim).unwrap_or(inner);
604                if names.next().is_none() && outer == name.0.as_ref() {
605                    Some(GenericAtomIdent(Atom::from(inner)))
606                } else {
607                    None
608                }
609            })
610    }
611
612    fn is_part(&self, name: &<Self::Impl as selectors::SelectorImpl>::Identifier) -> bool {
613        self.attr(markup5ever::LocalName::from("part"))
614            .is_some_and(|parts| {
615                parts
616                    .split_ascii_whitespace()
617                    .any(|part| part == name.0.as_ref())
618            })
619    }
620
621    fn is_empty(&self) -> bool {
622        self.children.is_empty()
623    }
624
625    fn is_root(&self) -> bool {
626        self.parent_node()
627            .is_some_and(|parent| parent.as_document().is_some())
628    }
629
630    fn has_custom_state(
631        &self,
632        _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
633    ) -> bool {
634        false
635    }
636
637    fn add_element_unique_hashes(&self, filter: &mut selectors::bloom::BloomFilter) -> bool {
638        each_relevant_element_hash(*self, |hash| filter.insert_hash(hash & BLOOM_HASH_MASK));
639        true
640    }
641}
642
643impl<'a> TElement for BlitzNode<'a> {
644    type ConcreteNode = BlitzNode<'a>;
645
646    type TraversalChildrenIterator = Traverser<'a>;
647
648    fn as_node(&self) -> Self::ConcreteNode {
649        self
650    }
651
652    fn implicit_scope_for_sheet_in_shadow_root(
653        _opaque_host: OpaqueElement,
654        _sheet_index: usize,
655    ) -> Option<ImplicitScopeRoot> {
656        // DocumentStyleSheet has no implicit scope root. Authored @scope
657        // rules carry their own scope selectors.
658        None
659    }
660
661    fn traversal_children(&self) -> style::dom::LayoutIterator<Self::TraversalChildrenIterator> {
662        LayoutIterator(Traverser {
663            // dom: self.tree(),
664            parent: self,
665            child_index: 0,
666        })
667    }
668
669    fn inheritance_parent(&self) -> Option<Self> {
670        // Style sharing and cascading must use the same parent whose
671        // traversal_children scheduled this element.
672        TNode::traversal_parent(self)
673    }
674
675    fn is_html_element(&self) -> bool {
676        self.is_element()
677    }
678
679    // not implemented.....
680    fn is_mathml_element(&self) -> bool {
681        false
682    }
683
684    // need to check the namespace
685    fn is_svg_element(&self) -> bool {
686        false
687    }
688
689    fn style_attribute(&self) -> Option<ArcBorrow<'_, Locked<PropertyDeclarationBlock>>> {
690        self.element_data()
691            .expect("Not an element")
692            .style_attribute
693            .as_ref()
694            .map(|f| f.borrow_arc())
695    }
696
697    fn state(&self) -> ElementState {
698        *self.element_state()
699    }
700
701    fn has_part_attr(&self) -> bool {
702        self.attr(markup5ever::LocalName::from("part")).is_some()
703    }
704
705    fn exports_any_part(&self) -> bool {
706        self.attr(markup5ever::LocalName::from("exportparts"))
707            .is_some()
708    }
709
710    fn each_part<F>(&self, mut callback: F)
711    where
712        F: FnMut(&AtomIdent),
713    {
714        if let Some(parts) = self.attr(markup5ever::LocalName::from("part")) {
715            for part in parts.split_ascii_whitespace() {
716                callback(&GenericAtomIdent(Atom::from(part)));
717            }
718        }
719    }
720
721    fn each_exported_part<F>(&self, name: &AtomIdent, mut callback: F)
722    where
723        F: FnMut(&AtomIdent),
724    {
725        if let Some(parts) = self.attr(markup5ever::LocalName::from("exportparts")) {
726            for mapping in parts.split(',') {
727                let mut names = mapping.trim().split(':');
728                let Some(inner) = names.next().map(str::trim) else {
729                    continue;
730                };
731                let outer = names.next().map(str::trim).unwrap_or(inner);
732                if names.next().is_none() && inner == name.0.as_ref() && !outer.is_empty() {
733                    callback(&GenericAtomIdent(Atom::from(outer)));
734                }
735            }
736        }
737    }
738
739    fn id(&self) -> Option<&style::Atom> {
740        self.element_data().and_then(|data| data.id.as_ref())
741    }
742
743    fn each_class<F>(&self, mut callback: F)
744    where
745        F: FnMut(&style::values::AtomIdent),
746    {
747        let class_attr = self.data.attr(local_name!("class"));
748        if let Some(class_attr) = class_attr {
749            // split the class attribute
750            for pheme in class_attr.split_ascii_whitespace() {
751                let atom = Atom::from(pheme); // interns the string
752                callback(AtomIdent::cast(&atom));
753            }
754        }
755    }
756
757    fn each_attr_name<F>(&self, mut callback: F)
758    where
759        F: FnMut(&style::LocalName),
760    {
761        if let Some(attrs) = self.data.attrs() {
762            for attr in attrs.iter() {
763                callback(&GenericAtomIdent(attr.name.local.clone()));
764            }
765        }
766    }
767
768    fn has_dirty_descendants(&self) -> bool {
769        Node::has_dirty_descendants(self)
770    }
771
772    fn has_snapshot(&self) -> bool {
773        Node::has_snapshot(self)
774    }
775
776    fn handled_snapshot(&self) -> bool {
777        self.snapshot_handled().load(Ordering::SeqCst)
778    }
779
780    unsafe fn set_handled_snapshot(&self) {
781        self.snapshot_handled().store(true, Ordering::SeqCst);
782    }
783
784    unsafe fn set_dirty_descendants(&self) {
785        Node::set_dirty_descendants(self);
786        Node::mark_ancestors_dirty(self);
787    }
788
789    unsafe fn unset_dirty_descendants(&self) {
790        Node::unset_dirty_descendants(self);
791    }
792
793    fn store_children_to_process(&self, n: isize) {
794        self.children_to_process.store(n, Ordering::Relaxed);
795    }
796
797    fn did_process_child(&self) -> isize {
798        self.children_to_process.fetch_sub(1, Ordering::AcqRel) - 1
799    }
800
801    unsafe fn ensure_data(&self) -> ElementDataMut<'_> {
802        // SAFETY: stylo traversal has exclusive access to nodes
803        unsafe { self.stylo_element_data().ensure_init() }
804    }
805
806    /// Deliberately keeps the data.
807    ///
808    /// Stylo calls this from exactly one place: `clear_descendant_data`, which
809    /// runs when an element is restyled to `display: none` and throws away the
810    /// computed styles of everything beneath it. That is right for Gecko, where
811    /// a hidden subtree has no frames and the styles are dead weight.
812    ///
813    /// It is wrong for an application that retains its tabs. Hiding a pane
814    /// deleted every computed style under it, so revealing it again had no old
815    /// style to diff against, every node came back as fully damaged, and the
816    /// pane was reconstructed, re-shaped and laid out from nothing. Measured on
817    /// six retained panes of a real project tab, that made a *re-reveal* cost
818    /// exactly what the first reveal cost, 46,526 layout computations and 55ms,
819    /// on every switch forever.
820    ///
821    /// Keeping the styles is safe because nothing else consults them while the
822    /// subtree is hidden, and a mutation inside a hidden pane still marks its
823    /// nodes dirty the usual way, so the reveal restyles precisely what changed.
824    /// The memory is the styles of tabs the user is holding open, which is the
825    /// trade the application already made by retaining them.
826    ///
827    /// The cost is that `clear_descendant_data` still walks the subtree to find
828    /// nothing to clear, since it descends on `has_data`. One walk per hide.
829    unsafe fn clear_data(&self) {}
830
831    fn has_data(&self) -> bool {
832        self.stylo_element_data_opt().is_some_and(|s| s.has_data())
833    }
834
835    fn borrow_data(&self) -> Option<ElementDataRef<'_>> {
836        self.stylo_element_data_opt().and_then(|s| s.get())
837    }
838
839    fn mutate_data(&self) -> Option<ElementDataMut<'_>> {
840        unsafe { self.stylo_element_data().unsafe_stylo_only_mut() }
841    }
842
843    fn skip_item_display_fixup(&self) -> bool {
844        false
845    }
846
847    fn may_have_animations(&self) -> bool {
848        true
849    }
850
851    fn has_animations(&self, context: &SharedStyleContext) -> bool {
852        self.has_css_animations(context, None) || self.has_css_transitions(context, None)
853    }
854
855    fn has_css_animations(
856        &self,
857        context: &SharedStyleContext,
858        pseudo_element: Option<PseudoElement>,
859    ) -> bool {
860        let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
861        context.animations.has_active_animations(&key)
862    }
863
864    fn has_css_transitions(
865        &self,
866        context: &SharedStyleContext,
867        pseudo_element: Option<PseudoElement>,
868    ) -> bool {
869        let key = AnimationSetKey::new(TNode::opaque(&TElement::as_node(self)), pseudo_element);
870        context.animations.has_active_transitions(&key)
871    }
872
873    fn animation_rule(
874        &self,
875        context: &SharedStyleContext,
876    ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
877        let opaque = TNode::opaque(&TElement::as_node(self));
878        context.animations.get_animation_declarations(
879            &AnimationSetKey::new_for_non_pseudo(opaque),
880            context.current_time_for_animations,
881            self.guard(),
882        )
883    }
884
885    fn transition_rule(
886        &self,
887        context: &SharedStyleContext,
888    ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
889        let opaque = TNode::opaque(&TElement::as_node(self));
890        context.animations.get_transition_declarations(
891            &AnimationSetKey::new_for_non_pseudo(opaque),
892            context.current_time_for_animations,
893            self.guard(),
894        )
895    }
896
897    fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
898        self.shadow_root_id().map(|id| self.with(id))
899    }
900
901    fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
902        let mut node = *self;
903        while let Some(parent) = node.parent_node() {
904            if parent.is_shadow_root() {
905                return Some(parent);
906            }
907            node = parent;
908        }
909        None
910    }
911
912    fn get_attr(&self, attr: &style::LocalName, _ns: &style::Namespace) -> Option<String> {
913        // TODO: filter by namespace
914        // TODO: case-insensitive matching for HTML-ns attrs
915        self.attr(attr.0.clone()).map(|s| s.to_string())
916    }
917
918    fn lang_attr(&self) -> Option<style::selector_parser::AttrValue> {
919        None
920    }
921
922    fn match_element_lang(
923        &self,
924        _override_lang: Option<Option<style::selector_parser::AttrValue>>,
925        _value: &style::selector_parser::Lang,
926    ) -> bool {
927        false
928    }
929
930    fn is_html_document_body_element(&self) -> bool {
931        // Check node is a <body> element
932        let is_body_element = self.data.is_element_with_tag_name(&local_name!("body"));
933
934        // If it isn't then return early
935        if !is_body_element {
936            return false;
937        }
938
939        // If it is then check if it is a child of the root (<html>) element
940        let root_node = TNode::owner_doc(self);
941        let root_element = TDocument::as_node(&root_node)
942            .first_element_child()
943            .unwrap();
944        root_element.children.contains(&self.id)
945    }
946
947    fn synthesize_presentational_hints_for_legacy_attributes<V>(
948        &self,
949        _visited_handling: VisitedHandlingMode,
950        hints: &mut V,
951    ) where
952        V: Push<style::applicable_declarations::ApplicableDeclarationBlock>,
953    {
954        let Some(elem) = self.data.downcast_element() else {
955            return;
956        };
957
958        let tag = &elem.name.local;
959
960        let mut push_style = |decl: PropertyDeclaration| {
961            hints.push(ApplicableDeclarationBlock::from_declarations(
962                Arc::new(
963                    self.guard()
964                        .wrap(PropertyDeclarationBlock::with_one(decl, Importance::Normal)),
965                ),
966                CascadeLevel::new(CascadeOrigin::PresHints),
967                LayerOrder::root(),
968            ));
969        };
970
971        // SVG pointer-events is a presentation attribute. Putting it in the
972        // cascade lets CSS override it and descendants restore auto.
973        if elem.name.ns.as_ref() == "http://www.w3.org/2000/svg"
974            && let Some(value) = elem.attr(markup5ever::LocalName::from("pointer-events"))
975        {
976            use style::computed_values::pointer_events::T as PointerEvents;
977            let value = match value.trim() {
978                "none" => Some(PointerEvents::None),
979                "auto" => Some(PointerEvents::Auto),
980                _ => None,
981            };
982            if let Some(value) = value {
983                push_style(PropertyDeclaration::PointerEvents(value));
984            }
985        }
986
987        fn parse_color_attr(value: &str) -> Option<(u8, u8, u8, f32)> {
988            if !value.starts_with('#') {
989                return None;
990            }
991
992            let value = &value[1..];
993            if value.len() == 3 {
994                let r = u8::from_str_radix(&value[0..1], 16).ok()?;
995                let g = u8::from_str_radix(&value[1..2], 16).ok()?;
996                let b = u8::from_str_radix(&value[2..3], 16).ok()?;
997                return Some((r, g, b, 1.0));
998            }
999
1000            if value.len() == 6 {
1001                let r = u8::from_str_radix(&value[0..2], 16).ok()?;
1002                let g = u8::from_str_radix(&value[2..4], 16).ok()?;
1003                let b = u8::from_str_radix(&value[4..6], 16).ok()?;
1004                return Some((r, g, b, 1.0));
1005            }
1006
1007            None
1008        }
1009
1010        /// The HTML "rules for parsing dimension values" -- Stylo's
1011        /// implementation of them -- packaged as a specified
1012        /// `<length-percentage>`. `ignoring_zero` selects the spec's separate
1013        /// "maps to the dimension property (ignoring zero)" mapping, where a
1014        /// zero value is dropped rather than honoured.
1015        ///
1016        /// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-dimension-values
1017        fn parse_size_attr(
1018            value: &str,
1019            ignoring_zero: bool,
1020        ) -> Option<style::values::specified::LengthPercentage> {
1021            use style::servo::attr::{
1022                LengthOrPercentageOrAuto, parse_length, parse_nonzero_length,
1023            };
1024            use style::values::specified::{LengthPercentage, NoCalcLength};
1025            let parsed = if ignoring_zero {
1026                parse_nonzero_length(value)
1027            } else {
1028                parse_length(value)
1029            };
1030            match parsed {
1031                LengthOrPercentageOrAuto::Length(length) => Some(LengthPercentage::Length(
1032                    NoCalcLength::from_px(length.to_f32_px()),
1033                )),
1034                LengthOrPercentageOrAuto::Percentage(fraction) => Some(
1035                    LengthPercentage::Percentage(NoCalcPercentage::new(fraction)),
1036                ),
1037                LengthOrPercentageOrAuto::Auto => None,
1038            }
1039        }
1040
1041        /// Parse the value of an SVG `width`/`height` presentation attribute.
1042        /// Unlike the legacy HTML dimension attributes, these accept any CSS
1043        /// <length-percentage> (e.g. `1em`), and a unitless number means user
1044        /// units, which map to CSS px.
1045        fn parse_svg_size_attr(value: &str) -> Option<style::values::specified::LengthPercentage> {
1046            use style::values::specified::{LengthPercentage, NoCalcLength};
1047            use style_traits::ParsingMode;
1048
1049            let value = value.trim();
1050            if let Some(number) = value.strip_suffix('%') {
1051                let val: f32 = number.trim().parse().ok()?;
1052                return (val >= 0.0)
1053                    .then(|| LengthPercentage::Percentage(NoCalcPercentage::new(val / 100.0)));
1054            }
1055
1056            // Split into number and unit: the unit is the trailing run of
1057            // ASCII alphabetic characters (this never eats into a scientific
1058            // exponent such as `1e3`, which ends in a digit).
1059            let number_len = value
1060                .trim_end_matches(|c: char| c.is_ascii_alphabetic())
1061                .len();
1062            let (number, unit) = value.split_at(number_len);
1063            let val: f32 = number.trim().parse().ok().filter(|v| *v >= 0.0)?;
1064            let length = if unit.is_empty() {
1065                NoCalcLength::from_px(val)
1066            } else {
1067                NoCalcLength::parse_dimension_with_flags(ParsingMode::DEFAULT, false, val, unit)
1068                    .ok()?
1069            };
1070            Some(LengthPercentage::Length(length))
1071        }
1072
1073        // `<input type=image>` is the only input type that is replaced
1074        // content, and it is the only one that takes the embedded-content
1075        // presentational attributes. The type attribute is matched ASCII
1076        // case-insensitively, as attribute keywords always are.
1077        let is_image_input = *tag == local_name!("input")
1078            && elem.attrs().iter().any(|attr| {
1079                attr.name.local == local_name!("type")
1080                    && attr.value.as_ref().eq_ignore_ascii_case("image")
1081            });
1082
1083        for attr in elem.attrs() {
1084            let name = &attr.name.local;
1085            let value: &str = attr.value.as_ref();
1086
1087            if *name == local_name!("align") {
1088                use style::values::specified::TextAlign;
1089                let keyword = match value {
1090                    "left" => Some(StyloTextAlign::MozLeft),
1091                    "right" => Some(StyloTextAlign::MozRight),
1092                    "center" => Some(StyloTextAlign::MozCenter),
1093                    _ => None,
1094                };
1095
1096                if let Some(keyword) = keyword {
1097                    push_style(PropertyDeclaration::TextAlign(TextAlign::Keyword(keyword)));
1098                }
1099            }
1100
1101            // The width/height attributes on these elements map to the
1102            // corresponding dimension properties (percentages allowed):
1103            // https://html.spec.whatwg.org/multipage/rendering.html#dimRendering
1104            // https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images
1105            let is_width = *name == local_name!("width");
1106            let is_height = *name == local_name!("height");
1107            // The elements whose width/height attributes map to the dimension
1108            // properties. `input` only joins them as type=image, which is the
1109            // one replaced input type.
1110            let is_embedded = *tag == local_name!("iframe")
1111                || *tag == local_name!("embed")
1112                || *tag == local_name!("video")
1113                || *tag == local_name!("object")
1114                || *tag == local_name!("img")
1115                || *tag == local_name!("marquee")
1116                || is_image_input;
1117            let maps_to_dimension = if is_width {
1118                is_embedded
1119                    || *tag == local_name!("table")
1120                    || *tag == local_name!("col")
1121                    || *tag == local_name!("colgroup")
1122                    || *tag == local_name!("tr")
1123                    || *tag == local_name!("td")
1124                    || *tag == local_name!("th")
1125                    || *tag == local_name!("hr")
1126            } else if is_height {
1127                is_embedded
1128                    || *tag == local_name!("table")
1129                    || *tag == local_name!("thead")
1130                    || *tag == local_name!("tbody")
1131                    || *tag == local_name!("tfoot")
1132                    || *tag == local_name!("tr")
1133                    || *tag == local_name!("td")
1134                    || *tag == local_name!("th")
1135            } else {
1136                false
1137            };
1138            if maps_to_dimension {
1139                // Three of these use the "(ignoring zero)" variant of the
1140                // mapping, where a zero is dropped instead of honoured:
1141                // `table width`, and `td`/`th` in both axes.
1142                let is_cell = *tag == local_name!("td") || *tag == local_name!("th");
1143                let ignoring_zero = is_cell || (is_width && *tag == local_name!("table"));
1144                if let Some(size) = parse_size_attr(value, ignoring_zero) {
1145                    use style::values::generics::{NonNegative, length::Size};
1146                    let size = Size::LengthPercentage(NonNegative(size));
1147                    push_style(if is_width {
1148                        PropertyDeclaration::Width(size)
1149                    } else {
1150                        PropertyDeclaration::Height(size)
1151                    });
1152                }
1153            }
1154
1155            // hspace/vspace map to the horizontal and vertical margins as
1156            // dimension properties. This is a *smaller* set than the one that
1157            // takes width/height: `iframe` and `video` take width and height
1158            // but not hspace/vspace, and html/rendering/unmapped-attributes
1159            // checks exactly that -- browsers have got it wrong before.
1160            let takes_spacing = *tag == local_name!("embed")
1161                || *tag == local_name!("img")
1162                || *tag == local_name!("object")
1163                || *tag == local_name!("marquee")
1164                || is_image_input;
1165            if takes_spacing {
1166                let is_hspace = *name == local_name!("hspace");
1167                let is_vspace = *name == local_name!("vspace");
1168                if is_hspace || is_vspace {
1169                    if let Some(size) = parse_size_attr(value, false) {
1170                        use style::values::generics::length::GenericMargin;
1171                        let margin = GenericMargin::LengthPercentage(size);
1172                        if is_hspace {
1173                            push_style(PropertyDeclaration::MarginLeft(margin.clone()));
1174                            push_style(PropertyDeclaration::MarginRight(margin));
1175                        } else {
1176                            push_style(PropertyDeclaration::MarginTop(margin.clone()));
1177                            push_style(PropertyDeclaration::MarginBottom(margin));
1178                        }
1179                    }
1180                }
1181            }
1182
1183            // https://svgwg.org/svg2-draft/geometry.html#Sizing
1184            // The `width` and `height` attributes on an `<svg>` element are
1185            // presentation attributes that map to the CSS `width`/`height`
1186            // properties, so e.g. `width="1em"` must resolve against the
1187            // element's font-size like any other CSS length.
1188            if *tag == local_name!("svg")
1189                && (*name == local_name!("width") || *name == local_name!("height"))
1190            {
1191                if let Some(size) = parse_svg_size_attr(value) {
1192                    use style::values::generics::{NonNegative, length::Size};
1193                    let size = Size::LengthPercentage(NonNegative(size));
1194                    push_style(if *name == local_name!("width") {
1195                        PropertyDeclaration::Width(size)
1196                    } else {
1197                        PropertyDeclaration::Height(size)
1198                    });
1199                }
1200            }
1201
1202            // The `border` attribute maps to the four border widths as a
1203            // pixel length, plus the four border styles as `solid` -- width
1204            // alone would compute back to zero against the default
1205            // border-style of `none`. It is only these three elements:
1206            // `embed`, `iframe`, `marquee` and non-image `input` all have a
1207            // `border` attribute that must stay unmapped.
1208            if *name == local_name!("border")
1209                && (*tag == local_name!("img") || *tag == local_name!("object") || is_image_input)
1210            {
1211                if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1212                    use style::values::specified::{BorderSideWidth, BorderStyle};
1213                    let width = BorderSideWidth::from_px(px as f32);
1214                    push_style(PropertyDeclaration::BorderTopWidth(width.clone()));
1215                    push_style(PropertyDeclaration::BorderRightWidth(width.clone()));
1216                    push_style(PropertyDeclaration::BorderBottomWidth(width.clone()));
1217                    push_style(PropertyDeclaration::BorderLeftWidth(width));
1218                    push_style(PropertyDeclaration::BorderTopStyle(BorderStyle::Solid));
1219                    push_style(PropertyDeclaration::BorderRightStyle(BorderStyle::Solid));
1220                    push_style(PropertyDeclaration::BorderBottomStyle(BorderStyle::Solid));
1221                    push_style(PropertyDeclaration::BorderLeftStyle(BorderStyle::Solid));
1222                }
1223            }
1224
1225            // `body` carries four legacy margin attributes, as pixel lengths:
1226            // marginwidth and marginheight set both sides of an axis, and
1227            // leftmargin and topmargin set one side each.
1228            //
1229            // There is deliberately no `rightmargin` or `bottommargin`. They
1230            // look like the obvious counterparts to the two that exist, but
1231            // the spec does not define them and browsers ignore them in both
1232            // standards and quirks mode -- body-margin-3a/3b assert exactly
1233            // that.
1234            if *tag == local_name!("body") {
1235                // Matched as strings: these are not in the static atom set,
1236                // so `local_name!` will not compile for them.
1237                let sides: &[u8] = match &**name {
1238                    "marginwidth" => b"lr",
1239                    "marginheight" => b"tb",
1240                    "leftmargin" => b"l",
1241                    "topmargin" => b"t",
1242                    _ => b"",
1243                };
1244                if !sides.is_empty() {
1245                    if let Ok(px) = style::servo::attr::parse_unsigned_integer(value.chars()) {
1246                        use style::values::generics::length::GenericMargin;
1247                        use style::values::specified::{LengthPercentage, NoCalcLength};
1248                        let margin = GenericMargin::LengthPercentage(LengthPercentage::Length(
1249                            NoCalcLength::from_px(px as f32),
1250                        ));
1251                        for side in sides {
1252                            push_style(match side {
1253                                b'l' => PropertyDeclaration::MarginLeft(margin.clone()),
1254                                b'r' => PropertyDeclaration::MarginRight(margin.clone()),
1255                                b't' => PropertyDeclaration::MarginTop(margin.clone()),
1256                                b'b' => PropertyDeclaration::MarginBottom(margin.clone()),
1257                                _ => unreachable!("side table above only yields lrtb"),
1258                            });
1259                        }
1260                    }
1261                }
1262            }
1263
1264            if *name == local_name!("bgcolor") {
1265                use style::values::specified::Color;
1266                if let Some((r, g, b, a)) = parse_color_attr(value) {
1267                    push_style(PropertyDeclaration::BackgroundColor(
1268                        Color::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)),
1269                    ));
1270                }
1271            }
1272
1273            if *name == local_name!("hidden") {
1274                use style::values::specified::Display;
1275                push_style(PropertyDeclaration::Display(Display::None));
1276            }
1277        }
1278    }
1279
1280    fn local_name(&self) -> &LocalName {
1281        &self.element_data().expect("Not an element").name.local
1282    }
1283
1284    fn namespace(&self) -> &Namespace {
1285        &self.element_data().expect("Not an element").name.ns
1286    }
1287
1288    fn query_container_size(
1289        &self,
1290        _display: &style::values::specified::Display,
1291    ) -> euclid::default::Size2D<Option<app_units::Au>> {
1292        // FIXME: Implement container queries. For now this effectively disables them without panicking.
1293        Default::default()
1294    }
1295
1296    fn each_custom_state<F>(&self, _callback: F)
1297    where
1298        F: FnMut(&AtomIdent),
1299    {
1300    }
1301
1302    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
1303        self.selector_flags().get().contains(flags)
1304    }
1305
1306    fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
1307        let flags = self.selector_flags().get();
1308        if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
1309        {
1310            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING
1311        } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
1312        {
1313            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
1314        } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) {
1315            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
1316        } else {
1317            ElementSelectorFlags::empty()
1318        }
1319    }
1320
1321    fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
1322        compute_layout_damage(old, new)
1323        // ALL_DAMAGE
1324    }
1325
1326    // fn update_animations(
1327    //     &self,
1328    //     before_change_style: Option<Arc<ComputedValues>>,
1329    //     tasks: style::context::UpdateAnimationsTasks,
1330    // ) {
1331    //     todo!()
1332    // }
1333
1334    // fn process_post_animation(&self, tasks: style::context::PostAnimationTasks) {
1335    //     todo!()
1336    // }
1337
1338    // fn needs_transitions_update(
1339    //     &self,
1340    //     before_change_style: &ComputedValues,
1341    //     after_change_style: &ComputedValues,
1342    // ) -> bool {
1343    //     todo!()
1344    // }
1345}
1346
1347pub struct Traverser<'a> {
1348    // dom: &'a Slab<Node>,
1349    parent: BlitzNode<'a>,
1350    child_index: usize,
1351}
1352
1353impl<'a> Iterator for Traverser<'a> {
1354    type Item = BlitzNode<'a>;
1355
1356    fn next(&mut self) -> Option<Self::Item> {
1357        // Iterate the flattened-tree children so Stylo styles the composed tree
1358        // (shadow hosts expose their shadow root's children; <slot>s expose
1359        // their assigned light-DOM nodes).
1360        let node_id = self.parent.layout_dom_children().get(self.child_index)?;
1361        let node = self.parent.with(*node_id);
1362
1363        self.child_index += 1;
1364
1365        Some(node)
1366    }
1367}
1368
1369impl std::hash::Hash for BlitzNode<'_> {
1370    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1371        state.write_u64(self.id.as_u64())
1372    }
1373}
1374
1375/// Handle custom painters like images for layouting
1376///
1377/// todo: actually implement this
1378pub struct RegisteredPaintersImpl;
1379impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
1380    fn get(&self, _name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
1381        None
1382    }
1383}
1384
1385use style::traversal::recalc_style_at;
1386
1387pub struct RecalcStyle<'a> {
1388    context: SharedStyleContext<'a>,
1389}
1390
1391impl<'a> RecalcStyle<'a> {
1392    pub fn new(context: SharedStyleContext<'a>) -> Self {
1393        RecalcStyle { context }
1394    }
1395}
1396
1397#[allow(unsafe_code)]
1398impl<E> DomTraversal<E> for RecalcStyle<'_>
1399where
1400    E: TElement,
1401{
1402    fn process_preorder<F: FnMut(E::ConcreteNode)>(
1403        &self,
1404        traversal_data: &PerLevelTraversalData,
1405        context: &mut StyleContext<E>,
1406        node: E::ConcreteNode,
1407        note_child: F,
1408    ) {
1409        if let Some(el) = node.as_element() {
1410            // let mut data = el.mutate_data().unwrap();
1411            let mut data = unsafe { el.ensure_data() };
1412            recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
1413
1414            // Gets set later on
1415            unsafe { el.unset_dirty_descendants() }
1416        }
1417    }
1418
1419    #[inline]
1420    fn needs_postorder_traversal() -> bool {
1421        false
1422    }
1423
1424    fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
1425        panic!("this should never be called")
1426    }
1427
1428    #[inline]
1429    fn shared_context(&self) -> &SharedStyleContext<'_> {
1430        &self.context
1431    }
1432}
1433
1434#[test]
1435fn assert_size_of_equals() {
1436    // use std::mem;
1437
1438    // fn assert_layout<E>() {
1439    //     assert_eq!(
1440    //         mem::size_of::<SharingCache<E>>(),
1441    //         mem::size_of::<TypelessSharingCache>()
1442    //     );
1443    //     assert_eq!(
1444    //         mem::align_of::<SharingCache<E>>(),
1445    //         mem::align_of::<TypelessSharingCache>()
1446    //     );
1447    // }
1448
1449    // let size = mem::size_of::<StyleSharingCandidate<BlitzNode>>();
1450    // dbg!(size);
1451}
1452
1453#[test]
1454fn parse_inline() {
1455    // let attrs = style::attr::AttrValue::from_serialized_tokenlist(
1456    //     r#"visibility: hidden; left: 1306.5px; top: 50px; display: none;"#.to_string(),
1457    // );
1458
1459    // let val = CSSInlineStyleDeclaration();
1460}