Skip to main content

blitz_dom/
stylo.rs

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