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