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