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