1use app_units::Au;
6use atomic_refcell::{AtomicRef, AtomicRefCell};
7use style::logical_geometry::LogicalAxis;
8use style::properties::ComputedValues;
9use style::values::computed::CSSPixelLength;
10use style::values::computed::length_percentage::CalcLengthPercentage;
11use style::values::specified::align::AlignFlags;
12use style::values::specified::box_::DisplayInside;
13use style::{Atom, Zero};
14use taffy::style_helpers::{TaffyMaxContent, TaffyMinContent};
15use taffy::{AvailableSpace, MaybeMath, RequestedAxis, RunMode};
16
17use super::{
18 SpecificTaffyGridInfo, TaffyContainer, TaffyItemBox, TaffyItemBoxInner, TaffyStyloStyle,
19};
20use crate::cell::ArcRefCell;
21use crate::context::LayoutContext;
22use crate::dom::WeakLayoutBox;
23use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
24use crate::fragment_tree::{
25 BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
26};
27use crate::geom::{
28 LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec,
29};
30use crate::layout_box_base::IndependentFormattingContextLayoutResult;
31use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
32use crate::sizing::{
33 ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, Size,
34 SizeConstraint,
35};
36use crate::style_ext::{Clamp, ContentBoxSizesAndPBM, LayoutStyle};
37use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock};
38
39const DUMMY_NODE_ID: taffy::NodeId = taffy::NodeId::new(u64::MAX);
40
41fn resolve_content_size(constraint: AvailableSpace, content_sizes: ContentSizes) -> f32 {
42 match constraint {
43 AvailableSpace::Definite(limit) => {
44 let min = content_sizes.min_content.to_f32_px();
45 let max = content_sizes.max_content.to_f32_px();
46 limit.min(max).max(min)
47 },
48 AvailableSpace::MinContent => content_sizes.min_content.to_f32_px(),
49 AvailableSpace::MaxContent => content_sizes.max_content.to_f32_px(),
50 }
51}
52
53#[inline(always)]
54fn with_independent_formatting_context<T>(
55 item: &mut TaffyItemBoxInner,
56 cb: impl FnOnce(&IndependentFormattingContext) -> T,
57) -> T {
58 match item {
59 TaffyItemBoxInner::InFlowBox(context) => cb(context),
60 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abspos_box) => {
61 cb(&AtomicRefCell::borrow(abspos_box).context)
62 },
63 }
64}
65
66struct TaffyContainerContext<'a> {
69 source_child_nodes: &'a [ArcRefCell<TaffyItemBox>],
70 layout_context: &'a LayoutContext<'a>,
71 positioning_context: &'a mut PositioningContext,
72 content_box_size_override: &'a ContainingBlock<'a>,
73 style: &'a ComputedValues,
74 fragment_flags: FragmentFlags,
75 specific_layout_info: Option<SpecificLayoutInfo>,
76
77 child_specific_layout_infos: Vec<Option<SpecificLayoutInfo>>,
79}
80
81struct ChildIter(std::ops::Range<usize>);
82impl Iterator for ChildIter {
83 type Item = taffy::NodeId;
84 fn next(&mut self) -> Option<Self::Item> {
85 self.0.next().map(taffy::NodeId::from)
86 }
87
88 fn size_hint(&self) -> (usize, Option<usize>) {
89 (self.0.len(), Some(self.0.len()))
90 }
91}
92
93impl taffy::TraversePartialTree for TaffyContainerContext<'_> {
94 type ChildIter<'a>
95 = ChildIter
96 where
97 Self: 'a;
98
99 fn child_ids(&self, _node_id: taffy::NodeId) -> Self::ChildIter<'_> {
100 ChildIter(0..self.source_child_nodes.len())
101 }
102
103 fn child_count(&self, _node_id: taffy::NodeId) -> usize {
104 self.source_child_nodes.len()
105 }
106
107 fn get_child_id(&self, _node_id: taffy::NodeId, index: usize) -> taffy::NodeId {
108 taffy::NodeId::from(index)
109 }
110}
111
112impl taffy::LayoutPartialTree for TaffyContainerContext<'_> {
113 type CustomIdent = Atom;
114
115 type CoreContainerStyle<'a>
116 = TaffyStyloStyle<&'a ComputedValues>
117 where
118 Self: 'a;
119
120 fn get_core_container_style(&self, _node_id: taffy::NodeId) -> Self::CoreContainerStyle<'_> {
121 TaffyStyloStyle::new(self.style, self.fragment_flags)
122 }
123
124 fn set_unrounded_layout(&mut self, node_id: taffy::NodeId, layout: &taffy::Layout) {
125 let id = usize::from(node_id);
126 (*self.source_child_nodes[id]).borrow_mut().taffy_layout = *layout;
127 }
128
129 #[expect(unsafe_code)]
130 fn resolve_calc_value(&self, val: *const (), basis: f32) -> f32 {
131 let calc = unsafe { &*(val as *const CalcLengthPercentage) };
139 calc.resolve(CSSPixelLength::new(basis)).px()
140 }
141
142 fn compute_child_layout(
143 &mut self,
144 node_id: taffy::NodeId,
145 inputs: taffy::LayoutInput,
146 ) -> taffy::LayoutOutput {
147 let mut child = (*self.source_child_nodes[usize::from(node_id)]).borrow_mut();
148 let child = &mut *child;
149
150 let output = with_independent_formatting_context(
151 &mut child.taffy_level_box,
152 |independent_context| -> taffy::LayoutOutput {
153 let containing_block = self.content_box_size_override;
155 let style = independent_context.style();
156
157 let child_containing_block = IndefiniteContainingBlock {
158 size: LogicalVec2 {
159 inline: inputs.parent_size.width.map(Au::from_f32_px),
160 block: inputs.parent_size.height.map(Au::from_f32_px),
161 },
162 style: containing_block.style,
163 };
164
165 let ContentBoxSizesAndPBM {
167 content_box_sizes,
168 pbm,
169 ..
170 } = independent_context
171 .layout_style()
172 .content_box_sizes_and_padding_border_margin(&child_containing_block);
173 let pb_sum = pbm.padding_border_sums.map(|v| v.to_f32_px());
174 let content_box_known_dimensions = taffy::Size {
175 width: inputs
176 .known_dimensions
177 .width
178 .map(|width| width - pb_sum.inline),
179 height: inputs
180 .known_dimensions
181 .height
182 .map(|height| height - pb_sum.block),
183 };
184 let preferred_aspect_ratio =
185 independent_context.preferred_aspect_ratio(&pbm.padding_border_sums);
186
187 let tentative_block_size = content_box_known_dimensions
189 .height
190 .map(Au::from_f32_px)
191 .map_or_else(SizeConstraint::default, SizeConstraint::Definite);
192
193 let inline_size = content_box_known_dimensions.width.unwrap_or_else(|| {
195 let constraint_space = ConstraintSpace {
196 block_size: tentative_block_size,
197 style,
198 preferred_aspect_ratio,
199 };
200
201 let result = independent_context
203 .inline_content_sizes(self.layout_context, &constraint_space);
204 let adjusted_available_space = inputs
205 .available_space
206 .width
207 .map_definite_value(|width| (width - pb_sum.inline).max(0.0));
208
209 let stretch_size = adjusted_available_space.into_option().map(Au::from_f32_px);
210 let (_, min_size, max_size) = content_box_sizes.inline.resolve_each(
211 Size::FitContent,
212 Au::zero,
213 stretch_size,
214 || result.sizes,
215 independent_context.layout_style().is_table(),
216 );
217 Au::from_f32_px(resolve_content_size(adjusted_available_space, result.sizes))
218 .clamp_between_extremums(min_size, max_size)
219 .to_f32_px()
220 });
221
222 if inputs.run_mode == RunMode::ComputeSize &&
224 inputs.axis == RequestedAxis::Horizontal
225 {
226 return taffy::LayoutOutput::from_outer_size(taffy::Size {
227 width: inline_size + pb_sum.inline,
228 height: 0.0,
230 });
231 }
232
233 let lazy_block_size = match content_box_known_dimensions.height {
234 None => LazySize::new(
235 &content_box_sizes.block,
236 LogicalAxis::Block,
237 Size::FitContent,
238 Au::zero,
239 None,
240 independent_context.layout_style().is_table(),
241 ),
242 Some(height) => Au::from_f32_px(height).into(),
243 };
244
245 if inputs.run_mode == RunMode::ComputeSize {
246 if let Some(height) = inputs.known_dimensions.height {
247 return taffy::LayoutOutput::from_outer_size(taffy::Size {
248 width: inline_size + pb_sum.inline,
249 height,
250 });
251 }
252
253 let content_block_size = independent_context
260 .content_block_size_under_default_constraint(
261 self.layout_context,
262 &mut PositioningContext::default(),
263 Au::from_f32_px(inline_size),
264 containing_block,
265 preferred_aspect_ratio,
266 &lazy_block_size,
267 );
268 let block_size = lazy_block_size.resolve(|| content_block_size).to_f32_px();
269 return taffy::LayoutOutput::from_outer_size(taffy::Size {
270 width: inline_size + pb_sum.inline,
271 height: block_size + pb_sum.block,
272 });
273 }
274
275 let content_box_size_override = ContainingBlock {
276 size: ContainingBlockSize {
277 inline: Au::from_f32_px(inline_size),
278 block: tentative_block_size,
279 },
280 style,
281 };
282
283 child.positioning_context = PositioningContext::default();
284 let layout = independent_context.layout(
285 self.layout_context,
286 &mut child.positioning_context,
287 &content_box_size_override,
288 containing_block,
289 preferred_aspect_ratio,
290 &lazy_block_size,
291 );
292
293 child.child_fragments = layout.fragments;
294 self.child_specific_layout_infos[usize::from(node_id)] =
295 layout.specific_layout_info;
296
297 let block_size = lazy_block_size
298 .resolve(|| layout.content_block_size)
299 .to_f32_px();
300
301 let computed_size = taffy::Size {
302 width: inline_size + pb_sum.inline,
303 height: block_size + pb_sum.block,
304 };
305 let size = inputs.known_dimensions.unwrap_or(computed_size);
306
307 taffy::LayoutOutput {
308 size,
309 baselines: taffy::Baselines {
310 first: layout.baselines.first.map(|baseline| {
311 (baseline + pbm.padding.block_start + pbm.border.block_start)
312 .to_f32_px()
313 }),
314 last: layout.baselines.last.map(|baseline| {
315 (baseline + pbm.padding.block_start + pbm.border.block_start)
316 .to_f32_px()
317 }),
318 },
319 ..taffy::LayoutOutput::DEFAULT
320 }
321 },
322 );
323 child.taffy_baselines = output.baselines;
324 output
325 }
326}
327
328impl taffy::LayoutGridContainer for TaffyContainerContext<'_> {
329 type GridContainerStyle<'a>
330 = TaffyStyloStyle<&'a ComputedValues>
331 where
332 Self: 'a;
333
334 type GridItemStyle<'a>
335 = TaffyStyloStyle<AtomicRef<'a, ComputedValues>>
336 where
337 Self: 'a;
338
339 fn get_grid_container_style(
340 &self,
341 _node_id: taffy::prelude::NodeId,
342 ) -> Self::GridContainerStyle<'_> {
343 TaffyStyloStyle::new(self.style, self.fragment_flags)
344 }
345
346 fn get_grid_child_style(
347 &self,
348 child_node_id: taffy::prelude::NodeId,
349 ) -> Self::GridItemStyle<'_> {
350 let id = usize::from(child_node_id);
351 let child = (*self.source_child_nodes[id]).borrow();
352 let fragment_flags = child.with_base(|base| base.base_fragment_info.flags);
353 let stylo_style = AtomicRef::map(child, |c| &*c.style);
354 TaffyStyloStyle::new(stylo_style, fragment_flags)
355 }
356
357 fn set_detailed_grid_info(
358 &mut self,
359 _node_id: taffy::NodeId,
360 specific_layout_info: taffy::DetailedGridInfo<Atom>,
361 ) {
362 self.specific_layout_info = Some(SpecificLayoutInfo::Grid(Box::new(
363 SpecificTaffyGridInfo::from_detailed_grid_layout(specific_layout_info),
364 )));
365 }
366}
367
368impl ComputeInlineContentSizes for TaffyContainer {
369 fn compute_inline_content_sizes(
370 &self,
371 layout_context: &LayoutContext,
372 _constraint_space: &ConstraintSpace,
373 ) -> InlineContentSizesResult {
374 let style = &self.style;
375
376 let max_content_inputs = taffy::LayoutInput {
377 run_mode: taffy::RunMode::ComputeSize,
378 sizing_mode: taffy::SizingMode::InherentSize,
379 axis: taffy::RequestedAxis::Horizontal,
380 vertical_margins_are_collapsible: taffy::Line::FALSE,
381
382 known_dimensions_are_definite: taffy::Size {
383 width: true,
384 height: true,
385 },
386 known_dimensions: taffy::Size::NONE,
387 parent_size: taffy::Size::NONE,
388 available_space: taffy::Size::MAX_CONTENT,
389 };
390
391 let min_content_inputs = taffy::LayoutInput {
392 available_space: taffy::Size::MIN_CONTENT,
393 ..max_content_inputs
394 };
395
396 let containing_block = &ContainingBlock {
397 size: ContainingBlockSize {
398 inline: Au::zero(),
399 block: SizeConstraint::default(),
400 },
401 style,
402 };
403
404 let mut grid_context = TaffyContainerContext {
405 layout_context,
406 positioning_context: &mut PositioningContext::default(),
407 content_box_size_override: containing_block,
408 style,
409 fragment_flags: self.fragment_flags,
410 source_child_nodes: &self.children,
411 specific_layout_info: None,
412 child_specific_layout_infos: vec![None; self.children.len()],
413 };
414
415 let (max_content_output, min_content_output) = match style.get_display().inside() {
416 DisplayInside::Grid => {
417 let max_content_output = taffy::compute_grid_layout(
418 &mut grid_context,
419 DUMMY_NODE_ID,
420 max_content_inputs,
421 );
422 let min_content_output = taffy::compute_grid_layout(
423 &mut grid_context,
424 DUMMY_NODE_ID,
425 min_content_inputs,
426 );
427 (max_content_output, min_content_output)
428 },
429 _ => panic!("Servo is only configured to use Taffy for CSS Grid layout"),
430 };
431
432 let pb_sums = self
433 .layout_style()
434 .padding_border_margin(containing_block)
435 .padding_border_sums;
436
437 InlineContentSizesResult {
438 sizes: ContentSizes {
439 max_content: Au::from_f32_px(max_content_output.size.width) - pb_sums.inline,
440 min_content: Au::from_f32_px(min_content_output.size.width) - pb_sums.inline,
441 },
442
443 depends_on_block_constraints: true,
448 }
449 }
450}
451
452impl TaffyContainer {
453 pub(crate) fn layout(
455 &self,
456 layout_context: &LayoutContext,
457 positioning_context: &mut PositioningContext,
458 content_box_size_override: &ContainingBlock,
459 containing_block: &ContainingBlock,
460 ) -> IndependentFormattingContextLayoutResult {
461 let mut container_ctx = TaffyContainerContext {
462 layout_context,
463 positioning_context,
464 content_box_size_override,
465 style: content_box_size_override.style,
466 fragment_flags: self.fragment_flags,
467 source_child_nodes: &self.children,
468 specific_layout_info: None,
469 child_specific_layout_infos: vec![None; self.children.len()],
470 };
471
472 let container_style = &content_box_size_override.style;
473 let align_items = container_style.get_align_items();
474 let justify_items = container_style.get_justify_items();
475 let pbm = self.layout_style().padding_border_margin(containing_block);
476
477 let known_dimensions = taffy::Size {
478 width: Some(
479 (content_box_size_override.size.inline + pbm.padding_border_sums.inline)
480 .to_f32_px(),
481 ),
482 height: content_box_size_override
483 .size
484 .block
485 .to_definite()
486 .map(Au::to_f32_px)
487 .maybe_add(pbm.padding_border_sums.block.to_f32_px()),
488 };
489
490 let taffy_containing_block = taffy::Size {
491 width: Some(containing_block.size.inline.to_f32_px()),
492 height: containing_block.size.block.to_definite().map(Au::to_f32_px),
493 };
494
495 let layout_input = taffy::LayoutInput {
496 run_mode: taffy::RunMode::PerformLayout,
497 sizing_mode: taffy::SizingMode::InherentSize,
498 axis: taffy::RequestedAxis::Vertical,
499 vertical_margins_are_collapsible: taffy::Line::FALSE,
500
501 known_dimensions_are_definite: taffy::Size {
502 width: true,
503 height: true,
504 },
505 known_dimensions,
506 parent_size: taffy_containing_block,
507 available_space: taffy::Size {
508 width: AvailableSpace::from(taffy_containing_block.width),
509 height: AvailableSpace::MaxContent,
510 },
511 };
512
513 let output = match container_ctx.style.get_display().inside() {
514 DisplayInside::Grid => {
515 taffy::compute_grid_layout(&mut container_ctx, DUMMY_NODE_ID, layout_input)
516 },
517 _ => panic!("Servo is only configured to use Taffy for CSS Grid layout"),
518 };
519
520 let writing_mode = containing_block.style.writing_mode;
521 let physical_padding = pbm.padding.to_physical(writing_mode);
522 let physical_border = pbm.border.to_physical(writing_mode);
523 let content_box_offset = PhysicalVec::new(
524 physical_padding.left + physical_border.left,
525 physical_padding.top + physical_border.top,
526 );
527 let mut out_of_flow_candidates = output.oof_candidates.iter();
528
529 let fragments: Vec<Fragment> = self
532 .children
533 .iter()
534 .map(|child| (**child).borrow_mut())
535 .enumerate()
536 .map(|(child_id, mut child)| {
537 fn rect_to_physical_sides<T>(rect: taffy::Rect<T>) -> PhysicalSides<T> {
538 PhysicalSides::new(rect.top, rect.right, rect.bottom, rect.left)
539 }
540
541 fn size_and_pos_to_logical_rect<T: Default>(
542 position: taffy::Point<T>,
543 size: taffy::Size<T>,
544 ) -> PhysicalRect<T> {
545 PhysicalRect::new(
546 PhysicalPoint::new(position.x, position.y),
547 PhysicalSize::new(size.width, size.height),
548 )
549 }
550
551 let layout = &child.taffy_layout;
552
553 let padding = rect_to_physical_sides(layout.padding.map(Au::from_f32_px));
554 let border = rect_to_physical_sides(layout.border.map(Au::from_f32_px));
555 let margin = rect_to_physical_sides(layout.margin.map(Au::from_f32_px));
556
557 let content_size = size_and_pos_to_logical_rect(
562 taffy::Point {
563 x: Au::from_f32_px(
564 layout.location.x + layout.padding.left + layout.border.left,
565 ) - pbm.padding.inline_start -
566 pbm.border.inline_start,
567 y: Au::from_f32_px(
568 layout.location.y + layout.padding.top + layout.border.top,
569 ) - pbm.padding.block_start -
570 pbm.border.block_start,
571 },
572 taffy::Size {
573 width: layout.size.width -
574 layout.padding.left -
575 layout.padding.right -
576 layout.border.left -
577 layout.border.right,
578 height: layout.size.height -
579 layout.padding.top -
580 layout.padding.bottom -
581 layout.border.top -
582 layout.border.bottom,
583 }
584 .map(Au::from_f32_px),
585 );
586
587 let child_specific_layout_info: Option<SpecificLayoutInfo> =
588 std::mem::take(&mut container_ctx.child_specific_layout_infos[child_id]);
589
590 let fragment = match &mut child.taffy_level_box {
591 TaffyItemBoxInner::InFlowBox(independent_box) => {
592 let mut fragment_info = independent_box.base_fragment_info();
593 fragment_info
594 .flags
595 .insert(FragmentFlags::IS_FLEX_OR_GRID_ITEM);
596 let mut box_fragment = BoxFragment::new(
597 fragment_info,
598 independent_box.style().clone(),
599 std::mem::take(&mut child.child_fragments),
600 content_size,
601 padding,
602 border,
603 margin,
604 child_specific_layout_info,
605 )
606 .with_baselines(Baselines {
607 first: child.taffy_baselines.first.map(|baseline| {
608 Au::from_f32_px(baseline) - padding.top - border.top
609 }),
610 last: child.taffy_baselines.last.map(|baseline| {
611 Au::from_f32_px(baseline) - padding.top - border.top
612 }),
613 });
614
615 child.positioning_context.layout_collected_children(
616 container_ctx.layout_context,
617 &mut box_fragment,
618 );
619
620 child
621 .positioning_context
622 .adjust_static_position_of_hoisted_fragments_with_offset(
623 &box_fragment.content_rect().origin.to_vector(),
624 PositioningContextLength::zero(),
625 );
626 container_ctx
627 .positioning_context
628 .append(std::mem::take(&mut child.positioning_context));
629
630 Fragment::Box(box_fragment.into())
631 },
632 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box) => {
633 fn resolve_alignment(value: AlignFlags, auto: AlignFlags) -> AlignFlags {
634 match value {
635 AlignFlags::AUTO => auto,
636 AlignFlags::NORMAL => AlignFlags::STRETCH,
637 value => value,
638 }
639 }
640
641 let candidate = out_of_flow_candidates.next();
642 debug_assert_eq!(
643 candidate.map(|candidate| usize::from(candidate.node)),
644 Some(child_id)
645 );
646 let static_position_rect = candidate
647 .map(|candidate| {
648 let taffy::Point { x, y } = candidate.static_position;
649 PhysicalRect::new(
650 PhysicalPoint::new(
651 Au::from_f32_px(x.area.start),
652 Au::from_f32_px(y.area.start),
653 ) - content_box_offset,
654 PhysicalSize::new(
655 Au::from_f32_px(x.area.end - x.area.start),
656 Au::from_f32_px(y.area.end - y.area.start),
657 ),
658 )
659 })
660 .unwrap_or_default();
661
662 let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
663 abs_pos_box.clone(),
664 static_position_rect,
665 LogicalVec2 {
666 inline: resolve_alignment(
667 child.style.get_justify_self().0,
668 justify_items.computed.0.0,
669 ),
670 block: resolve_alignment(
671 child.style.get_align_self().0,
672 align_items.0,
673 ),
674 },
675 container_ctx.style.writing_mode,
676 );
677 let hoisted_fragment = hoisted_box.fragment.clone();
678 container_ctx.positioning_context.push(hoisted_box);
679 Fragment::AbsoluteOrFixedPositionedPlaceholder(hoisted_fragment)
680 },
681 };
682
683 if let TaffyItemBoxInner::InFlowBox(independent_formatting_context) =
684 &child.taffy_level_box
685 {
686 independent_formatting_context
687 .base
688 .set_fragment(fragment.clone());
689 }
690 fragment
691 })
692 .collect();
693 debug_assert!(out_of_flow_candidates.next().is_none());
694
695 IndependentFormattingContextLayoutResult {
696 fragments,
697 content_block_size: Au::from_f32_px(output.size.height) - pbm.padding_border_sums.block,
698 content_inline_size_for_table: None,
699 baselines: Baselines {
700 first: output.baselines.first.map(|baseline| {
701 Au::from_f32_px(baseline) - pbm.padding.block_start - pbm.border.block_start
702 }),
703 last: output.baselines.last.map(|baseline| {
704 Au::from_f32_px(baseline) - pbm.padding.block_start - pbm.border.block_start
705 }),
706 },
707
708 depends_on_block_constraints: true,
713 specific_layout_info: container_ctx.specific_layout_info,
714 collapsible_margins_in_children: CollapsedBlockMargins::zero(),
715 }
716 }
717
718 #[inline]
719 pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
720 LayoutStyle::Default(&self.style)
721 }
722
723 pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
724 for child in &self.children {
725 child.borrow_mut().with_base_mut(|base| {
726 base.parent_box.replace(layout_box.clone());
727 });
728 }
729 }
730
731 pub(crate) fn subtree_size(&self) -> usize {
732 self.children
733 .iter()
734 .map(|child| child.borrow().with_base(|base| base.subtree_size()))
735 .sum()
736 }
737}