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