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(root_element, root_element.style(&context.style_context));
165 let box_style = info.style.get_box();
166
167 let display_inside = match Display::from(box_style.display) {
168 Display::None => return Vec::new(),
169 Display::Contents => {
170 unreachable!()
174 },
175 Display::GeneratingBox(display_generating_box) => display_generating_box.display_inside(),
177 };
178
179 let contents = Contents::for_element(root_element, context);
180
181 let propagated_data = PropagatedBoxTreeData::default();
182 let root_box = if box_style.position.is_absolutely_positioned() {
183 BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new(
184 AbsolutelyPositionedBox::construct(context, &info, display_inside, contents),
185 ))
186 } else if box_style.float.is_floating() {
187 BlockLevelBox::OutOfFlowFloatBox(FloatBox::construct(
188 context,
189 &info,
190 display_inside,
191 contents,
192 propagated_data,
193 ))
194 } else {
195 BlockLevelBox::Independent(IndependentFormattingContext::construct(
196 context,
197 &info,
198 display_inside,
199 contents,
200 propagated_data,
201 ))
202 };
203
204 let root_box = ArcRefCell::new(root_box);
205 root_element
206 .box_slot()
207 .set(LayoutBox::BlockLevel(root_box.clone()));
208 vec![root_box]
209}
210
211impl BoxTree {
212 #[servo_tracing::instrument(name = "Fragment Tree Construction", skip_all)]
213 pub(crate) fn layout(
214 &self,
215 layout_context: &LayoutContext,
216 viewport: UntypedSize2D<Au>,
217 ) -> FragmentTree {
218 let style = layout_context
219 .style_context
220 .stylist
221 .device()
222 .default_computed_values();
223
224 let physical_containing_block: Rect<Au, CSSPixel> =
227 PhysicalSize::from_untyped(viewport).into();
228 let initial_containing_block = DefiniteContainingBlock {
229 size: LogicalVec2 {
230 inline: physical_containing_block.size.width,
231 block: physical_containing_block.size.height,
232 },
233 style,
234 };
235
236 let mut positioning_context = PositioningContext::default();
237 let independent_layout = self.root.layout(
238 layout_context,
239 &mut positioning_context,
240 &(&initial_containing_block).into(),
241 );
242
243 let mut root_fragments = independent_layout.fragments;
244
245 assert!(root_fragments.len() <= 1);
247
248 positioning_context.layout_initial_containing_block_children(
252 layout_context,
253 &initial_containing_block,
254 &mut root_fragments,
255 );
256
257 let viewport_scroll_sensitivity = AxesScrollSensitivity {
258 x: self.viewport_overflow.x.into(),
259 y: self.viewport_overflow.y.into(),
260 };
261
262 FragmentTree::new(
263 layout_context,
264 root_fragments,
265 physical_containing_block,
266 viewport_scroll_sensitivity,
267 )
268 }
269}
270
271#[expect(clippy::enum_variant_names)]
272enum DirtyRootBoxTreeNode {
273 AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>),
274 AbsolutelyPositionedInlineLevelBox(InlineItem, usize),
275 AbsolutelyPositionedFlexLevelBox(ArcRefCell<FlexLevelBox>),
276 AbsolutelyPositionedTaffyLevelBox(ArcRefCell<TaffyItemBox>),
277}
278
279struct IncrementalBoxTreeUpdate<'dom> {
280 node: ServoLayoutNode<'dom>,
281 box_tree_node: DirtyRootBoxTreeNode,
282 primary_style: Arc<ComputedValues>,
283 display_inside: DisplayInside,
284}
285
286impl<'dom> IncrementalBoxTreeUpdate<'dom> {
287 fn find(dirty_root_from_script: ServoLayoutNode<'dom>) -> Option<Self> {
288 let mut maybe_dirty_root_node = Some(dirty_root_from_script);
289 while let Some(dirty_root_node) = maybe_dirty_root_node {
290 if let Some(dirty_root) = Self::new_if_valid(dirty_root_node) {
291 return Some(dirty_root);
292 }
293
294 maybe_dirty_root_node = dirty_root_node.parent_node();
295 }
296
297 None
298 }
299
300 fn new_if_valid(potential_dirty_root_node: ServoLayoutNode<'dom>) -> Option<Self> {
301 if !potential_dirty_root_node.is_element() {
302 return None;
303 }
304
305 if potential_dirty_root_node.type_id() ==
306 LayoutNodeType::Element(LayoutElementType::HTMLBodyElement)
307 {
308 return None;
310 }
311
312 let potential_thread_safe_dirty_root_node = potential_dirty_root_node.to_threadsafe();
314 let element_data = potential_thread_safe_dirty_root_node
315 .style_data()?
316 .element_data
317 .borrow();
318 if !element_data.styles.pseudos.is_empty() {
319 return None;
320 }
321
322 let layout_data = NodeExt::inner_layout_data(&potential_thread_safe_dirty_root_node)?;
323 if !layout_data.pseudo_boxes.is_empty() {
324 return None;
325 }
326
327 let primary_style = element_data.styles.primary();
328 let box_style = primary_style.get_box();
329
330 if !box_style.position.is_absolutely_positioned() {
331 return None;
332 }
333
334 let display_inside = match Display::from(box_style.display) {
335 Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside,
336 _ => return None,
337 };
338
339 let box_tree_node =
340 match &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)? {
341 LayoutBox::DisplayContents(..) => return None,
342 LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() {
343 BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
344 if box_style.position.is_absolutely_positioned() =>
345 {
346 if box_style.original_display.outside() == DisplayOutside::Inline {
350 return None;
351 }
352 DirtyRootBoxTreeNode::AbsolutelyPositionedBlockLevelBox(
353 block_level_box.clone(),
354 )
355 },
356 _ => return None,
357 },
358 LayoutBox::InlineLevel(inline_level_box) => {
359 let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) =
360 inline_level_box
361 else {
362 return None;
363 };
364 DirtyRootBoxTreeNode::AbsolutelyPositionedInlineLevelBox(
365 inline_level_box.clone(),
366 *text_offset_index,
367 )
368 },
369 LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
370 FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
371 if box_style.position.is_absolutely_positioned() =>
372 {
373 DirtyRootBoxTreeNode::AbsolutelyPositionedFlexLevelBox(
374 flex_level_box.clone(),
375 )
376 },
377 _ => return None,
378 },
379 LayoutBox::TableLevelBox(..) => return None,
380 LayoutBox::TaffyItemBox(taffy_level_box) => {
381 match &taffy_level_box.borrow().taffy_level_box {
382 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(_)
383 if box_style.position.is_absolutely_positioned() =>
384 {
385 DirtyRootBoxTreeNode::AbsolutelyPositionedTaffyLevelBox(
386 taffy_level_box.clone(),
387 )
388 },
389 _ => return None,
390 }
391 },
392 };
393
394 Some(Self {
395 node: potential_dirty_root_node,
396 box_tree_node,
397 primary_style: primary_style.clone(),
398 display_inside,
399 })
400 }
401
402 #[servo_tracing::instrument(name = "Box Tree Update From Dirty Root", skip_all)]
403 fn update_from_dirty_root(&self, context: &LayoutContext) {
404 let node = self.node.to_threadsafe();
405 let contents = Contents::for_element(node, context);
406
407 let info = NodeAndStyleInfo::new(node, self.primary_style.clone());
408
409 let build_new_box = |old_parent| {
410 let mut out_of_flow_absolutely_positioned_box =
411 AbsolutelyPositionedBox::construct(context, &info, self.display_inside, contents);
412 if let Some(old_parent) = old_parent {
413 out_of_flow_absolutely_positioned_box
414 .context
415 .base
416 .parent_box
417 .replace(old_parent);
418 }
419 out_of_flow_absolutely_positioned_box
420 };
421
422 match &self.box_tree_node {
423 DirtyRootBoxTreeNode::AbsolutelyPositionedBlockLevelBox(block_level_box) => {
424 let mut block_level_box = block_level_box.borrow_mut();
425 let old_parent = block_level_box.with_base(|base| base.parent_box.clone());
426 *block_level_box = BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
427 ArcRefCell::new(build_new_box(old_parent)),
428 );
429 },
430 DirtyRootBoxTreeNode::AbsolutelyPositionedInlineLevelBox(
431 inline_level_box,
432 text_offset_index,
433 ) => match inline_level_box {
434 InlineItem::OutOfFlowAbsolutelyPositionedBox(positioned_box, offset_in_text) => {
435 let mut positioned_box = positioned_box.borrow_mut();
436 let old_parent = positioned_box.context.base.parent_box.clone();
437 *positioned_box = build_new_box(old_parent);
438 assert_eq!(
439 *offset_in_text, *text_offset_index,
440 "The offset of the dirty root shouldn't have changed"
441 );
442 },
443 _ => unreachable!("The dirty root should be absolutely positioned"),
444 },
445 DirtyRootBoxTreeNode::AbsolutelyPositionedFlexLevelBox(flex_level_box) => {
446 let mut flex_level_box = flex_level_box.borrow_mut();
447 let old_parent = flex_level_box.with_base(|base| base.parent_box.clone());
448 *flex_level_box = FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new(
449 build_new_box(old_parent),
450 ));
451 },
452 DirtyRootBoxTreeNode::AbsolutelyPositionedTaffyLevelBox(taffy_level_box) => {
453 let mut taffy_level_box = taffy_level_box.borrow_mut();
454 let old_parent = taffy_level_box.with_base(|base| base.parent_box.clone());
455 taffy_level_box.taffy_level_box =
456 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new(
457 build_new_box(old_parent),
458 ));
459 },
460 }
461 }
462}