Skip to main content

blitz_dom/
stylo.rs

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