1use app_units::Au;
6use atomic_refcell::AtomicRef;
7use compositing_traits::display_list::AxesScrollSensitivity;
8use euclid::Rect;
9use euclid::default::Size2D as UntypedSize2D;
10use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
11use layout_api::{LayoutElementType, LayoutNodeType};
12use malloc_size_of_derive::MallocSizeOf;
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, NonReplacedContents};
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::replaced::ReplacedContents;
34use crate::style_ext::{AxesOverflow, Display, DisplayGeneratingBox, DisplayInside};
35use crate::taffy::{TaffyItemBox, TaffyItemBoxInner};
36use crate::{DefiniteContainingBlock, PropagatedBoxTreeData};
37
38#[derive(MallocSizeOf)]
39pub struct BoxTree {
40 root: BlockFormattingContext,
43
44 viewport_scroll_sensitivity: AxesScrollSensitivity,
46}
47
48impl BoxTree {
49 #[servo_tracing::instrument(name = "Box Tree Construction", skip_all)]
50 pub(crate) fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self {
51 let root_element = root_element.to_threadsafe();
52
53 let root_style = root_element.style(&context.style_context);
62
63 let mut viewport_overflow = AxesOverflow::from(&*root_style);
64 let mut element_propagating_overflow = root_element;
65 if viewport_overflow.x == Overflow::Visible &&
66 viewport_overflow.y == Overflow::Visible &&
67 !root_style.get_box().display.is_none()
68 {
69 for child in root_element.children() {
70 if !child
71 .as_element()
72 .is_some_and(|element| element.is_body_element_of_html_element_root())
73 {
74 continue;
75 }
76
77 let style = child.style(&context.style_context);
78 if !style.get_box().display.is_none() {
79 viewport_overflow = AxesOverflow::from(&*style);
80 element_propagating_overflow = child;
81
82 break;
83 }
84 }
85 }
86
87 let boxes = construct_for_root_element(context, root_element);
88
89 assert!(boxes.len() <= 1);
91
92 if let Some(layout_data) = element_propagating_overflow.inner_layout_data() {
93 if let Some(ref mut layout_box) = *layout_data.self_box.borrow_mut() {
94 layout_box.with_base_mut(|base| {
95 base.base_fragment_info
96 .flags
97 .insert(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT)
98 });
99 }
100 }
101
102 let contents = BlockContainer::BlockLevelBoxes(boxes);
103 let contains_floats = contents.contains_floats();
104 Self {
105 root: BlockFormattingContext {
106 contents,
107 contains_floats,
108 },
109 viewport_scroll_sensitivity: AxesScrollSensitivity {
113 x: viewport_overflow.x.to_scrollable().into(),
114 y: viewport_overflow.y.to_scrollable().into(),
115 },
116 }
117 }
118
119 pub(crate) fn update(
140 context: &LayoutContext,
141 dirty_root_from_script: ServoLayoutNode<'_>,
142 ) -> bool {
143 let Some(box_tree_update) = IncrementalBoxTreeUpdate::find(dirty_root_from_script) else {
144 return false;
145 };
146 box_tree_update.update_from_dirty_root(context);
147 true
148 }
149}
150
151fn construct_for_root_element(
152 context: &LayoutContext,
153 root_element: ServoThreadSafeLayoutNode<'_>,
154) -> Vec<ArcRefCell<BlockLevelBox>> {
155 let info = NodeAndStyleInfo::new(
156 root_element,
157 root_element.style(&context.style_context),
158 root_element.take_restyle_damage(),
159 );
160 let box_style = info.style.get_box();
161
162 let display_inside = match Display::from(box_style.display) {
163 Display::None => {
164 root_element.unset_all_boxes();
165 return Vec::new();
166 },
167 Display::Contents => {
168 unreachable!()
172 },
173 Display::GeneratingBox(display_generating_box) => display_generating_box.display_inside(),
175 };
176
177 let contents = ReplacedContents::for_element(root_element, context)
178 .map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced);
179
180 let propagated_data = PropagatedBoxTreeData::default();
181 let root_box = if box_style.position.is_absolutely_positioned() {
182 BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(ArcRefCell::new(
183 AbsolutelyPositionedBox::construct(context, &info, display_inside, contents),
184 ))
185 } else if box_style.float.is_floating() {
186 BlockLevelBox::OutOfFlowFloatBox(FloatBox::construct(
187 context,
188 &info,
189 display_inside,
190 contents,
191 propagated_data,
192 ))
193 } else {
194 BlockLevelBox::Independent(IndependentFormattingContext::construct(
195 context,
196 &info,
197 display_inside,
198 contents,
199 propagated_data,
200 ))
201 };
202
203 let root_box = ArcRefCell::new(root_box);
204 root_element
205 .box_slot()
206 .set(LayoutBox::BlockLevel(root_box.clone()));
207 vec![root_box]
208}
209
210impl BoxTree {
211 #[servo_tracing::instrument(name = "Fragment Tree Construction", skip_all)]
212 pub(crate) fn layout(
213 &self,
214 layout_context: &LayoutContext,
215 viewport: UntypedSize2D<Au>,
216 ) -> FragmentTree {
217 let style = layout_context
218 .style_context
219 .stylist
220 .device()
221 .default_computed_values();
222
223 let physical_containing_block: Rect<Au, CSSPixel> =
226 PhysicalSize::from_untyped(viewport).into();
227 let initial_containing_block = DefiniteContainingBlock {
228 size: LogicalVec2 {
229 inline: physical_containing_block.size.width,
230 block: physical_containing_block.size.height,
231 },
232 style,
233 };
234
235 let mut positioning_context = PositioningContext::default();
236 let independent_layout = self.root.layout(
237 layout_context,
238 &mut positioning_context,
239 &(&initial_containing_block).into(),
240 );
241
242 let mut root_fragments = independent_layout.fragments.into_iter().collect::<Vec<_>>();
243
244 assert!(root_fragments.len() <= 1);
246
247 positioning_context.layout_initial_containing_block_children(
251 layout_context,
252 &initial_containing_block,
253 &mut root_fragments,
254 );
255
256 FragmentTree::new(
257 layout_context,
258 root_fragments,
259 physical_containing_block,
260 self.viewport_scroll_sensitivity,
261 )
262 }
263}
264
265#[allow(clippy::enum_variant_names)]
266enum DirtyRootBoxTreeNode {
267 AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>),
268 AbsolutelyPositionedInlineLevelBox(ArcRefCell<InlineItem>, usize),
269 AbsolutelyPositionedFlexLevelBox(ArcRefCell<FlexLevelBox>),
270 AbsolutelyPositionedTaffyLevelBox(ArcRefCell<TaffyItemBox>),
271}
272
273struct IncrementalBoxTreeUpdate<'dom> {
274 node: ServoLayoutNode<'dom>,
275 box_tree_node: DirtyRootBoxTreeNode,
276 primary_style: Arc<ComputedValues>,
277 display_inside: DisplayInside,
278}
279
280impl<'dom> IncrementalBoxTreeUpdate<'dom> {
281 fn find(dirty_root_from_script: ServoLayoutNode<'dom>) -> Option<Self> {
282 let mut maybe_dirty_root_node = Some(dirty_root_from_script);
283 while let Some(dirty_root_node) = maybe_dirty_root_node {
284 if let Some(dirty_root) = Self::new_if_valid(dirty_root_node) {
285 return Some(dirty_root);
286 }
287
288 maybe_dirty_root_node = dirty_root_node.parent_node();
289 }
290
291 None
292 }
293
294 fn new_if_valid(potential_dirty_root_node: ServoLayoutNode<'dom>) -> Option<Self> {
295 if !potential_dirty_root_node.is_element() {
296 return None;
297 }
298
299 if potential_dirty_root_node.type_id() ==
300 LayoutNodeType::Element(LayoutElementType::HTMLBodyElement)
301 {
302 return None;
304 }
305
306 let potential_thread_safe_dirty_root_node = potential_dirty_root_node.to_threadsafe();
308 let element_data = potential_thread_safe_dirty_root_node
309 .style_data()?
310 .element_data
311 .borrow();
312 if !element_data.styles.pseudos.is_empty() {
313 return None;
314 }
315
316 let layout_data = NodeExt::inner_layout_data(&potential_thread_safe_dirty_root_node)?;
317 if !layout_data.pseudo_boxes.is_empty() {
318 return None;
319 }
320
321 let primary_style = element_data.styles.primary();
322 let box_style = primary_style.get_box();
323
324 if !box_style.position.is_absolutely_positioned() {
325 return None;
326 }
327
328 let display_inside = match Display::from(box_style.display) {
329 Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { inside, .. }) => inside,
330 _ => return None,
331 };
332
333 let box_tree_node =
334 match &*AtomicRef::filter_map(layout_data.self_box.borrow(), Option::as_ref)? {
335 LayoutBox::DisplayContents(..) => return None,
336 LayoutBox::BlockLevel(block_level_box) => match &*block_level_box.borrow() {
337 BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
338 if box_style.position.is_absolutely_positioned() =>
339 {
340 if box_style.original_display.outside() == DisplayOutside::Inline {
344 return None;
345 }
346 DirtyRootBoxTreeNode::AbsolutelyPositionedBlockLevelBox(
347 block_level_box.clone(),
348 )
349 },
350 _ => return None,
351 },
352 LayoutBox::InlineLevel(inline_level_items) => {
353 let inline_level_box = inline_level_items.first()?;
354 let InlineItem::OutOfFlowAbsolutelyPositionedBox(_, text_offset_index) =
355 &*inline_level_box.borrow()
356 else {
357 return None;
358 };
359 DirtyRootBoxTreeNode::AbsolutelyPositionedInlineLevelBox(
360 inline_level_box.clone(),
361 *text_offset_index,
362 )
363 },
364 LayoutBox::FlexLevel(flex_level_box) => match &*flex_level_box.borrow() {
365 FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_)
366 if box_style.position.is_absolutely_positioned() =>
367 {
368 DirtyRootBoxTreeNode::AbsolutelyPositionedFlexLevelBox(
369 flex_level_box.clone(),
370 )
371 },
372 _ => return None,
373 },
374 LayoutBox::TableLevelBox(..) => return None,
375 LayoutBox::TaffyItemBox(taffy_level_box) => {
376 match &taffy_level_box.borrow().taffy_level_box {
377 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(_)
378 if box_style.position.is_absolutely_positioned() =>
379 {
380 DirtyRootBoxTreeNode::AbsolutelyPositionedTaffyLevelBox(
381 taffy_level_box.clone(),
382 )
383 },
384 _ => return None,
385 }
386 },
387 };
388
389 Some(Self {
390 node: potential_dirty_root_node,
391 box_tree_node,
392 primary_style: primary_style.clone(),
393 display_inside,
394 })
395 }
396
397 #[servo_tracing::instrument(name = "Box Tree Update From Dirty Root", skip_all)]
398 fn update_from_dirty_root(&self, context: &LayoutContext) {
399 let node = self.node.to_threadsafe();
400 let contents = ReplacedContents::for_element(node, context)
401 .map_or_else(|| NonReplacedContents::OfElement.into(), Contents::Replaced);
402
403 let info =
404 NodeAndStyleInfo::new(node, self.primary_style.clone(), node.take_restyle_damage());
405
406 let out_of_flow_absolutely_positioned_box = ArcRefCell::new(
407 AbsolutelyPositionedBox::construct(context, &info, self.display_inside, contents),
408 );
409 match &self.box_tree_node {
410 DirtyRootBoxTreeNode::AbsolutelyPositionedBlockLevelBox(block_level_box) => {
411 *block_level_box.borrow_mut() = BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(
412 out_of_flow_absolutely_positioned_box,
413 );
414 },
415 DirtyRootBoxTreeNode::AbsolutelyPositionedInlineLevelBox(
416 inline_level_box,
417 text_offset_index,
418 ) => {
419 *inline_level_box.borrow_mut() = InlineItem::OutOfFlowAbsolutelyPositionedBox(
420 out_of_flow_absolutely_positioned_box,
421 *text_offset_index,
422 );
423 },
424 DirtyRootBoxTreeNode::AbsolutelyPositionedFlexLevelBox(flex_level_box) => {
425 *flex_level_box.borrow_mut() = FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(
426 out_of_flow_absolutely_positioned_box,
427 );
428 },
429 DirtyRootBoxTreeNode::AbsolutelyPositionedTaffyLevelBox(taffy_level_box) => {
430 taffy_level_box.borrow_mut().taffy_level_box =
431 TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(
432 out_of_flow_absolutely_positioned_box,
433 );
434 },
435 }
436
437 let mut invalidate_start_point = self.node;
438 while let Some(parent_node) = invalidate_start_point.parent_node() {
439 parent_node.to_threadsafe().take_restyle_damage();
445
446 invalidate_start_point = parent_node;
447 }
448 }
449}