1use app_units::Au;
6use atomic_refcell::AtomicRef;
7use euclid::Rect;
8use euclid::default::Size2D as UntypedSize2D;
9use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
10use layout_api::{AxesOverflow, LayoutElementType, LayoutNodeType};
11use malloc_size_of_derive::MallocSizeOf;
12use paint_api::display_list::AxesScrollSensitivity;
13use script::layout_dom::{ServoLayoutNode, ServoThreadSafeLayoutNode};
14use servo_arc::Arc;
15use style::dom::{NodeInfo, TNode};
16use style::properties::ComputedValues;
17use style::values::computed::Overflow;
18use style::values::specified::box_::DisplayOutside;
19use style_traits::CSSPixel;
20
21use crate::cell::ArcRefCell;
22use crate::context::LayoutContext;
23use crate::dom::{LayoutBox, NodeExt};
24use crate::dom_traversal::{Contents, NodeAndStyleInfo};
25use crate::flexbox::FlexLevelBox;
26use crate::flow::float::FloatBox;
27use crate::flow::inline::InlineItem;
28use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
29use crate::formatting_contexts::IndependentFormattingContext;
30use crate::fragment_tree::{FragmentFlags, FragmentTree};
31use crate::geom::{LogicalVec2, PhysicalSize};
32use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
33use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
34use crate::taffy::{TaffyItemBox, TaffyItemBoxInner};
35use crate::{DefiniteContainingBlock, PropagatedBoxTreeData};
36
37#[derive(MallocSizeOf)]
38pub struct BoxTree {
39 root: BlockFormattingContext,
42
43 pub(crate) viewport_overflow: AxesOverflow,
45}
46
47impl BoxTree {
48 #[servo_tracing::instrument(name = "Box Tree Construction", skip_all)]
49 pub(crate) fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self {
50 let root_element = root_element.to_threadsafe();
51 let boxes = construct_for_root_element(context, root_element);
52
53 assert!(boxes.len() <= 1);
55
56 let viewport_overflow = Self::viewport_overflow(root_element, boxes.first());
57 let contents = BlockContainer::BlockLevelBoxes(boxes);
58 let contains_floats = contents.contains_floats();
59 Self {
60 root: BlockFormattingContext {
61 contents,
62 contains_floats,
63 },
64 viewport_overflow: viewport_overflow.to_scrollable(),
68 }
69 }
70
71 fn viewport_overflow(
72 root_element: ServoThreadSafeLayoutNode<'_>,
73 root_box: Option<&ArcRefCell<BlockLevelBox>>,
74 ) -> AxesOverflow {
75 let Some(root_box) = root_box else {
88 return AxesOverflow::default();
89 };
90
91 let propagate_from_body = || {
92 let body = root_element.children().find(|child| {
96 child
97 .as_element()
98 .is_some_and(|element| element.is_body_element_of_html_element_root())
99 })?;
100
101 let body_layout_data = body.inner_layout_data()?;
105 let mut body_box = body_layout_data.self_box.borrow_mut();
106 body_box.as_mut()?.with_base_mut(|base| {
107 base.base_fragment_info
108 .flags
109 .insert(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT);
110 AxesOverflow::from(&*base.style)
111 })
112 };
113
114 root_box.borrow_mut().with_base_mut(|base| {
115 let root_overflow = AxesOverflow::from(&*base.style);
116 if root_overflow.x == Overflow::Visible && root_overflow.y == Overflow::Visible {
117 if let Some(body_overflow) = propagate_from_body() {
118 return body_overflow;
119 }
120 }
121 base.base_fragment_info
122 .flags
123 .insert(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT);
124 root_overflow
125 })
126 }
127
128 pub(crate) fn update(
149 context: &LayoutContext,
150 dirty_root_from_script: ServoLayoutNode<'_>,
151 ) -> bool {
152 let Some(box_tree_update) = IncrementalBoxTreeUpdate::find(dirty_root_from_script) else {
153 return false;
154 };
155 box_tree_update.update_from_dirty_root(context);
156 true
157 }
158}
159
160fn construct_for_root_element(
161 context: &LayoutContext,
162 root_element: ServoThreadSafeLayoutNode<'_>,
163) -> Vec<ArcRefCell<BlockLevelBox>> {
164 let info = NodeAndStyleInfo::new(
165 root_element,
166 root_element.style(&context.style_context),
167 root_element.take_restyle_damage(),
168 );
169 let box_style = info.style.get_box();
170
171 let display_inside = match Display::from(box_style.display) {
172 Display::None => {
173 root_element.unset_all_boxes();
174 return Vec::new();
175 },
176 Display::Contents => {
177 unreachable!()
181 },
182 Display::GeneratingBox(display_generating_box) => display_generating_box.display_inside(),
184 };
185
186 let contents = Contents::for_element(root_element, context);
187
188 let propagated_data = PropagatedBoxTreeData::default();
189 let root_box = if box_style.position.is_absolutely_positioned() {
190 BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new(
191 AbsolutelyPositionedBox::construct(context, &info, display_inside, contents),
192 ))
193 } else if box_style.float.is_floating() {
194 BlockLevelBox::OutOfFlowFloatBox(FloatBox::construct(
195 context,
196 &info,
197 display_inside,
198 contents,
199 propagated_data,
200 ))
201 } else {
202 BlockLevelBox::Independent(IndependentFormattingContext::construct(
203 context,
204 &info,
205 display_inside,
206 contents,
207 propagated_data,
208 ))
209 };
210
211 let root_box = ArcRefCell::new(root_box);
212 root_element
213 .box_slot()
214 .set(LayoutBox::BlockLevel(root_box.clone()));
215 vec![root_box]
216}
217
218impl BoxTree {
219 #[servo_tracing::instrument(name = "Fragment Tree Construction", skip_all)]
220 pub(crate) fn layout(
221 &self,
222 layout_context: &LayoutContext,
223 viewport: UntypedSize2D<Au>,
224 ) -> FragmentTree {
225 let style = layout_context
226 .style_context
227 .stylist
228 .device()
229 .default_computed_values();
230
231 let physical_containing_block: Rect<Au, CSSPixel> =
234 PhysicalSize::from_untyped(viewport).into();
235 let initial_containing_block = DefiniteContainingBlock {
236 size: LogicalVec2 {
237 inline: physical_containing_block.size.width,
238 block: physical_containing_block.size.height,
239 },
240 style,
241 };
242
243 let mut positioning_context = PositioningContext::default();
244 let independent_layout = self.root.layout(
245 layout_context,
246 &mut positioning_context,
247 &(&initial_containing_block).into(),
248 );
249
250 let mut root_fragments = independent_layout.fragments.into_iter().collect::<Vec<_>>();
251
252 assert!(root_fragments.len() <= 1);
254
255 positioning_context.layout_initial_containing_block_children(
259 layout_context,
260 &initial_containing_block,
261 &mut root_fragments,
262 );
263
264 let viewport_scroll_sensitivity = AxesScrollSensitivity {
265 x: self.viewport_overflow.x.into(),
266 y: self.viewport_overflow.y.into(),
267 };
268
269 FragmentTree::new(
270 layout_context,
271 root_fragments,
272 physical_containing_block,
273 viewport_scroll_sensitivity,
274 )
275 }
276}
277
278#[expect(clippy::enum_variant_names)]
279enum DirtyRootBoxTreeNode {
280 AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>),
281 AbsolutelyPositionedInlineLevelBox(InlineItem, usize),
282 AbsolutelyPositionedFlexLevelBox(ArcRefCell<FlexLevelBox>),
283 AbsolutelyPositionedTaffyLevelBox(ArcRefCell<TaffyItemBox>),
284}
285
286struct IncrementalBoxTreeUpdate<'dom> {
287 node: ServoLayoutNode<'dom>,
288 box_tree_node: DirtyRootBoxTreeNode,
289 primary_style: Arc<ComputedValues>,
290 display_inside: DisplayInside,
291}
292
293impl<'dom> IncrementalBoxTreeUpdate<'dom> {
294 fn find(dirty_root_from_script: ServoLayoutNode<'dom>) -> Option<Self> {
295 let mut maybe_dirty_root_node = Some(dirty_root_from_script);
296 while let Some(dirty_root_node) = maybe_dirty_root_node {
297 if let Some(dirty_root) = Self::new_if_valid(dirty_root_node) {
298 return Some(dirty_root);
299 }
300
301 maybe_dirty_root_node = dirty_root_node.parent_node();
302 }
303
304 None
305 }
306
307 fn new_if_valid(potential_dirty_root_node: ServoLayoutNode<'dom>) -> Option<Self> {
308 if !potential_dirty_root_node.is_element() {
309 return None;
310 }
311
312 if potential_dirty_root_node.type_id() ==
313 LayoutNodeType::Element(LayoutElementType::HTMLBodyElement)
314 {
315 return None;
317 }
318
319 let potential_thread_safe_dirty_root_node = potential_dirty_root_node.to_threadsafe();
321 let element_data = potential_thread_safe_dirty_root_node
322 .style_data()?
323 .element_data
324 .borrow();
325 if !element_data.styles.pseudos.is_empty() {
326 return None;
327 }
328
329 let layout_data = NodeExt::inner_layout_data(&potential_thread_safe_dirty_root_node)?;
330 if !layout_data.pseudo_boxes.is_empty() {
331 return None;
332 }
333
334 let primary_style = element_data.styles.primary();
335 let box_style = primary_style.get_box();
336
337 if !box_style.position.is_absolutely_positioned() {
338 return None;
339 }
340
341 let display_inside = match Display::from(box_style.display) {
342 Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside,
343 _ => return None,
344 };
345
346 let box_tree_node =
347 match &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)? {
348 LayoutBox::DisplayContents(..) => return None,
349 LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() {
350 BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
351 if box_style.position.is_absolutely_positioned() =>
352 {
353 if box_style.original_display.outside() == DisplayOutside::Inline {
357 return None;
358 }
359 DirtyRootBoxTreeNode::AbsolutelyPositionedBlockLevelBox(
360 block_level_box.clone(),
361 )
362 },
363 _ => return None,
364 },
365 LayoutBox::InlineLevel(inline_level_box) => {
366 let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) =
367 inline_level_box
368 else {
369 return None;
370 };
371 DirtyRootBoxTreeNode::AbsolutelyPositionedInlineLevelBox(
372 inline_level_box.clone(),
373 *text_offset_index,
374 )
375 },
376 LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
377 FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
378 if box_style.position.is_absolutely_positioned() =>
379 {
380 DirtyRootBoxTreeNode::AbsolutelyPositionedFlexLevelBox(
381 flex_level_box.clone(),
382 )
383 },
384 _ => return None,
385 },
386 LayoutBox::TableLevelBox(..) => return None,
387 LayoutBox::TaffyItemBox(taffy_level_box) => {
388 match &taffy_level_box.borrow().taffy_level_box {
389 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(_)
390 if box_style.position.is_absolutely_positioned() =>
391 {
392 DirtyRootBoxTreeNode::AbsolutelyPositionedTaffyLevelBox(
393 taffy_level_box.clone(),
394 )
395 },
396 _ => return None,
397 }
398 },
399 };
400
401 Some(Self {
402 node: potential_dirty_root_node,
403 box_tree_node,
404 primary_style: primary_style.clone(),
405 display_inside,
406 })
407 }
408
409 #[servo_tracing::instrument(name = "Box Tree Update From Dirty Root", skip_all)]
410 fn update_from_dirty_root(&self, context: &LayoutContext) {
411 let node = self.node.to_threadsafe();
412 let contents = Contents::for_element(node, context);
413
414 let info =
415 NodeAndStyleInfo::new(node, self.primary_style.clone(), node.take_restyle_damage());
416
417 let out_of_flow_absolutely_positioned_box =
418 AbsolutelyPositionedBox::construct(context, &info, self.display_inside, contents);
419 match &self.box_tree_node {
420 DirtyRootBoxTreeNode::AbsolutelyPositionedBlockLevelBox(block_level_box) => {
421 *block_level_box.borrow_mut() = BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
422 ArcRefCell::new(out_of_flow_absolutely_positioned_box),
423 );
424 },
425 DirtyRootBoxTreeNode::AbsolutelyPositionedInlineLevelBox(
426 inline_level_box,
427 text_offset_index,
428 ) => match inline_level_box {
429 InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, offset_in_text) => {
430 *positioned_box.borrow_mut() = out_of_flow_absolutely_positioned_box;
431 assert_eq!(
432 *offset_in_text, *text_offset_index,
433 "The offset of the dirty root shouldn't have changed"
434 );
435 },
436 _ => unreachable!("The dirty root should be absolutely positioned"),
437 },
438 DirtyRootBoxTreeNode::AbsolutelyPositionedFlexLevelBox(flex_level_box) => {
439 *flex_level_box.borrow_mut() = FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(
440 ArcRefCell::new(out_of_flow_absolutely_positioned_box),
441 );
442 },
443 DirtyRootBoxTreeNode::AbsolutelyPositionedTaffyLevelBox(taffy_level_box) => {
444 taffy_level_box.borrow_mut().taffy_level_box =
445 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new(
446 out_of_flow_absolutely_positioned_box,
447 ));
448 },
449 }
450
451 let mut invalidate_start_point = self.node;
452 while let Some(parent_node) = invalidate_start_point.parent_node() {
453 parent_node.to_threadsafe().take_restyle_damage();
459
460 invalidate_start_point = parent_node;
461 }
462 }
463}