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;
25use crate::flexbox::FlexLevelBox;
26use crate::flow::BlockLevelBox;
27use crate::flow::inline::{InlineItem, SharedInlineStyles};
28use crate::fragment_tree::Fragment;
29use crate::geom::PhysicalSize;
30use crate::layout_box_base::LayoutBoxBase;
31use crate::replaced::CanvasInfo;
32use crate::table::TableLevelBox;
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            .map(|layout_box| layout_box.with_base_flat(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_each_layout_box_base(&self, callback: impl Fn(&LayoutBoxBase)) {
98        if let Some(data) = self.self_box.borrow().as_ref() {
99            data.with_each_base(callback);
100        }
101    }
102
103    fn with_each_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase)) {
104        self.with_each_layout_box_base(&callback);
105        for pseudo_layout_data in self.pseudo_boxes.iter() {
106            pseudo_layout_data
107                .data
108                .borrow()
109                .with_each_layout_box_base(&callback);
110        }
111    }
112}
113
114/// A box that is stored in one of the `DOMLayoutData` slots.
115#[derive(MallocSizeOf)]
116pub(super) enum LayoutBox {
117    DisplayContents(SharedInlineStyles),
118    BlockLevel(ArcRefCell<BlockLevelBox>),
119    InlineLevel(Vec<ArcRefCell<InlineItem>>),
120    FlexLevel(ArcRefCell<FlexLevelBox>),
121    TableLevelBox(TableLevelBox),
122    TaffyItemBox(ArcRefCell<TaffyItemBox>),
123}
124
125impl LayoutBox {
126    pub(crate) fn with_each_base(&self, callback: impl Fn(&LayoutBoxBase)) {
127        self.with_base_fold((), |_, base| callback(base))
128    }
129
130    pub(crate) fn with_first_base<T>(
131        &self,
132        callback: impl FnOnce(&LayoutBoxBase) -> T,
133    ) -> Option<T> {
134        Some(match self {
135            LayoutBox::DisplayContents(..) => return None,
136            LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().with_base(callback),
137            LayoutBox::InlineLevel(inline_items) => {
138                inline_items.first()?.borrow().with_base(callback)
139            },
140            LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().with_base(callback),
141            LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().with_base(callback),
142            LayoutBox::TableLevelBox(table_box) => table_box.with_base(callback),
143        })
144    }
145
146    pub(crate) fn with_base_flat<T>(&self, callback: impl Fn(&LayoutBoxBase) -> Vec<T>) -> Vec<T> {
147        match self {
148            LayoutBox::DisplayContents(..) => vec![],
149            LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().with_base(callback),
150            LayoutBox::InlineLevel(inline_items) => inline_items
151                .iter()
152                .flat_map(|inline_item| inline_item.borrow().with_base(&callback))
153                .collect(),
154            LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().with_base(callback),
155            LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().with_base(callback),
156            LayoutBox::TableLevelBox(table_box) => table_box.with_base(callback),
157        }
158    }
159
160    pub(crate) fn with_base_fold<T>(
161        &self,
162        init: T,
163        callback: impl Fn(T, &LayoutBoxBase) -> T,
164    ) -> T {
165        match self {
166            LayoutBox::DisplayContents(..) => init,
167            LayoutBox::BlockLevel(block_level_box) => block_level_box
168                .borrow()
169                .with_base(|base| callback(init, base)),
170            LayoutBox::InlineLevel(inline_items) => inline_items.iter().fold(init, |acc, item| {
171                item.borrow().with_base(|base| callback(acc, base))
172            }),
173            LayoutBox::FlexLevel(flex_level_box) => flex_level_box
174                .borrow()
175                .with_base(|base| callback(init, base)),
176            LayoutBox::TableLevelBox(table_level_box) => {
177                table_level_box.with_base(|base| callback(init, base))
178            },
179            LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box
180                .borrow()
181                .with_base(|base| callback(init, base)),
182        }
183    }
184
185    pub(crate) fn with_base_mut_fold<T>(
186        &mut self,
187        init: T,
188        callback: impl Fn(T, &mut LayoutBoxBase) -> T,
189    ) -> T {
190        match self {
191            LayoutBox::DisplayContents(..) => init,
192            LayoutBox::BlockLevel(block_level_box) => block_level_box
193                .borrow_mut()
194                .with_base_mut(|base| callback(init, base)),
195            LayoutBox::InlineLevel(inline_items) => inline_items.iter().fold(init, |acc, item| {
196                item.borrow_mut().with_base_mut(|base| callback(acc, base))
197            }),
198            LayoutBox::FlexLevel(flex_level_box) => flex_level_box
199                .borrow_mut()
200                .with_base_mut(|base| callback(init, base)),
201            LayoutBox::TableLevelBox(table_level_box) => {
202                table_level_box.with_base_mut(|base| callback(init, base))
203            },
204            LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box
205                .borrow_mut()
206                .with_base_mut(|base| callback(init, base)),
207        }
208    }
209
210    fn repair_style(
211        &self,
212        context: &SharedStyleContext,
213        node: &ServoThreadSafeLayoutNode,
214        new_style: &ServoArc<ComputedValues>,
215    ) {
216        match self {
217            LayoutBox::DisplayContents(inline_shared_styles) => {
218                *inline_shared_styles.style.borrow_mut() = new_style.clone();
219                *inline_shared_styles.selected.borrow_mut() = node.selected_style();
220            },
221            LayoutBox::BlockLevel(block_level_box) => {
222                block_level_box
223                    .borrow_mut()
224                    .repair_style(context, node, new_style);
225            },
226            LayoutBox::InlineLevel(inline_items) => {
227                for inline_item in inline_items {
228                    inline_item
229                        .borrow_mut()
230                        .repair_style(context, node, new_style);
231                }
232            },
233            LayoutBox::FlexLevel(flex_level_box) => flex_level_box
234                .borrow_mut()
235                .repair_style(context, node, new_style),
236            LayoutBox::TableLevelBox(table_level_box) => {
237                table_level_box.repair_style(context, node, new_style)
238            },
239            LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box
240                .borrow_mut()
241                .repair_style(context, node, new_style),
242        }
243    }
244
245    /// If this [`LayoutBox`] represents an unsplit (due to inline-block splits) inline
246    /// level item, unwrap and return it. If not, return `None`.
247    pub(crate) fn unsplit_inline_level_layout_box(self) -> Option<ArcRefCell<InlineItem>> {
248        let LayoutBox::InlineLevel(inline_level_boxes) = self else {
249            return None;
250        };
251        // If this element box has been subject to inline-block splitting, ignore it. It's
252        // not useful currently for incremental box tree construction.
253        if inline_level_boxes.len() != 1 {
254            return None;
255        }
256        inline_level_boxes.into_iter().next()
257    }
258}
259
260/// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data
261/// structure interior mutability, as we will need to mutate the layout data of
262/// non-mutable DOM nodes.
263#[derive(Default, MallocSizeOf)]
264pub struct DOMLayoutData(AtomicRefCell<InnerDOMLayoutData>);
265
266// The implementation of this trait allows the data to be stored in the DOM.
267impl LayoutDataTrait for DOMLayoutData {}
268impl GenericLayoutDataTrait for DOMLayoutData {
269    fn as_any(&self) -> &dyn std::any::Any {
270        self
271    }
272}
273
274pub struct BoxSlot<'dom> {
275    pub(crate) slot: Option<ArcRefCell<Option<LayoutBox>>>,
276    pub(crate) marker: PhantomData<&'dom ()>,
277}
278
279impl From<ArcRefCell<Option<LayoutBox>>> for BoxSlot<'_> {
280    fn from(layout_box_slot: ArcRefCell<Option<LayoutBox>>) -> Self {
281        let slot = Some(layout_box_slot);
282        Self {
283            slot,
284            marker: PhantomData,
285        }
286    }
287}
288
289/// A mutable reference to a `LayoutBox` stored in a DOM element.
290impl BoxSlot<'_> {
291    pub(crate) fn set(mut self, box_: LayoutBox) {
292        if let Some(slot) = &mut self.slot {
293            *slot.borrow_mut() = Some(box_);
294        }
295    }
296
297    pub(crate) fn take_layout_box_if_undamaged(&self, damage: LayoutDamage) -> Option<LayoutBox> {
298        if damage.has_box_damage() {
299            return None;
300        }
301        self.slot.as_ref().and_then(|slot| slot.borrow_mut().take())
302    }
303}
304
305impl Drop for BoxSlot<'_> {
306    fn drop(&mut self) {
307        if !std::thread::panicking() {
308            if let Some(slot) = &mut self.slot {
309                assert!(slot.borrow().is_some(), "failed to set a layout box");
310            }
311        }
312    }
313}
314
315pub(crate) trait NodeExt<'dom> {
316    /// Returns the image if it’s loaded, and its size in image pixels
317    /// adjusted for `image_density`.
318    fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)>;
319    fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
320    fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
321    fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
322    fn as_svg(&self) -> Option<SVGElementData>;
323    fn as_typeless_object_with_data_attribute(&self) -> Option<String>;
324
325    fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>;
326    fn inner_layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>;
327    fn box_slot(&self) -> BoxSlot<'dom>;
328
329    /// Remove boxes for the element itself, and all of its pseudo-element boxes.
330    fn unset_all_boxes(&self);
331
332    /// Remove all pseudo-element boxes for this element.
333    fn unset_all_pseudo_boxes(&self);
334
335    fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment>;
336    fn with_each_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase));
337
338    fn repair_style(&self, context: &SharedStyleContext);
339    fn take_restyle_damage(&self) -> LayoutDamage;
340}
341
342impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
343    fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)> {
344        let (resource, metadata) = self.image_data()?;
345        let width = metadata.map(|metadata| metadata.width).unwrap_or_default();
346        let height = metadata.map(|metadata| metadata.height).unwrap_or_default();
347        let (mut width, mut height) = (width as f64, height as f64);
348        if let Some(density) = self.image_density().filter(|density| *density != 1.) {
349            width /= density;
350            height /= density;
351        }
352        Some((resource, PhysicalSize::new(width, height)))
353    }
354
355    fn as_svg(&self) -> Option<SVGElementData> {
356        self.svg_data()
357    }
358
359    fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)> {
360        let data = self.media_data()?;
361        let natural_size = if let Some(frame) = data.current_frame {
362            Some(PhysicalSize::new(frame.width.into(), frame.height.into()))
363        } else {
364            data.metadata
365                .map(|meta| PhysicalSize::new(meta.width.into(), meta.height.into()))
366        };
367        Some((
368            data.current_frame.map(|frame| frame.image_key),
369            natural_size,
370        ))
371    }
372
373    fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)> {
374        let canvas_data = self.canvas_data()?;
375        let source = canvas_data.image_key;
376        Some((
377            CanvasInfo { source },
378            PhysicalSize::new(canvas_data.width.into(), canvas_data.height.into()),
379        ))
380    }
381
382    fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)> {
383        match (self.iframe_pipeline_id(), self.iframe_browsing_context_id()) {
384            (Some(pipeline_id), Some(browsing_context_id)) => {
385                Some((pipeline_id, browsing_context_id))
386            },
387            _ => None,
388        }
389    }
390
391    fn as_typeless_object_with_data_attribute(&self) -> Option<String> {
392        if self.type_id() !=
393            Some(ScriptLayoutNodeType::Element(
394                LayoutElementType::HTMLObjectElement,
395            ))
396        {
397            return None;
398        }
399
400        // TODO: This is the what the legacy layout system did, but really if Servo
401        // supports any `<object>` that's an image, it should support those with URLs
402        // and `type` attributes with image mime types.
403        let element = self.as_element()?;
404        if element.get_attr(&ns!(), &local_name!("type")).is_some() {
405            return None;
406        }
407        element
408            .get_attr(&ns!(), &local_name!("data"))
409            .map(|string| string.to_owned())
410    }
411
412    fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData> {
413        if self.layout_data().is_none() {
414            self.initialize_layout_data::<DOMLayoutData>();
415        }
416        self.layout_data()
417            .unwrap()
418            .as_any()
419            .downcast_ref::<DOMLayoutData>()
420            .unwrap()
421            .0
422            .borrow_mut()
423    }
424
425    fn inner_layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> {
426        self.layout_data().map(|data| {
427            data.as_any()
428                .downcast_ref::<DOMLayoutData>()
429                .unwrap()
430                .0
431                .borrow()
432        })
433    }
434
435    fn box_slot(&self) -> BoxSlot<'dom> {
436        let pseudo_element_chain = self.pseudo_element_chain();
437        let Some(primary) = pseudo_element_chain.primary else {
438            return self.ensure_inner_layout_data().self_box.clone().into();
439        };
440
441        let Some(secondary) = pseudo_element_chain.secondary else {
442            let primary_layout_data = self
443                .ensure_inner_layout_data()
444                .create_pseudo_layout_data(primary);
445            return primary_layout_data.borrow().self_box.clone().into();
446        };
447
448        // It's *very* important that this not borrow the element's main
449        // `InnerLayoutData`. Primary pseudo-elements are processed at the same recursion
450        // level as the main data, so the `BoxSlot` is created sequentially with other
451        // primary pseudo-elements and the element itself. The secondary pseudo-element is
452        // one level deep, so could be happening in parallel with the primary
453        // pseudo-elements or main element layout.
454        let primary_layout_data = self
455            .inner_layout_data()
456            .expect("Should already have element InnerLayoutData here.")
457            .pseudo_layout_data(primary)
458            .expect("Should already have primary pseudo-element InnerLayoutData here");
459        let secondary_layout_data = primary_layout_data
460            .borrow_mut()
461            .create_pseudo_layout_data(secondary);
462        secondary_layout_data.borrow().self_box.clone().into()
463    }
464
465    fn unset_all_boxes(&self) {
466        let mut layout_data = self.ensure_inner_layout_data();
467        *layout_data.self_box.borrow_mut() = None;
468        layout_data.pseudo_boxes.clear();
469
470        // Stylo already takes care of removing all layout data
471        // for DOM descendants of elements with `display: none`.
472    }
473
474    fn unset_all_pseudo_boxes(&self) {
475        self.ensure_inner_layout_data().pseudo_boxes.clear();
476    }
477
478    fn with_each_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase)) {
479        if let Some(inner_layout_data) = self.inner_layout_data() {
480            inner_layout_data.with_each_layout_box_base_including_pseudos(callback);
481        }
482    }
483
484    fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment> {
485        let Some(layout_data) = self.inner_layout_data() else {
486            return vec![];
487        };
488        match pseudo_element {
489            Some(pseudo_element) => layout_data
490                .pseudo_layout_data(pseudo_element)
491                .map(|pseudo_layout_data| pseudo_layout_data.borrow().fragments())
492                .unwrap_or_default(),
493            None => layout_data.fragments(),
494        }
495    }
496
497    fn repair_style(&self, context: &SharedStyleContext) {
498        if let Some(layout_data) = self.inner_layout_data() {
499            layout_data.repair_style(self, context);
500        }
501    }
502
503    fn take_restyle_damage(&self) -> LayoutDamage {
504        let damage = self
505            .style_data()
506            .map(|style_data| std::mem::take(&mut style_data.element_data.borrow_mut().damage))
507            .unwrap_or_else(RestyleDamage::reconstruct);
508        LayoutDamage::from_bits_retain(damage.bits())
509    }
510}