Skip to main content

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