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