Skip to main content

apace_dom/
stylo.rs

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