blitz_dom/
stylo.rs

1//! Enable the dom to participate in styling by servo
2//!
3
4use std::ptr::NonNull;
5use std::sync::atomic::Ordering;
6
7use crate::layout::damage::ALL_DAMAGE;
8use crate::layout::damage::compute_layout_damage;
9use crate::node::Node;
10use crate::node::NodeData;
11use atomic_refcell::{AtomicRef, AtomicRefMut};
12use markup5ever::{LocalName, LocalNameStaticSet, Namespace, NamespaceStaticSet, local_name};
13use selectors::{
14    Element, OpaqueElement,
15    attr::{AttrSelectorOperation, AttrSelectorOperator, NamespaceConstraint},
16    matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode},
17    sink::Push,
18};
19use style::CaseSensitivityExt;
20use style::applicable_declarations::ApplicableDeclarationBlock;
21use style::color::AbsoluteColor;
22use style::properties::ComputedValues;
23use style::properties::{Importance, PropertyDeclaration};
24use style::rule_tree::CascadeLevel;
25use style::selector_parser::PseudoElement;
26use style::selector_parser::RestyleDamage;
27use style::stylesheets::layer_rule::LayerOrder;
28use style::stylesheets::scope_rule::ImplicitScopeRoot;
29use style::values::AtomString;
30use style::values::computed::Percentage;
31use style::{
32    Atom,
33    animation::DocumentAnimationSet,
34    context::{
35        QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters,
36        SharedStyleContext, StyleContext,
37    },
38    dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot},
39    global_style_data::GLOBAL_STYLE_DATA,
40    properties::PropertyDeclarationBlock,
41    selector_parser::{NonTSPseudoClass, SelectorImpl},
42    servo_arc::{Arc, ArcBorrow},
43    shared_lock::{Locked, SharedRwLock, StylesheetGuards},
44    thread_state::ThreadState,
45    traversal::{DomTraversal, PerLevelTraversalData},
46    traversal_flags::TraversalFlags,
47    values::{AtomIdent, GenericAtomIdent},
48};
49use style_dom::ElementState;
50
51use style::values::computed::text::TextAlign as StyloTextAlign;
52
53impl crate::document::BaseDocument {
54    pub fn resolve_stylist(&mut self) {
55        style::thread_state::enter(ThreadState::LAYOUT);
56
57        let guard = &self.guard;
58        let guards = StylesheetGuards {
59            author: &guard.read(),
60            ua_or_user: &guard.read(),
61        };
62
63        let root = TDocument::as_node(&&self.nodes[0])
64            .first_element_child()
65            .unwrap()
66            .as_element()
67            .unwrap();
68
69        self.stylist
70            .flush(&guards, Some(root), Some(&self.snapshots));
71
72        // Build the style context used by the style traversal
73        let context = SharedStyleContext {
74            traversal_flags: TraversalFlags::empty(),
75            stylist: &self.stylist,
76            options: GLOBAL_STYLE_DATA.options.clone(),
77            guards,
78            visited_styles_enabled: false,
79            animations: DocumentAnimationSet::default().clone(),
80            current_time_for_animations: 0.0,
81            snapshot_map: &self.snapshots,
82            registered_speculative_painters: &RegisteredPaintersImpl,
83        };
84
85        // components/layout_2020/lib.rs:983
86        let root = self.root_element();
87        // dbg!(root);
88        let token = RecalcStyle::pre_traverse(root, &context);
89
90        if token.should_traverse() {
91            // Style the elements, resolving their data
92            let traverser = RecalcStyle::new(context);
93            style::driver::traverse_dom(&traverser, token, None);
94        }
95
96        for opaque in self.snapshots.keys() {
97            let id = opaque.id();
98            if let Some(node) = self.nodes.get_mut(id) {
99                node.has_snapshot = false;
100            }
101        }
102        self.snapshots.clear();
103
104        style::thread_state::exit(ThreadState::LAYOUT);
105    }
106}
107
108/// A handle to a node that Servo's style traits are implemented against
109///
110/// Since BlitzNodes are not persistent (IE we don't keep the pointers around between frames), we choose to just implement
111/// the tree structure in the nodes themselves, and temporarily give out pointers during the layout phase.
112type BlitzNode<'a> = &'a Node;
113
114impl<'a> TDocument for BlitzNode<'a> {
115    type ConcreteNode = BlitzNode<'a>;
116
117    fn as_node(&self) -> Self::ConcreteNode {
118        self
119    }
120
121    fn is_html_document(&self) -> bool {
122        true
123    }
124
125    fn quirks_mode(&self) -> QuirksMode {
126        QuirksMode::NoQuirks
127    }
128
129    fn shared_lock(&self) -> &SharedRwLock {
130        &self.guard
131    }
132}
133
134impl NodeInfo for BlitzNode<'_> {
135    fn is_element(&self) -> bool {
136        Node::is_element(self)
137    }
138
139    fn is_text_node(&self) -> bool {
140        Node::is_text_node(self)
141    }
142}
143
144impl<'a> TShadowRoot for BlitzNode<'a> {
145    type ConcreteNode = BlitzNode<'a>;
146
147    fn as_node(&self) -> Self::ConcreteNode {
148        self
149    }
150
151    fn host(&self) -> <Self::ConcreteNode as TNode>::ConcreteElement {
152        todo!("Shadow roots not implemented")
153    }
154
155    fn style_data<'b>(&self) -> Option<&'b style::stylist::CascadeData>
156    where
157        Self: 'b,
158    {
159        todo!("Shadow roots not implemented")
160    }
161}
162
163// components/styleaapper.rs:
164impl<'a> TNode for BlitzNode<'a> {
165    type ConcreteElement = BlitzNode<'a>;
166    type ConcreteDocument = BlitzNode<'a>;
167    type ConcreteShadowRoot = BlitzNode<'a>;
168
169    fn parent_node(&self) -> Option<Self> {
170        self.parent.map(|id| self.with(id))
171    }
172
173    fn first_child(&self) -> Option<Self> {
174        self.children.first().map(|id| self.with(*id))
175    }
176
177    fn last_child(&self) -> Option<Self> {
178        self.children.last().map(|id| self.with(*id))
179    }
180
181    fn prev_sibling(&self) -> Option<Self> {
182        self.backward(1)
183    }
184
185    fn next_sibling(&self) -> Option<Self> {
186        self.forward(1)
187    }
188
189    fn owner_doc(&self) -> Self::ConcreteDocument {
190        self.with(1)
191    }
192
193    fn is_in_document(&self) -> bool {
194        true
195    }
196
197    // I think this is the same as parent_node only in the cases when the direct parent is not a real element, forcing us
198    // to travel upwards
199    //
200    // For the sake of this demo, we're just going to return the parent node ann
201    fn traversal_parent(&self) -> Option<Self::ConcreteElement> {
202        self.parent_node().and_then(|node| node.as_element())
203    }
204
205    fn opaque(&self) -> OpaqueNode {
206        OpaqueNode(self.id)
207    }
208
209    fn debug_id(self) -> usize {
210        self.id
211    }
212
213    fn as_element(&self) -> Option<Self::ConcreteElement> {
214        match self.data {
215            NodeData::Element { .. } => Some(self),
216            _ => None,
217        }
218    }
219
220    fn as_document(&self) -> Option<Self::ConcreteDocument> {
221        match self.data {
222            NodeData::Document => Some(self),
223            _ => None,
224        }
225    }
226
227    fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> {
228        // TODO: implement shadow DOM
229        None
230    }
231}
232
233impl selectors::Element for BlitzNode<'_> {
234    type Impl = SelectorImpl;
235
236    fn opaque(&self) -> selectors::OpaqueElement {
237        // FIXME: this is wrong in the case where pushing new elements casuses reallocations.
238        // We should see if selectors will accept a PR that allows creation from a usize
239        let non_null = NonNull::new((self.id + 1) as *mut ()).unwrap();
240        OpaqueElement::from_non_null_ptr(non_null)
241    }
242
243    fn parent_element(&self) -> Option<Self> {
244        TElement::traversal_parent(self)
245    }
246
247    fn parent_node_is_shadow_root(&self) -> bool {
248        false
249    }
250
251    fn containing_shadow_host(&self) -> Option<Self> {
252        None
253    }
254
255    fn is_pseudo_element(&self) -> bool {
256        matches!(self.data, NodeData::AnonymousBlock(_))
257    }
258
259    // These methods are implemented naively since we only threaded real nodes and not fake nodes
260    // we should try and use `find` instead of this foward/backward stuff since its ugly and slow
261    fn prev_sibling_element(&self) -> Option<Self> {
262        let mut n = 1;
263        while let Some(node) = self.backward(n) {
264            if node.is_element() {
265                return Some(node);
266            }
267            n += 1;
268        }
269
270        None
271    }
272
273    fn next_sibling_element(&self) -> Option<Self> {
274        let mut n = 1;
275        while let Some(node) = self.forward(n) {
276            if node.is_element() {
277                return Some(node);
278            }
279            n += 1;
280        }
281
282        None
283    }
284
285    fn first_element_child(&self) -> Option<Self> {
286        let mut children = self.dom_children();
287        children.find(|child| child.is_element())
288    }
289
290    fn is_html_element_in_html_document(&self) -> bool {
291        true // self.has_namespace(ns!(html))
292    }
293
294    fn has_local_name(&self, local_name: &LocalName) -> bool {
295        self.data.is_element_with_tag_name(local_name)
296    }
297
298    fn has_namespace(&self, ns: &Namespace) -> bool {
299        self.element_data().expect("Not an element").name.ns == *ns
300    }
301
302    fn is_same_type(&self, other: &Self) -> bool {
303        self.local_name() == other.local_name() && self.namespace() == other.namespace()
304    }
305
306    fn attr_matches(
307        &self,
308        _ns: &NamespaceConstraint<&GenericAtomIdent<NamespaceStaticSet>>,
309        local_name: &GenericAtomIdent<LocalNameStaticSet>,
310        operation: &AttrSelectorOperation<&AtomString>,
311    ) -> bool {
312        let Some(attr_value) = self.data.attr(local_name.0.clone()) else {
313            return false;
314        };
315
316        match operation {
317            AttrSelectorOperation::Exists => true,
318            AttrSelectorOperation::WithValue {
319                operator,
320                case_sensitivity: _,
321                value,
322            } => {
323                let value = value.as_ref();
324
325                // TODO: case sensitivity
326                match operator {
327                    AttrSelectorOperator::Equal => attr_value == value,
328                    AttrSelectorOperator::Includes => attr_value
329                        .split_ascii_whitespace()
330                        .any(|word| word == value),
331                    AttrSelectorOperator::DashMatch => {
332                        // Represents elements with an attribute name of attr whose value can be exactly value
333                        // or can begin with value immediately followed by a hyphen, - (U+002D)
334                        attr_value.starts_with(value)
335                            && (attr_value.len() == value.len()
336                                || attr_value.chars().nth(value.len()) == Some('-'))
337                    }
338                    AttrSelectorOperator::Prefix => attr_value.starts_with(value),
339                    AttrSelectorOperator::Substring => attr_value.contains(value),
340                    AttrSelectorOperator::Suffix => attr_value.ends_with(value),
341                }
342            }
343        }
344    }
345
346    fn match_non_ts_pseudo_class(
347        &self,
348        pseudo_class: &<Self::Impl as selectors::SelectorImpl>::NonTSPseudoClass,
349        _context: &mut MatchingContext<Self::Impl>,
350    ) -> bool {
351        match *pseudo_class {
352            NonTSPseudoClass::Active => self.element_state.contains(ElementState::ACTIVE),
353            NonTSPseudoClass::AnyLink => self
354                .data
355                .downcast_element()
356                .map(|elem| {
357                    (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
358                        && elem.attr(local_name!("href")).is_some()
359                })
360                .unwrap_or(false),
361            NonTSPseudoClass::Checked => self
362                .data
363                .downcast_element()
364                .and_then(|elem| elem.checkbox_input_checked())
365                .unwrap_or(false),
366            NonTSPseudoClass::Valid => false,
367            NonTSPseudoClass::Invalid => false,
368            NonTSPseudoClass::Defined => false,
369            NonTSPseudoClass::Disabled => false,
370            NonTSPseudoClass::Enabled => false,
371            NonTSPseudoClass::Focus => self.element_state.contains(ElementState::FOCUS),
372            NonTSPseudoClass::FocusWithin => false,
373            NonTSPseudoClass::FocusVisible => false,
374            NonTSPseudoClass::Fullscreen => false,
375            NonTSPseudoClass::Hover => self.element_state.contains(ElementState::HOVER),
376            NonTSPseudoClass::Indeterminate => false,
377            NonTSPseudoClass::Lang(_) => false,
378            NonTSPseudoClass::CustomState(_) => false,
379            NonTSPseudoClass::Link => self
380                .data
381                .downcast_element()
382                .map(|elem| {
383                    (elem.name.local == local_name!("a") || elem.name.local == local_name!("area"))
384                        && elem.attr(local_name!("href")).is_some()
385                })
386                .unwrap_or(false),
387            NonTSPseudoClass::PlaceholderShown => false,
388            NonTSPseudoClass::ReadWrite => false,
389            NonTSPseudoClass::ReadOnly => false,
390            NonTSPseudoClass::ServoNonZeroBorder => false,
391            NonTSPseudoClass::Target => false,
392            NonTSPseudoClass::Visited => false,
393            NonTSPseudoClass::Autofill => false,
394            NonTSPseudoClass::Default => false,
395
396            NonTSPseudoClass::InRange => false,
397            NonTSPseudoClass::Modal => false,
398            NonTSPseudoClass::Optional => false,
399            NonTSPseudoClass::OutOfRange => false,
400            NonTSPseudoClass::PopoverOpen => false,
401            NonTSPseudoClass::Required => false,
402            NonTSPseudoClass::UserInvalid => false,
403            NonTSPseudoClass::UserValid => false,
404            NonTSPseudoClass::MozMeterOptimum => false,
405            NonTSPseudoClass::MozMeterSubOptimum => false,
406            NonTSPseudoClass::MozMeterSubSubOptimum => false,
407        }
408    }
409
410    fn match_pseudo_element(
411        &self,
412        pe: &PseudoElement,
413        _context: &mut MatchingContext<Self::Impl>,
414    ) -> bool {
415        match self.data {
416            NodeData::AnonymousBlock(_) => *pe == PseudoElement::ServoAnonymousBox,
417            _ => false,
418        }
419    }
420
421    fn apply_selector_flags(&self, flags: ElementSelectorFlags) {
422        // Handle flags that apply to the element.
423        let self_flags = flags.for_self();
424        if !self_flags.is_empty() {
425            *self.selector_flags.borrow_mut() |= self_flags;
426        }
427
428        // Handle flags that apply to the parent.
429        let parent_flags = flags.for_parent();
430        if !parent_flags.is_empty() {
431            if let Some(parent) = self.parent_node() {
432                *parent.selector_flags.borrow_mut() |= parent_flags;
433            }
434        }
435    }
436
437    fn is_link(&self) -> bool {
438        self.data.is_element_with_tag_name(&local_name!("a"))
439    }
440
441    fn is_html_slot_element(&self) -> bool {
442        false
443    }
444
445    fn has_id(
446        &self,
447        id: &<Self::Impl as selectors::SelectorImpl>::Identifier,
448        case_sensitivity: selectors::attr::CaseSensitivity,
449    ) -> bool {
450        self.element_data()
451            .and_then(|data| data.id.as_ref())
452            .map(|id_attr| case_sensitivity.eq_atom(id_attr, id))
453            .unwrap_or(false)
454    }
455
456    fn has_class(
457        &self,
458        search_name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
459        case_sensitivity: selectors::attr::CaseSensitivity,
460    ) -> bool {
461        let class_attr = self.data.attr(local_name!("class"));
462        if let Some(class_attr) = class_attr {
463            // split the class attribute
464            for pheme in class_attr.split_ascii_whitespace() {
465                let atom = Atom::from(pheme);
466                if case_sensitivity.eq_atom(&atom, search_name) {
467                    return true;
468                }
469            }
470        }
471
472        false
473    }
474
475    fn imported_part(
476        &self,
477        _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
478    ) -> Option<<Self::Impl as selectors::SelectorImpl>::Identifier> {
479        None
480    }
481
482    fn is_part(&self, _name: &<Self::Impl as selectors::SelectorImpl>::Identifier) -> bool {
483        false
484    }
485
486    fn is_empty(&self) -> bool {
487        self.dom_children().next().is_none()
488    }
489
490    fn is_root(&self) -> bool {
491        self.parent_node()
492            .and_then(|parent| parent.parent_node())
493            .is_none()
494    }
495
496    fn has_custom_state(
497        &self,
498        _name: &<Self::Impl as selectors::SelectorImpl>::Identifier,
499    ) -> bool {
500        false
501    }
502
503    fn add_element_unique_hashes(&self, _filter: &mut selectors::bloom::BloomFilter) -> bool {
504        false
505    }
506}
507
508impl<'a> TElement for BlitzNode<'a> {
509    type ConcreteNode = BlitzNode<'a>;
510
511    type TraversalChildrenIterator = Traverser<'a>;
512
513    fn as_node(&self) -> Self::ConcreteNode {
514        self
515    }
516
517    fn implicit_scope_for_sheet_in_shadow_root(
518        _opaque_host: OpaqueElement,
519        _sheet_index: usize,
520    ) -> Option<ImplicitScopeRoot> {
521        // We cannot currently implement this as we are using the NodeId as the OpaqueElement,
522        // and need a reference to the Slab to convert it back into an Element
523        //
524        // Luckily it is only needed for shadow dom.
525        todo!();
526    }
527
528    fn traversal_children(&self) -> style::dom::LayoutIterator<Self::TraversalChildrenIterator> {
529        LayoutIterator(Traverser {
530            // dom: self.tree(),
531            parent: self,
532            child_index: 0,
533        })
534    }
535
536    fn is_html_element(&self) -> bool {
537        self.is_element()
538    }
539
540    // not implemented.....
541    fn is_mathml_element(&self) -> bool {
542        false
543    }
544
545    // need to check the namespace
546    fn is_svg_element(&self) -> bool {
547        false
548    }
549
550    fn style_attribute(&self) -> Option<ArcBorrow<'_, Locked<PropertyDeclarationBlock>>> {
551        self.element_data()
552            .expect("Not an element")
553            .style_attribute
554            .as_ref()
555            .map(|f| f.borrow_arc())
556    }
557
558    fn animation_rule(
559        &self,
560        _: &SharedStyleContext,
561    ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
562        None
563    }
564
565    fn transition_rule(
566        &self,
567        _context: &SharedStyleContext,
568    ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> {
569        None
570    }
571
572    fn state(&self) -> ElementState {
573        self.element_state
574    }
575
576    fn has_part_attr(&self) -> bool {
577        false
578    }
579
580    fn exports_any_part(&self) -> bool {
581        false
582    }
583
584    fn id(&self) -> Option<&style::Atom> {
585        self.element_data().and_then(|data| data.id.as_ref())
586    }
587
588    fn each_class<F>(&self, mut callback: F)
589    where
590        F: FnMut(&style::values::AtomIdent),
591    {
592        let class_attr = self.data.attr(local_name!("class"));
593        if let Some(class_attr) = class_attr {
594            // split the class attribute
595            for pheme in class_attr.split_ascii_whitespace() {
596                let atom = Atom::from(pheme); // interns the string
597                callback(AtomIdent::cast(&atom));
598            }
599        }
600    }
601
602    fn each_attr_name<F>(&self, mut callback: F)
603    where
604        F: FnMut(&style::LocalName),
605    {
606        if let Some(attrs) = self.data.attrs() {
607            for attr in attrs.iter() {
608                callback(&GenericAtomIdent(attr.name.local.clone()));
609            }
610        }
611    }
612
613    fn has_dirty_descendants(&self) -> bool {
614        true
615    }
616
617    fn has_snapshot(&self) -> bool {
618        self.has_snapshot
619    }
620
621    fn handled_snapshot(&self) -> bool {
622        self.snapshot_handled.load(Ordering::SeqCst)
623    }
624
625    unsafe fn set_handled_snapshot(&self) {
626        self.snapshot_handled.store(true, Ordering::SeqCst);
627    }
628
629    unsafe fn set_dirty_descendants(&self) {}
630
631    unsafe fn unset_dirty_descendants(&self) {}
632
633    fn store_children_to_process(&self, _n: isize) {
634        unimplemented!()
635    }
636
637    fn did_process_child(&self) -> isize {
638        unimplemented!()
639    }
640
641    unsafe fn ensure_data(&self) -> AtomicRefMut<'_, style::data::ElementData> {
642        let mut stylo_data = self.stylo_element_data.borrow_mut();
643        if stylo_data.is_none() {
644            *stylo_data = Some(style::data::ElementData {
645                damage: ALL_DAMAGE,
646                ..Default::default()
647            });
648        }
649        AtomicRefMut::map(stylo_data, |sd| sd.as_mut().unwrap())
650    }
651
652    unsafe fn clear_data(&self) {
653        *self.stylo_element_data.borrow_mut() = None;
654    }
655
656    fn has_data(&self) -> bool {
657        self.stylo_element_data.borrow().is_some()
658    }
659
660    fn borrow_data(&self) -> Option<AtomicRef<'_, style::data::ElementData>> {
661        let stylo_data = self.stylo_element_data.borrow();
662        if stylo_data.is_some() {
663            Some(AtomicRef::map(stylo_data, |sd| sd.as_ref().unwrap()))
664        } else {
665            None
666        }
667    }
668
669    fn mutate_data(&self) -> Option<AtomicRefMut<'_, style::data::ElementData>> {
670        let stylo_data = self.stylo_element_data.borrow_mut();
671        if stylo_data.is_some() {
672            Some(AtomicRefMut::map(stylo_data, |sd| sd.as_mut().unwrap()))
673        } else {
674            None
675        }
676    }
677
678    fn skip_item_display_fixup(&self) -> bool {
679        false
680    }
681
682    fn may_have_animations(&self) -> bool {
683        false
684    }
685
686    fn has_animations(&self, _context: &SharedStyleContext) -> bool {
687        false
688    }
689
690    fn has_css_animations(
691        &self,
692        _context: &SharedStyleContext,
693        _pseudo_element: Option<style::selector_parser::PseudoElement>,
694    ) -> bool {
695        false
696    }
697
698    fn has_css_transitions(
699        &self,
700        _context: &SharedStyleContext,
701        _pseudo_element: Option<style::selector_parser::PseudoElement>,
702    ) -> bool {
703        false
704    }
705
706    fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
707        None
708    }
709
710    fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot> {
711        None
712    }
713
714    fn lang_attr(&self) -> Option<style::selector_parser::AttrValue> {
715        None
716    }
717
718    fn match_element_lang(
719        &self,
720        _override_lang: Option<Option<style::selector_parser::AttrValue>>,
721        _value: &style::selector_parser::Lang,
722    ) -> bool {
723        false
724    }
725
726    fn is_html_document_body_element(&self) -> bool {
727        // Check node is a <body> element
728        let is_body_element = self.data.is_element_with_tag_name(&local_name!("body"));
729
730        // If it isn't then return early
731        if !is_body_element {
732            return false;
733        }
734
735        // If it is then check if it is a child of the root (<html>) element
736        let root_node = &self.tree()[0];
737        let root_element = TDocument::as_node(&root_node)
738            .first_element_child()
739            .unwrap();
740        root_element.children.contains(&self.id)
741    }
742
743    fn synthesize_presentational_hints_for_legacy_attributes<V>(
744        &self,
745        _visited_handling: VisitedHandlingMode,
746        hints: &mut V,
747    ) where
748        V: Push<style::applicable_declarations::ApplicableDeclarationBlock>,
749    {
750        let Some(elem) = self.data.downcast_element() else {
751            return;
752        };
753
754        let tag = &elem.name.local;
755
756        let mut push_style = |decl: PropertyDeclaration| {
757            hints.push(ApplicableDeclarationBlock::from_declarations(
758                Arc::new(
759                    self.guard
760                        .wrap(PropertyDeclarationBlock::with_one(decl, Importance::Normal)),
761                ),
762                CascadeLevel::PresHints,
763                LayerOrder::root(),
764            ));
765        };
766
767        fn parse_color_attr(value: &str) -> Option<(u8, u8, u8, f32)> {
768            if !value.starts_with('#') {
769                return None;
770            }
771
772            let value = &value[1..];
773            if value.len() == 3 {
774                let r = u8::from_str_radix(&value[0..1], 16).ok()?;
775                let g = u8::from_str_radix(&value[1..2], 16).ok()?;
776                let b = u8::from_str_radix(&value[2..3], 16).ok()?;
777                return Some((r, g, b, 1.0));
778            }
779
780            if value.len() == 6 {
781                let r = u8::from_str_radix(&value[0..2], 16).ok()?;
782                let g = u8::from_str_radix(&value[2..4], 16).ok()?;
783                let b = u8::from_str_radix(&value[4..6], 16).ok()?;
784                return Some((r, g, b, 1.0));
785            }
786
787            None
788        }
789
790        fn parse_size_attr(
791            value: &str,
792            filter_fn: impl FnOnce(&f32) -> bool,
793        ) -> Option<style::values::specified::LengthPercentage> {
794            use style::values::specified::{AbsoluteLength, LengthPercentage, NoCalcLength};
795            if let Some(value) = value.strip_suffix("px") {
796                let val: f32 = value.parse().ok()?;
797                return Some(LengthPercentage::Length(NoCalcLength::Absolute(
798                    AbsoluteLength::Px(val),
799                )));
800            }
801
802            if let Some(value) = value.strip_suffix("%") {
803                let val: f32 = value.parse().ok()?;
804                return Some(LengthPercentage::Percentage(Percentage(val / 100.0)));
805            }
806
807            let val: f32 = value.parse().ok().filter(filter_fn)?;
808            Some(LengthPercentage::Length(NoCalcLength::Absolute(
809                AbsoluteLength::Px(val),
810            )))
811        }
812
813        for attr in elem.attrs() {
814            let name = &attr.name.local;
815            let value = attr.value.as_str();
816
817            if *name == local_name!("align") {
818                use style::values::specified::TextAlign;
819                let keyword = match value {
820                    "left" => Some(StyloTextAlign::MozLeft),
821                    "right" => Some(StyloTextAlign::MozRight),
822                    "center" => Some(StyloTextAlign::MozCenter),
823                    _ => None,
824                };
825
826                if let Some(keyword) = keyword {
827                    push_style(PropertyDeclaration::TextAlign(TextAlign::Keyword(keyword)));
828                }
829            }
830
831            // https://html.spec.whatwg.org/multipage/rendering.html#dimRendering
832            if *name == local_name!("width")
833                && (*tag == local_name!("table")
834                    || *tag == local_name!("col")
835                    || *tag == local_name!("tr")
836                    || *tag == local_name!("td")
837                    || *tag == local_name!("th")
838                    || *tag == local_name!("hr"))
839            {
840                let is_table = *tag == local_name!("table");
841                if let Some(width) = parse_size_attr(value, |v| !is_table || *v != 0.0) {
842                    use style::values::generics::{NonNegative, length::Size};
843
844                    push_style(PropertyDeclaration::Width(Size::LengthPercentage(
845                        NonNegative(width),
846                    )));
847                }
848            }
849
850            if *name == local_name!("height")
851                && (*tag == local_name!("table")
852                    || *tag == local_name!("thead")
853                    || *tag == local_name!("tbody")
854                    || *tag == local_name!("tfoot"))
855            {
856                if let Some(height) = parse_size_attr(value, |_| true) {
857                    use style::values::generics::{NonNegative, length::Size};
858                    push_style(PropertyDeclaration::Height(Size::LengthPercentage(
859                        NonNegative(height),
860                    )));
861                }
862            }
863
864            if *name == local_name!("bgcolor") {
865                use style::values::specified::Color;
866                if let Some((r, g, b, a)) = parse_color_attr(value) {
867                    push_style(PropertyDeclaration::BackgroundColor(
868                        Color::from_absolute_color(AbsoluteColor::srgb_legacy(r, g, b, a)),
869                    ));
870                }
871            }
872
873            if *name == local_name!("hidden") {
874                use style::values::specified::Display;
875                push_style(PropertyDeclaration::Display(Display::None));
876            }
877        }
878    }
879
880    fn local_name(&self) -> &LocalName {
881        &self.element_data().expect("Not an element").name.local
882    }
883
884    fn namespace(&self) -> &Namespace {
885        &self.element_data().expect("Not an element").name.ns
886    }
887
888    fn query_container_size(
889        &self,
890        _display: &style::values::specified::Display,
891    ) -> euclid::default::Size2D<Option<app_units::Au>> {
892        // FIXME: Implement container queries. For now this effectively disables them without panicking.
893        Default::default()
894    }
895
896    fn each_custom_state<F>(&self, _callback: F)
897    where
898        F: FnMut(&AtomIdent),
899    {
900        todo!()
901    }
902
903    fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
904        self.selector_flags.borrow().contains(flags)
905    }
906
907    fn relative_selector_search_direction(&self) -> ElementSelectorFlags {
908        let flags = self.selector_flags.borrow();
909        if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING)
910        {
911            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR_SIBLING
912        } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR)
913        {
914            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_ANCESTOR
915        } else if flags.contains(ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING) {
916            ElementSelectorFlags::RELATIVE_SELECTOR_SEARCH_DIRECTION_SIBLING
917        } else {
918            ElementSelectorFlags::empty()
919        }
920    }
921
922    fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
923        compute_layout_damage(old, new)
924        // ALL_DAMAGE
925    }
926
927    // fn update_animations(
928    //     &self,
929    //     before_change_style: Option<Arc<ComputedValues>>,
930    //     tasks: style::context::UpdateAnimationsTasks,
931    // ) {
932    //     todo!()
933    // }
934
935    // fn process_post_animation(&self, tasks: style::context::PostAnimationTasks) {
936    //     todo!()
937    // }
938
939    // fn needs_transitions_update(
940    //     &self,
941    //     before_change_style: &ComputedValues,
942    //     after_change_style: &ComputedValues,
943    // ) -> bool {
944    //     todo!()
945    // }
946}
947
948pub struct Traverser<'a> {
949    // dom: &'a Slab<Node>,
950    parent: BlitzNode<'a>,
951    child_index: usize,
952}
953
954impl<'a> Iterator for Traverser<'a> {
955    type Item = BlitzNode<'a>;
956
957    fn next(&mut self) -> Option<Self::Item> {
958        let node_id = self.parent.children.get(self.child_index)?;
959        let node = self.parent.with(*node_id);
960
961        self.child_index += 1;
962
963        Some(node)
964    }
965}
966
967impl std::hash::Hash for BlitzNode<'_> {
968    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
969        state.write_usize(self.id)
970    }
971}
972
973/// Handle custom painters like images for layouting
974///
975/// todo: actually implement this
976pub struct RegisteredPaintersImpl;
977impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
978    fn get(&self, _name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
979        None
980    }
981}
982
983use style::traversal::recalc_style_at;
984
985pub struct RecalcStyle<'a> {
986    context: SharedStyleContext<'a>,
987}
988
989impl<'a> RecalcStyle<'a> {
990    pub fn new(context: SharedStyleContext<'a>) -> Self {
991        RecalcStyle { context }
992    }
993}
994
995#[allow(unsafe_code)]
996impl<E> DomTraversal<E> for RecalcStyle<'_>
997where
998    E: TElement,
999{
1000    fn process_preorder<F: FnMut(E::ConcreteNode)>(
1001        &self,
1002        traversal_data: &PerLevelTraversalData,
1003        context: &mut StyleContext<E>,
1004        node: E::ConcreteNode,
1005        note_child: F,
1006    ) {
1007        // Don't process textnodees in this traversal
1008        if node.is_text_node() {
1009            return;
1010        }
1011
1012        let el = node.as_element().unwrap();
1013        // let mut data = el.mutate_data().unwrap();
1014        let mut data = unsafe { el.ensure_data() };
1015        recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
1016
1017        // Gets set later on
1018        unsafe { el.unset_dirty_descendants() }
1019    }
1020
1021    #[inline]
1022    fn needs_postorder_traversal() -> bool {
1023        false
1024    }
1025
1026    fn process_postorder(&self, _style_context: &mut StyleContext<E>, _node: E::ConcreteNode) {
1027        panic!("this should never be called")
1028    }
1029
1030    #[inline]
1031    fn shared_context(&self) -> &SharedStyleContext<'_> {
1032        &self.context
1033    }
1034}
1035
1036#[test]
1037fn assert_size_of_equals() {
1038    // use std::mem;
1039
1040    // fn assert_layout<E>() {
1041    //     assert_eq!(
1042    //         mem::size_of::<SharingCache<E>>(),
1043    //         mem::size_of::<TypelessSharingCache>()
1044    //     );
1045    //     assert_eq!(
1046    //         mem::align_of::<SharingCache<E>>(),
1047    //         mem::align_of::<TypelessSharingCache>()
1048    //     );
1049    // }
1050
1051    // let size = mem::size_of::<StyleSharingCandidate<BlitzNode>>();
1052    // dbg!(size);
1053}
1054
1055#[test]
1056fn parse_inline() {
1057    // let attrs = style::attr::AttrValue::from_serialized_tokenlist(
1058    //     r#"visibility: hidden; left: 1306.5px; top: 50px; display: none;"#.to_string(),
1059    // );
1060
1061    // let val = CSSInlineStyleDeclaration();
1062}