layout/
dom.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::marker::PhantomData;
6
7use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
8use base::id::{BrowsingContextId, PipelineId};
9use html5ever::{local_name, ns};
10use layout_api::wrapper_traits::{LayoutDataTrait, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
11use layout_api::{
12    GenericLayoutDataTrait, LayoutDamage, LayoutElementType,
13    LayoutNodeType as ScriptLayoutNodeType, SVGElementData,
14};
15use malloc_size_of_derive::MallocSizeOf;
16use net_traits::image_cache::Image;
17use script::layout_dom::ServoThreadSafeLayoutNode;
18use servo_arc::Arc as ServoArc;
19use smallvec::SmallVec;
20use style::context::SharedStyleContext;
21use style::properties::ComputedValues;
22use style::selector_parser::{PseudoElement, RestyleDamage};
23
24use crate::cell::{ArcRefCell, WeakRefCell};
25use crate::flexbox::FlexLevelBox;
26use crate::flow::BlockLevelBox;
27use crate::flow::inline::{InlineItem, SharedInlineStyles, WeakInlineItem};
28use crate::fragment_tree::Fragment;
29use crate::geom::PhysicalSize;
30use crate::layout_box_base::LayoutBoxBase;
31use crate::replaced::CanvasInfo;
32use crate::table::{TableLevelBox, WeakTableLevelBox};
33use crate::taffy::TaffyItemBox;
34
35#[derive(MallocSizeOf)]
36pub struct PseudoLayoutData {
37    pseudo: PseudoElement,
38    data: ArcRefCell<InnerDOMLayoutData>,
39}
40
41/// The data that is stored in each DOM node that is used by layout.
42#[derive(Default, MallocSizeOf)]
43pub struct InnerDOMLayoutData {
44    pub(super) self_box: ArcRefCell<Option<LayoutBox>>,
45    pub(super) pseudo_boxes: SmallVec<[PseudoLayoutData; 2]>,
46}
47
48impl InnerDOMLayoutData {
49    fn pseudo_layout_data(
50        &self,
51        pseudo_element: PseudoElement,
52    ) -> Option<ArcRefCell<InnerDOMLayoutData>> {
53        for pseudo_layout_data in self.pseudo_boxes.iter() {
54            if pseudo_element == pseudo_layout_data.pseudo {
55                return Some(pseudo_layout_data.data.clone());
56            }
57        }
58        None
59    }
60
61    fn create_pseudo_layout_data(
62        &mut self,
63        pseudo_element: PseudoElement,
64    ) -> ArcRefCell<InnerDOMLayoutData> {
65        let data: ArcRefCell<InnerDOMLayoutData> = Default::default();
66        self.pseudo_boxes.push(PseudoLayoutData {
67            pseudo: pseudo_element,
68            data: data.clone(),
69        });
70        data
71    }
72
73    fn fragments(&self) -> Vec<Fragment> {
74        self.self_box
75            .borrow()
76            .as_ref()
77            .and_then(|layout_box| layout_box.with_base(LayoutBoxBase::fragments))
78            .unwrap_or_default()
79    }
80
81    fn repair_style(&self, node: &ServoThreadSafeLayoutNode, context: &SharedStyleContext) {
82        if let Some(layout_object) = &*self.self_box.borrow() {
83            layout_object.repair_style(context, node, &node.style(context));
84        }
85
86        for pseudo_layout_data in self.pseudo_boxes.iter() {
87            let Some(node_with_pseudo) = node.with_pseudo(pseudo_layout_data.pseudo) else {
88                continue;
89            };
90            pseudo_layout_data
91                .data
92                .borrow()
93                .repair_style(&node_with_pseudo, context);
94        }
95    }
96
97    fn with_layout_box_base(&self, callback: impl Fn(&LayoutBoxBase)) {
98        if let Some(data) = self.self_box.borrow().as_ref() {
99            data.with_base(callback);
100        }
101    }
102
103    fn with_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase)) {
104        self.with_layout_box_base(&callback);
105        for pseudo_layout_data in self.pseudo_boxes.iter() {
106            pseudo_layout_data
107                .data
108                .borrow()
109                .with_layout_box_base(&callback);
110        }
111    }
112}
113
114/// A box that is stored in one of the `DOMLayoutData` slots.
115#[derive(Debug, MallocSizeOf)]
116pub(super) enum LayoutBox {
117    DisplayContents(SharedInlineStyles),
118    BlockLevel(ArcRefCell<BlockLevelBox>),
119    InlineLevel(InlineItem),
120    FlexLevel(ArcRefCell<FlexLevelBox>),
121    TableLevelBox(TableLevelBox),
122    TaffyItemBox(ArcRefCell<TaffyItemBox>),
123}
124
125impl LayoutBox {
126    pub(crate) fn with_base<T>(&self, callback: impl FnOnce(&LayoutBoxBase) -> T) -> Option<T> {
127        Some(match self {
128            LayoutBox::DisplayContents(..) => return None,
129            LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().with_base(callback),
130            LayoutBox::InlineLevel(inline_item) => inline_item.with_base(callback),
131            LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().with_base(callback),
132            LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().with_base(callback),
133            LayoutBox::TableLevelBox(table_box) => table_box.with_base(callback),
134        })
135    }
136
137    pub(crate) fn with_base_mut<T>(
138        &mut self,
139        callback: impl FnOnce(&mut LayoutBoxBase) -> T,
140    ) -> Option<T> {
141        Some(match self {
142            LayoutBox::DisplayContents(..) => return None,
143            LayoutBox::BlockLevel(block_level_box) => {
144                block_level_box.borrow_mut().with_base_mut(callback)
145            },
146            LayoutBox::InlineLevel(inline_item) => inline_item.with_base_mut(callback),
147            LayoutBox::FlexLevel(flex_level_box) => {
148                flex_level_box.borrow_mut().with_base_mut(callback)
149            },
150            LayoutBox::TaffyItemBox(taffy_item_box) => {
151                taffy_item_box.borrow_mut().with_base_mut(callback)
152            },
153            LayoutBox::TableLevelBox(table_box) => table_box.with_base_mut(callback),
154        })
155    }
156
157    fn repair_style(
158        &self,
159        context: &SharedStyleContext,
160        node: &ServoThreadSafeLayoutNode,
161        new_style: &ServoArc<ComputedValues>,
162    ) {
163        match self {
164            LayoutBox::DisplayContents(inline_shared_styles) => {
165                *inline_shared_styles.style.borrow_mut() = new_style.clone();
166                *inline_shared_styles.selected.borrow_mut() = node.selected_style();
167            },
168            LayoutBox::BlockLevel(block_level_box) => {
169                block_level_box
170                    .borrow_mut()
171                    .repair_style(context, node, new_style);
172            },
173            LayoutBox::InlineLevel(inline_item) => {
174                inline_item.repair_style(context, node, new_style);
175            },
176            LayoutBox::FlexLevel(flex_level_box) => flex_level_box
177                .borrow_mut()
178                .repair_style(context, node, new_style),
179            LayoutBox::TableLevelBox(table_level_box) => {
180                table_level_box.repair_style(context, node, new_style)
181            },
182            LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box
183                .borrow_mut()
184                .repair_style(context, node, new_style),
185        }
186    }
187
188    fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
189        match self {
190            Self::DisplayContents(_) => {
191                // This box can't have children, its contents get reparented to its parent.
192                // Therefore, no need to do anything.
193            },
194            Self::BlockLevel(block_level_box) => {
195                block_level_box.borrow().attached_to_tree(layout_box)
196            },
197            Self::InlineLevel(inline_item) => inline_item.attached_to_tree(layout_box),
198            Self::FlexLevel(flex_level_box) => flex_level_box.borrow().attached_to_tree(layout_box),
199            Self::TableLevelBox(table_level_box) => table_level_box.attached_to_tree(layout_box),
200            Self::TaffyItemBox(taffy_item_box) => {
201                taffy_item_box.borrow().attached_to_tree(layout_box)
202            },
203        }
204    }
205
206    fn downgrade(&self) -> WeakLayoutBox {
207        match self {
208            Self::DisplayContents(inline_shared_styles) => {
209                WeakLayoutBox::DisplayContents(inline_shared_styles.clone())
210            },
211            Self::BlockLevel(block_level_box) => {
212                WeakLayoutBox::BlockLevel(block_level_box.downgrade())
213            },
214            Self::InlineLevel(inline_item) => WeakLayoutBox::InlineLevel(inline_item.downgrade()),
215            Self::FlexLevel(flex_level_box) => WeakLayoutBox::FlexLevel(flex_level_box.downgrade()),
216            Self::TableLevelBox(table_level_box) => {
217                WeakLayoutBox::TableLevelBox(table_level_box.downgrade())
218            },
219            Self::TaffyItemBox(taffy_item_box) => {
220                WeakLayoutBox::TaffyItemBox(taffy_item_box.downgrade())
221            },
222        }
223    }
224}
225
226#[derive(Clone, Debug, MallocSizeOf)]
227pub(super) enum WeakLayoutBox {
228    DisplayContents(SharedInlineStyles),
229    BlockLevel(WeakRefCell<BlockLevelBox>),
230    InlineLevel(WeakInlineItem),
231    FlexLevel(WeakRefCell<FlexLevelBox>),
232    TableLevelBox(WeakTableLevelBox),
233    TaffyItemBox(WeakRefCell<TaffyItemBox>),
234}
235
236impl WeakLayoutBox {
237    pub(crate) fn upgrade(&self) -> Option<LayoutBox> {
238        Some(match self {
239            Self::DisplayContents(inline_shared_styles) => {
240                LayoutBox::DisplayContents(inline_shared_styles.clone())
241            },
242            Self::BlockLevel(block_level_box) => LayoutBox::BlockLevel(block_level_box.upgrade()?),
243            Self::InlineLevel(inline_item) => LayoutBox::InlineLevel(inline_item.upgrade()?),
244            Self::FlexLevel(flex_level_box) => LayoutBox::FlexLevel(flex_level_box.upgrade()?),
245            Self::TableLevelBox(table_level_box) => {
246                LayoutBox::TableLevelBox(table_level_box.upgrade()?)
247            },
248            Self::TaffyItemBox(taffy_item_box) => {
249                LayoutBox::TaffyItemBox(taffy_item_box.upgrade()?)
250            },
251        })
252    }
253}
254
255/// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data
256/// structure interior mutability, as we will need to mutate the layout data of
257/// non-mutable DOM nodes.
258#[derive(Default, MallocSizeOf)]
259pub struct DOMLayoutData(AtomicRefCell<InnerDOMLayoutData>);
260
261// The implementation of this trait allows the data to be stored in the DOM.
262impl LayoutDataTrait for DOMLayoutData {}
263impl GenericLayoutDataTrait for DOMLayoutData {
264    fn as_any(&self) -> &dyn std::any::Any {
265        self
266    }
267}
268
269pub struct BoxSlot<'dom> {
270    pub(crate) slot: ArcRefCell<Option<LayoutBox>>,
271    pub(crate) marker: PhantomData<&'dom ()>,
272}
273
274impl From<ArcRefCell<Option<LayoutBox>>> for BoxSlot<'_> {
275    fn from(slot: ArcRefCell<Option<LayoutBox>>) -> Self {
276        Self {
277            slot,
278            marker: PhantomData,
279        }
280    }
281}
282
283/// A mutable reference to a `LayoutBox` stored in a DOM element.
284impl BoxSlot<'_> {
285    pub(crate) fn set(self, layout_box: LayoutBox) {
286        layout_box.attached_to_tree(layout_box.downgrade());
287        *self.slot.borrow_mut() = Some(layout_box);
288    }
289
290    pub(crate) fn take_layout_box_if_undamaged(&self, damage: LayoutDamage) -> Option<LayoutBox> {
291        if damage.has_box_damage() {
292            return None;
293        }
294        self.slot.borrow_mut().take()
295    }
296}
297
298impl Drop for BoxSlot<'_> {
299    fn drop(&mut self) {
300        if !std::thread::panicking() {
301            assert!(self.slot.borrow().is_some(), "failed to set a layout box");
302        }
303    }
304}
305
306pub(crate) trait NodeExt<'dom> {
307    /// Returns the image if it’s loaded, and its size in image pixels
308    /// adjusted for `image_density`.
309    fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)>;
310    fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
311    fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
312    fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
313    fn as_svg(&self) -> Option<SVGElementData<'dom>>;
314    fn as_typeless_object_with_data_attribute(&self) -> Option<String>;
315
316    fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>;
317    fn inner_layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>;
318    fn box_slot(&self) -> BoxSlot<'dom>;
319
320    /// Remove boxes for the element itself, and all of its pseudo-element boxes.
321    fn unset_all_boxes(&self);
322
323    /// Remove all pseudo-element boxes for this element.
324    fn unset_all_pseudo_boxes(&self);
325
326    fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment>;
327    fn with_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase));
328
329    fn repair_style(&self, context: &SharedStyleContext);
330    fn take_restyle_damage(&self) -> LayoutDamage;
331}
332
333impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
334    fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)> {
335        let (resource, metadata) = self.image_data()?;
336        let width = metadata.map(|metadata| metadata.width).unwrap_or_default();
337        let height = metadata.map(|metadata| metadata.height).unwrap_or_default();
338        let (mut width, mut height) = (width as f64, height as f64);
339        if let Some(density) = self.image_density().filter(|density| *density != 1.) {
340            width /= density;
341            height /= density;
342        }
343        Some((resource, PhysicalSize::new(width, height)))
344    }
345
346    fn as_svg(&self) -> Option<SVGElementData<'dom>> {
347        self.svg_data()
348    }
349
350    fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)> {
351        let data = self.media_data()?;
352        let natural_size = if let Some(frame) = data.current_frame {
353            Some(PhysicalSize::new(frame.width.into(), frame.height.into()))
354        } else {
355            data.metadata
356                .map(|meta| PhysicalSize::new(meta.width.into(), meta.height.into()))
357        };
358        Some((
359            data.current_frame.map(|frame| frame.image_key),
360            natural_size,
361        ))
362    }
363
364    fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)> {
365        let canvas_data = self.canvas_data()?;
366        let source = canvas_data.image_key;
367        Some((
368            CanvasInfo { source },
369            PhysicalSize::new(canvas_data.width.into(), canvas_data.height.into()),
370        ))
371    }
372
373    fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)> {
374        match (self.iframe_pipeline_id(), self.iframe_browsing_context_id()) {
375            (Some(pipeline_id), Some(browsing_context_id)) => {
376                Some((pipeline_id, browsing_context_id))
377            },
378            _ => None,
379        }
380    }
381
382    fn as_typeless_object_with_data_attribute(&self) -> Option<String> {
383        if self.type_id() !=
384            Some(ScriptLayoutNodeType::Element(
385                LayoutElementType::HTMLObjectElement,
386            ))
387        {
388            return None;
389        }
390
391        // TODO: This is the what the legacy layout system did, but really if Servo
392        // supports any `<object>` that's an image, it should support those with URLs
393        // and `type` attributes with image mime types.
394        let element = self.as_element()?;
395        if element.get_attr(&ns!(), &local_name!("type")).is_some() {
396            return None;
397        }
398        element
399            .get_attr(&ns!(), &local_name!("data"))
400            .map(|string| string.to_owned())
401    }
402
403    fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData> {
404        if self.layout_data().is_none() {
405            self.initialize_layout_data::<DOMLayoutData>();
406        }
407        self.layout_data()
408            .unwrap()
409            .as_any()
410            .downcast_ref::<DOMLayoutData>()
411            .unwrap()
412            .0
413            .borrow_mut()
414    }
415
416    fn inner_layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> {
417        self.layout_data().map(|data| {
418            data.as_any()
419                .downcast_ref::<DOMLayoutData>()
420                .unwrap()
421                .0
422                .borrow()
423        })
424    }
425
426    fn box_slot(&self) -> BoxSlot<'dom> {
427        let pseudo_element_chain = self.pseudo_element_chain();
428        let Some(primary) = pseudo_element_chain.primary else {
429            return self.ensure_inner_layout_data().self_box.clone().into();
430        };
431
432        let Some(secondary) = pseudo_element_chain.secondary else {
433            let primary_layout_data = self
434                .ensure_inner_layout_data()
435                .create_pseudo_layout_data(primary);
436            return primary_layout_data.borrow().self_box.clone().into();
437        };
438
439        // It's *very* important that this not borrow the element's main
440        // `InnerLayoutData`. Primary pseudo-elements are processed at the same recursion
441        // level as the main data, so the `BoxSlot` is created sequentially with other
442        // primary pseudo-elements and the element itself. The secondary pseudo-element is
443        // one level deep, so could be happening in parallel with the primary
444        // pseudo-elements or main element layout.
445        let primary_layout_data = self
446            .inner_layout_data()
447            .expect("Should already have element InnerLayoutData here.")
448            .pseudo_layout_data(primary)
449            .expect("Should already have primary pseudo-element InnerLayoutData here");
450        let secondary_layout_data = primary_layout_data
451            .borrow_mut()
452            .create_pseudo_layout_data(secondary);
453        secondary_layout_data.borrow().self_box.clone().into()
454    }
455
456    fn unset_all_boxes(&self) {
457        let mut layout_data = self.ensure_inner_layout_data();
458        *layout_data.self_box.borrow_mut() = None;
459        layout_data.pseudo_boxes.clear();
460
461        // Stylo already takes care of removing all layout data
462        // for DOM descendants of elements with `display: none`.
463    }
464
465    fn unset_all_pseudo_boxes(&self) {
466        self.ensure_inner_layout_data().pseudo_boxes.clear();
467    }
468
469    fn with_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase)) {
470        if let Some(inner_layout_data) = self.inner_layout_data() {
471            inner_layout_data.with_layout_box_base_including_pseudos(callback);
472        }
473    }
474
475    fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment> {
476        let Some(layout_data) = self.inner_layout_data() else {
477            return vec![];
478        };
479        match pseudo_element {
480            Some(pseudo_element) => layout_data
481                .pseudo_layout_data(pseudo_element)
482                .map(|pseudo_layout_data| pseudo_layout_data.borrow().fragments())
483                .unwrap_or_default(),
484            None => layout_data.fragments(),
485        }
486    }
487
488    fn repair_style(&self, context: &SharedStyleContext) {
489        if let Some(layout_data) = self.inner_layout_data() {
490            layout_data.repair_style(self, context);
491        }
492    }
493
494    fn take_restyle_damage(&self) -> LayoutDamage {
495        let damage = self
496            .style_data()
497            .map(|style_data| std::mem::take(&mut style_data.element_data.borrow_mut().damage))
498            .unwrap_or_else(RestyleDamage::reconstruct);
499        LayoutDamage::from_bits_retain(damage.bits())
500    }
501}