Skip to main content

ps_blitz_dom/layout/
damage.rs

1use std::ops::Range;
2
3use crate::Node;
4use crate::net::ResourceHandler;
5use crate::node::NodeFlags;
6use crate::{
7    BaseDocument, net::ImageHandler, node::ImageResourceData, node::Status, util::ImageLayerKind,
8};
9use style::properties::ComputedValues;
10use style::properties::generated::longhands::position::computed_value::T as Position;
11use style::selector_parser::RestyleDamage;
12use style::url::ComputedUrl;
13use style::values::computed::Float;
14use style::values::generics::image::Image as StyloImage;
15use style::values::specified::align::AlignFlags;
16use style::values::specified::box_::DisplayInside;
17use style::values::specified::box_::DisplayOutside;
18use taffy::Rect;
19
20pub(crate) const CONSTRUCT_BOX: RestyleDamage =
21    RestyleDamage::from_bits_retain(0b_0000_0000_0001_0000);
22pub(crate) const CONSTRUCT_FC: RestyleDamage =
23    RestyleDamage::from_bits_retain(0b_0000_0000_0010_0000);
24pub(crate) const CONSTRUCT_DESCENDENT: RestyleDamage =
25    RestyleDamage::from_bits_retain(0b_0000_0000_0100_0000);
26
27pub(crate) const ONLY_RELAYOUT: RestyleDamage =
28    RestyleDamage::from_bits_retain(0b_0000_0000_0000_1000);
29
30pub(crate) const ALL_DAMAGE: RestyleDamage =
31    RestyleDamage::from_bits_retain(0b_0000_0000_0111_1111);
32
33impl BaseDocument {
34    pub(crate) fn propagate_damage_flags(
35        &mut self,
36        node_id: usize,
37        damage_from_parent: RestyleDamage,
38    ) -> RestyleDamage {
39        let mut damage = if let Some(data) = self.nodes[node_id].stylo_element_data.get_mut() {
40            data.damage
41        } else {
42            return RestyleDamage::empty();
43        };
44        damage |= damage_from_parent;
45
46        // Flush updated pseudo-element styles to their anonymous nodes so that
47        // style changes which don't trigger box construction still take effect.
48        //
49        // TODO: see if this can be made more efficient (/run less often)
50        self.sync_pseudo_element_styles(node_id);
51
52        let damage_for_children = RestyleDamage::empty();
53        let children = std::mem::take(&mut self.nodes[node_id].children);
54        let layout_children = std::mem::take(self.nodes[node_id].layout_children.get_mut());
55        let use_layout_children = self.nodes[node_id].should_traverse_layout_children();
56        if use_layout_children {
57            let layout_children = layout_children.as_ref().unwrap();
58            for child in layout_children.iter() {
59                damage |= self.propagate_damage_flags(*child, damage_for_children);
60            }
61        } else {
62            for child in children.iter() {
63                damage |= self.propagate_damage_flags(*child, damage_for_children);
64            }
65            if let Some(before_id) = self.nodes[node_id].before {
66                damage |= self.propagate_damage_flags(before_id, damage_for_children);
67            }
68            if let Some(after_id) = self.nodes[node_id].after {
69                damage |= self.propagate_damage_flags(after_id, damage_for_children);
70            }
71        }
72
73        let node = &mut self.nodes[node_id];
74
75        // Put children back
76        node.children = children;
77        *node.layout_children.get_mut() = layout_children;
78
79        if damage.contains(CONSTRUCT_BOX) {
80            damage.insert(RestyleDamage::RELAYOUT);
81        }
82
83        // Compute damage to propagate to parent
84        let damage_for_parent = damage; // & RestyleDamage::RELAYOUT;
85
86        // If the node or any of it's children have been mutated or their layout styles
87        // have changed, then we should clear it's layout cache.
88        if damage.intersects(ONLY_RELAYOUT | CONSTRUCT_BOX) {
89            #[cfg(feature = "log-phase-times")]
90            crate::layout::layout_counters::note_cache_cleared();
91            node.cache.clear();
92            if let Some(inline_layout) = node
93                .data
94                .downcast_element_mut()
95                .and_then(|el| el.inline_layout_data.as_mut())
96            {
97                inline_layout.content_widths = None;
98            }
99            damage.remove(ONLY_RELAYOUT);
100        }
101
102        // Store damage for current node
103        node.set_damage(damage);
104
105        // let _is_fc_root = node
106        //     .primary_styles()
107        //     .map(|s| is_fc_root(&s))
108        //     .unwrap_or(false);
109
110        // if damage.contains(CONSTRUCT_BOX) {
111        //     // damage_for_parent.insert(CONSTRUCT_FC | CONSTRUCT_DESCENDENT);
112        //     damage_for_parent.insert(CONSTRUCT_BOX);
113        // }
114
115        // if damage.contains(CONSTRUCT_FC) {
116        //     damage_for_parent.insert(CONSTRUCT_DESCENDENT);
117        //     // if !is_fc_root {
118        //     damage_for_parent.insert(CONSTRUCT_FC);
119        //     // }
120        // }
121
122        // Propagate damage to parent
123        damage_for_parent
124    }
125
126    /// Flush updated pseudo-element (`::before`/`::after`) styles from the owning
127    /// element's stylo data to the pseudo-element's anonymous node.
128    ///
129    /// Pseudo-element styles are normally flushed to the pseudo-element's node
130    /// during box construction (see `flush_pseudo_elements`), but in incremental
131    /// mode box construction only runs for nodes with construction damage.
132    /// Pseudo-element style changes which don't require reconstruction (e.g.
133    /// animations/transitions of repaint- or relayout-only properties) must still
134    /// be flushed to the pseudo-element's node - along with the damage they imply -
135    /// so that layout and paint see the new style.
136    fn sync_pseudo_element_styles(&mut self, node_id: usize) {
137        let node = &self.nodes[node_id];
138
139        let before_node_id = node.before;
140        let after_node_id = node.after;
141        if before_node_id.is_none() && after_node_id.is_none() {
142            return;
143        }
144
145        let (before_style, after_style) = {
146            let style_data = node.stylo_element_data.get();
147            let Some(style_data) = style_data.as_ref() else {
148                return;
149            };
150            // Note: yes these are kinda backwards (see `flush_pseudo_elements`)
151            let pseudos = style_data.styles.pseudos.as_array();
152            (pseudos[1].clone(), pseudos[0].clone())
153        };
154
155        // Creation and removal of pseudo-elements is handled during box construction
156        // (Stylo generates construction damage for those cases), so only the case
157        // where the pseudo-element both was and remains present is handled here.
158        for (pe_node_id, pe_style) in [(before_node_id, before_style), (after_node_id, after_style)]
159        {
160            let (Some(pe_node_id), Some(pe_style)) = (pe_node_id, pe_style) else {
161                continue;
162            };
163            let mut pe_data = self.nodes[pe_node_id].stylo_element_data.get_mut();
164            let Some(pe_data) = pe_data.as_mut() else {
165                continue;
166            };
167            let Some(old_style) = pe_data.styles.primary.clone() else {
168                continue;
169            };
170            if std::ptr::eq(&*old_style, &*pe_style) {
171                continue;
172            }
173
174            let diff = RestyleDamage::compute_style_difference::<&Node>(&old_style, &pe_style);
175            pe_data.damage.insert(diff.damage);
176            pe_data.styles.primary = Some(pe_style);
177            pe_data.set_restyled();
178        }
179    }
180}
181
182// #[cfg(feature = "incremental")]
183// fn is_fc_root(style: &ComputedValues) -> bool {
184//     let display = style.clone_display();
185//     let display_inside = display.inside();
186
187//     match display_inside {
188//         DisplayInside::Flow => {
189//             // Depends on parent context
190//             false
191//         }
192
193//         DisplayInside::None => true,
194//         DisplayInside::FlowRoot => true,
195//         DisplayInside::Flex => true,
196//         DisplayInside::Grid => true,
197//         DisplayInside::Table => true,
198//         DisplayInside::TableCell => true,
199
200//         DisplayInside::Contents => false,
201//         DisplayInside::TableRowGroup => false,
202//         DisplayInside::TableColumn => false,
203//         DisplayInside::TableColumnGroup => false,
204//         DisplayInside::TableHeaderGroup => false,
205//         DisplayInside::TableFooterGroup => false,
206//         DisplayInside::TableRow => false,
207//     }
208// }
209
210pub(crate) fn compute_layout_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
211    let box_tree_needs_rebuild = || {
212        let old_box = old.get_box();
213        let new_box = new.get_box();
214
215        if old_box.display != new_box.display
216            || old_box.float != new_box.float
217            || old_box.position != new_box.position
218            || old.clone_visibility() != new.clone_visibility()
219        {
220            return true;
221        }
222
223        if old.get_font() != new.get_font() {
224            return true;
225        }
226
227        if new_box.display.outside() == DisplayOutside::Block
228            && new_box.display.inside() == DisplayInside::Flow
229        {
230            let alignment_establishes_new_block_formatting_context = |style: &ComputedValues| {
231                style.get_position().align_content.primary() != AlignFlags::NORMAL
232            };
233
234            let old_column = old.get_column();
235            let new_column = new.get_column();
236            if old_box.overflow_x.is_scrollable() != new_box.overflow_x.is_scrollable()
237                || old_column.is_multicol() != new_column.is_multicol()
238                || old_column.column_span != new_column.column_span
239                || alignment_establishes_new_block_formatting_context(old)
240                    != alignment_establishes_new_block_formatting_context(new)
241            {
242                return true;
243            }
244        }
245
246        if old_box.display.is_list_item() {
247            let old_list = old.get_list();
248            let new_list = new.get_list();
249            if old_list.list_style_position != new_list.list_style_position
250                || old_list.list_style_image != new_list.list_style_image
251                || (new_list.list_style_image == StyloImage::None
252                    && old_list.list_style_type != new_list.list_style_type)
253            {
254                return true;
255            }
256        }
257
258        if new.is_pseudo_style() && old.get_counters().content != new.get_counters().content {
259            return true;
260        }
261
262        false
263    };
264
265    let text_shaping_needs_recollect = || {
266        if old.clone_direction() != new.clone_direction()
267            || old.clone_unicode_bidi() != new.clone_unicode_bidi()
268        {
269            return true;
270        }
271
272        let old_text = old.get_inherited_text();
273        let new_text = new.get_inherited_text();
274        if !std::ptr::eq(old_text, new_text)
275            && (old_text.white_space_collapse != new_text.white_space_collapse
276                || old_text.text_transform != new_text.text_transform
277                || old_text.word_break != new_text.word_break
278                || old_text.overflow_wrap != new_text.overflow_wrap
279                || old_text.letter_spacing != new_text.letter_spacing
280                || old_text.word_spacing != new_text.word_spacing
281                || old_text.text_rendering != new_text.text_rendering)
282        {
283            return true;
284        }
285
286        false
287    };
288
289    #[allow(
290        clippy::if_same_then_else,
291        reason = "these branches will soon be different"
292    )]
293    if box_tree_needs_rebuild() {
294        ALL_DAMAGE
295    } else if text_shaping_needs_recollect() {
296        ALL_DAMAGE
297    } else {
298        // This element needs to be laid out again, but does not have any damage to
299        // its box. In the future, we will distinguish between types of damage to the
300        // fragment as well.
301        RestyleDamage::RELAYOUT
302    }
303}
304
305/// A child with a z_index that is hoisted up to it's containing Stacking Context for paint purposes
306#[derive(Debug, Clone)]
307pub struct HoistedPaintChild {
308    pub node_id: usize,
309    pub z_index: i32,
310    pub position: taffy::Point<f32>,
311}
312
313#[derive(Debug)]
314pub struct HoistedPaintChildren {
315    pub children: Vec<HoistedPaintChild>,
316    /// The number of hoisted point children with negative z_index
317    pub negative_z_count: u32,
318
319    pub content_area: taffy::Rect<f32>,
320}
321
322impl HoistedPaintChildren {
323    fn new() -> Self {
324        Self {
325            children: Vec::new(),
326            negative_z_count: 0,
327            content_area: taffy::Rect::ZERO,
328        }
329    }
330
331    pub fn reset(&mut self) {
332        self.children.clear();
333        self.negative_z_count = 0;
334    }
335
336    pub fn compute_content_size(&mut self, doc: &BaseDocument) {
337        fn child_pos(child: &HoistedPaintChild, doc: &BaseDocument) -> Rect<f32> {
338            let node = &doc.nodes[child.node_id];
339            let left = child.position.x + node.final_layout.location.x;
340            let top = child.position.y + node.final_layout.location.y;
341            let right = left + node.final_layout.size.width;
342            let bottom = top + node.final_layout.size.height;
343
344            taffy::Rect {
345                top,
346                left,
347                bottom,
348                right,
349            }
350        }
351
352        if self.children.is_empty() {
353            self.content_area = taffy::Rect::ZERO;
354        } else {
355            self.content_area = child_pos(&self.children[0], doc);
356            for child in self.children[1..].iter() {
357                let pos = child_pos(child, doc);
358                self.content_area.left = self.content_area.left.min(pos.left);
359                self.content_area.top = self.content_area.top.min(pos.top);
360                self.content_area.right = self.content_area.right.max(pos.right);
361                self.content_area.bottom = self.content_area.bottom.max(pos.bottom);
362            }
363        }
364    }
365
366    pub fn sort(&mut self) {
367        self.children.sort_by_key(|c| c.z_index);
368        self.negative_z_count = self.children.iter().take_while(|c| c.z_index < 0).count() as u32;
369    }
370
371    pub fn neg_z_range(&self) -> Range<usize> {
372        0..(self.negative_z_count as usize)
373    }
374
375    pub fn pos_z_range(&self) -> Range<usize> {
376        (self.negative_z_count as usize)..self.children.len()
377    }
378
379    pub fn neg_z_hoisted_children(
380        &self,
381    ) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
382        self.children[self.neg_z_range()].iter()
383    }
384
385    pub fn pos_z_hoisted_children(
386        &self,
387    ) -> impl ExactSizeIterator<Item = &HoistedPaintChild> + DoubleEndedIterator {
388        self.children[self.pos_z_range()].iter()
389    }
390}
391
392impl BaseDocument {
393    pub(crate) fn invalidate_inline_contexts(&mut self) {
394        let scale = self.viewport.scale();
395
396        let font_ctx = &self.font_ctx;
397        let layout_ctx = &mut self.layout_ctx;
398
399        let mut anon_nodes = Vec::new();
400
401        for (_, node) in self.nodes.iter_mut() {
402            if !(node.flags.contains(NodeFlags::IS_IN_DOCUMENT)) {
403                continue;
404            }
405
406            let Some(element) = node.data.downcast_element_mut() else {
407                continue;
408            };
409
410            if element.inline_layout_data.is_some() {
411                if node.is_anonymous() {
412                    anon_nodes.push(node.id);
413                } else {
414                    node.insert_damage(ALL_DAMAGE);
415                }
416            } else if let Some(input) = element.text_input_data_mut() {
417                input.editor.set_scale(scale);
418                let mut font_ctx = font_ctx.lock().unwrap();
419                input.editor.refresh_layout(&mut font_ctx, layout_ctx);
420                node.insert_damage(ONLY_RELAYOUT);
421            }
422        }
423
424        for node_id in anon_nodes {
425            if let Some(parent_id) = *(self.nodes[node_id].layout_parent.get_mut()) {
426                self.nodes[parent_id].insert_damage(ALL_DAMAGE);
427            }
428        }
429    }
430
431    pub fn flush_styles_to_layout(&mut self, node_id: usize) {
432        self.flush_styles_to_layout_impl(node_id, None);
433    }
434
435    /// Flush a CSS image layer list (`background-image` or `mask-image`) from style
436    /// to dedicated storage on the node, fetching any images which are not yet loaded.
437    fn flush_image_layers_from_style(&mut self, node_id: usize, kind: ImageLayerKind) {
438        let doc_id = self.id();
439        let node = self.nodes.get_mut(node_id).unwrap();
440        let stylo_element_data = node.stylo_element_data.get();
441        let primary_styles = stylo_element_data
442            .as_ref()
443            .and_then(|data| data.styles.get_primary());
444        let Some(style) = primary_styles else {
445            return;
446        };
447        let Some(elem) = node.data.downcast_element_mut() else {
448            return;
449        };
450
451        let (style_images, elem_images) = match kind {
452            ImageLayerKind::Background => (
453                &style.get_background().background_image.0,
454                &mut elem.background_images,
455            ),
456            ImageLayerKind::Mask => (&style.get_svg().mask_image.0, &mut elem.mask_images),
457        };
458
459        let len = style_images.len();
460        elem_images.resize_with(len, || None);
461
462        for idx in 0..len {
463            let style_image = &style_images[idx];
464            let new_image = match style_image {
465                StyloImage::Url(ComputedUrl::Valid(new_url)) => {
466                    let old_image = elem_images[idx].as_ref();
467                    let old_image_url = old_image.map(|data| &data.url);
468                    if old_image_url.is_some_and(|old_url| **new_url == **old_url) {
469                        break;
470                    }
471
472                    // Check cache first
473                    let url_str = new_url.as_str();
474                    if let Some(cached_image) = self.image_cache.get(url_str) {
475                        #[cfg(feature = "tracing")]
476                        tracing::info!("Loading image {url_str} from cache");
477                        Some(ImageResourceData {
478                            url: new_url.clone(),
479                            status: Status::Ok,
480                            image: cached_image.clone(),
481                        })
482                    } else if let Some(waiting_list) = self.pending_images.get_mut(url_str) {
483                        // Image is already being fetched, queue this node
484                        #[cfg(feature = "tracing")]
485                        tracing::info!("Image {url_str} already pending, queueing node {node_id}");
486                        waiting_list.push((node_id, kind.image_type(idx)));
487                        Some(ImageResourceData::new(new_url.clone()))
488                    } else {
489                        // Start fetch and track as pending
490                        #[cfg(feature = "tracing")]
491                        tracing::info!("Fetching image {url_str}");
492                        self.pending_images
493                            .insert(url_str.to_string(), vec![(node_id, kind.image_type(idx))]);
494
495                        self.net_provider.fetch(
496                            doc_id,
497                            crate::net::stamped_request(
498                                (**new_url).clone(),
499                                self.abort_signal.as_ref(),
500                            ),
501                            ResourceHandler::boxed(
502                                self.tx.clone(),
503                                doc_id,
504                                None, // Don't pass node_id, we'll handle via pending_images
505                                self.shell_provider.clone(),
506                                ImageHandler::new(kind.image_type(idx)),
507                            ),
508                        );
509
510                        Some(ImageResourceData::new(new_url.clone()))
511                    }
512                }
513                _ => None,
514            };
515
516            // Element will always exist due to resize_with above
517            elem_images[idx] = new_image;
518        }
519    }
520
521    /// Walk the whole tree, converting styles to layout
522    fn flush_styles_to_layout_impl(
523        &mut self,
524        node_id: usize,
525        parent_stacking_context: Option<&mut HoistedPaintChildren>,
526    ) {
527        let mut new_stacking_context: HoistedPaintChildren = HoistedPaintChildren::new();
528        let stacking_context = &mut new_stacking_context;
529
530        // Flush background/mask images from style to dedicated storage on the node
531        self.flush_image_layers_from_style(node_id, ImageLayerKind::Background);
532        self.flush_image_layers_from_style(node_id, ImageLayerKind::Mask);
533
534        let incremental = self.incremental_layout;
535        let display = {
536            let node = self.nodes.get_mut(node_id).unwrap();
537            let _damage = node.damage().unwrap_or(ALL_DAMAGE);
538            let stylo_element_data = node.stylo_element_data.get();
539            let primary_styles = stylo_element_data
540                .as_ref()
541                .and_then(|data| data.styles.get_primary());
542
543            let Some(style) = primary_styles else {
544                return;
545            };
546
547            // if damage.intersects(RestyleDamage::RELAYOUT | CONSTRUCT_BOX) {
548            node.style = stylo_taffy::to_taffy_style(style);
549            node.display_constructed_as = style.clone_display();
550            // }
551
552            // In non-incremental mode we unconditionally clear the Taffy cache.
553            // In incremental mode this is handled as part of damage propagation.
554            if !incremental {
555                node.cache.clear();
556                if let Some(inline_layout) = node
557                    .data
558                    .downcast_element_mut()
559                    .and_then(|el| el.inline_layout_data.as_mut())
560                {
561                    inline_layout.content_widths = None;
562                }
563            }
564
565            node.style.display
566        };
567
568        // If the node has children, then take those children and...
569        let children = self.nodes[node_id].layout_children.borrow_mut().take();
570        if let Some(mut children) = children {
571            let is_flex_or_grid = matches!(display, taffy::Display::Flex | taffy::Display::Grid);
572
573            // Recursively call flush_styles_to_layout on each child
574            for &child in children.iter() {
575                self.flush_styles_to_layout_impl(
576                    child,
577                    match self.nodes[child].is_stacking_context_root(is_flex_or_grid) {
578                        true => None,
579                        false => Some(stacking_context),
580                    },
581                );
582            }
583
584            // Sort layout_children
585            if is_flex_or_grid {
586                children.sort_by(|left, right| {
587                    let left_node = self.nodes.get(*left).unwrap();
588                    let right_node = self.nodes.get(*right).unwrap();
589                    left_node.order().cmp(&right_node.order())
590                });
591            }
592
593            // Reserve space for paint_children
594            let mut paint_children = self.nodes[node_id].paint_children.borrow_mut();
595            if paint_children.is_none() {
596                *paint_children = Some(Vec::new());
597            }
598            let paint_children = paint_children.as_mut().unwrap();
599            paint_children.clear();
600            paint_children.reserve(children.len());
601
602            // Push children to either paint_children or layout_children depending on
603            for &child_id in children.iter() {
604                let child = &self.nodes[child_id];
605
606                let Some(style) = child.primary_styles() else {
607                    paint_children.push(child_id);
608                    continue;
609                };
610
611                let position = style.clone_position();
612                let z_index = style.clone_z_index().integer_or(0);
613
614                // TODO: more complete hoisting detection
615                // z-index applies to static flex/grid items too
616                // (css-flexbox-1 §painting, css-grid-1 §z-order).
617                if z_index != 0 && (position != Position::Static || is_flex_or_grid) {
618                    stacking_context.children.push(HoistedPaintChild {
619                        node_id: child_id,
620                        z_index,
621                        position: taffy::Point::ZERO,
622                    })
623                } else {
624                    paint_children.push(child_id);
625                }
626            }
627
628            // Sort paint_children
629            paint_children.sort_by(|left, right| {
630                let left_node = self.nodes.get(*left).unwrap();
631                let right_node = self.nodes.get(*right).unwrap();
632                node_to_paint_order(left_node, is_flex_or_grid)
633                    .cmp(&node_to_paint_order(right_node, is_flex_or_grid))
634            });
635
636            // Put children back
637            *self.nodes[node_id].layout_children.borrow_mut() = Some(children);
638        }
639
640        if let Some(parent_stacking_context) = parent_stacking_context {
641            let position = self.nodes[node_id].final_layout.location;
642            let scroll_offset = self.nodes[node_id].scroll_offset;
643            for hoisted in stacking_context.children.iter_mut() {
644                hoisted.position.x += position.x - scroll_offset.x as f32;
645                hoisted.position.y += position.y - scroll_offset.y as f32;
646            }
647            parent_stacking_context
648                .children
649                .extend(stacking_context.children.iter().cloned());
650        } else {
651            stacking_context.sort();
652            stacking_context.compute_content_size(self);
653            self.nodes[node_id].stacking_context = Some(Box::new(new_stacking_context));
654        }
655    }
656}
657
658#[inline(always)]
659fn position_to_order(pos: Position) -> i32 {
660    match pos {
661        Position::Static => 0,
662        // All positioned descendants with z-index: auto share one paint
663        // level (CSS 2.1 Appendix E step 8); the stable sort keeps them in
664        // tree order among themselves, above in-flow content and floats.
665        Position::Relative | Position::Sticky | Position::Absolute | Position::Fixed => 2,
666    }
667}
668#[inline(always)]
669fn float_to_order(pos: Float) -> i32 {
670    match pos {
671        Float::None => 0,
672        _ => 1,
673    }
674}
675
676/// Paint sort key: (paint level, order-modified position). Positioned
677/// (z-index: auto) descendants paint above in-flow content (CSS 2.1
678/// Appendix E step 8); within a level the stable sort preserves
679/// (order-modified) document order.
680#[inline(always)]
681fn node_to_paint_order(node: &Node, is_flex_or_grid: bool) -> (i32, i32) {
682    let Some(style) = node.primary_styles() else {
683        return (0, 0);
684    };
685    let position = style.clone_position();
686    if is_flex_or_grid {
687        match position {
688            Position::Static => (0, style.clone_order()),
689            Position::Relative | Position::Sticky => (2, style.clone_order()),
690            // Out-of-flow children are not flex/grid items: `order` does
691            // not apply; tree order does.
692            Position::Absolute | Position::Fixed => (2, 0),
693        }
694    } else {
695        (
696            position_to_order(position) + float_to_order(style.clone_float()),
697            0,
698        )
699    }
700}