mirror of
https://github.com/servo/servo
synced 2026-05-05 06:32:13 +02:00
Previously it was only possible to set width and height of SVG elements in pixels. These changes allow parsing other units like em using a CSS parser in SVGSVGElement and resolving them to computed values in the layout code. Testing: Tested manually Fixes: - --------- Signed-off-by: Rodion Borovyk <rodion.borovyk@gmail.com>
502 lines
19 KiB
Rust
502 lines
19 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||
|
||
use std::marker::PhantomData;
|
||
|
||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||
use base::id::{BrowsingContextId, PipelineId};
|
||
use html5ever::{local_name, ns};
|
||
use layout_api::wrapper_traits::{LayoutDataTrait, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||
use layout_api::{
|
||
GenericLayoutDataTrait, LayoutDamage, LayoutElementType,
|
||
LayoutNodeType as ScriptLayoutNodeType, SVGElementData,
|
||
};
|
||
use malloc_size_of_derive::MallocSizeOf;
|
||
use net_traits::image_cache::Image;
|
||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||
use servo_arc::Arc as ServoArc;
|
||
use smallvec::SmallVec;
|
||
use style::context::SharedStyleContext;
|
||
use style::properties::ComputedValues;
|
||
use style::selector_parser::{PseudoElement, RestyleDamage};
|
||
|
||
use crate::cell::{ArcRefCell, WeakRefCell};
|
||
use crate::flexbox::FlexLevelBox;
|
||
use crate::flow::BlockLevelBox;
|
||
use crate::flow::inline::{InlineItem, SharedInlineStyles, WeakInlineItem};
|
||
use crate::fragment_tree::Fragment;
|
||
use crate::geom::PhysicalSize;
|
||
use crate::layout_box_base::LayoutBoxBase;
|
||
use crate::replaced::CanvasInfo;
|
||
use crate::table::{TableLevelBox, WeakTableLevelBox};
|
||
use crate::taffy::TaffyItemBox;
|
||
|
||
#[derive(MallocSizeOf)]
|
||
pub struct PseudoLayoutData {
|
||
pseudo: PseudoElement,
|
||
data: ArcRefCell<InnerDOMLayoutData>,
|
||
}
|
||
|
||
/// The data that is stored in each DOM node that is used by layout.
|
||
#[derive(Default, MallocSizeOf)]
|
||
pub struct InnerDOMLayoutData {
|
||
pub(super) self_box: ArcRefCell<Option<LayoutBox>>,
|
||
pub(super) pseudo_boxes: SmallVec<[PseudoLayoutData; 2]>,
|
||
}
|
||
|
||
impl InnerDOMLayoutData {
|
||
fn pseudo_layout_data(
|
||
&self,
|
||
pseudo_element: PseudoElement,
|
||
) -> Option<ArcRefCell<InnerDOMLayoutData>> {
|
||
for pseudo_layout_data in self.pseudo_boxes.iter() {
|
||
if pseudo_element == pseudo_layout_data.pseudo {
|
||
return Some(pseudo_layout_data.data.clone());
|
||
}
|
||
}
|
||
None
|
||
}
|
||
|
||
fn create_pseudo_layout_data(
|
||
&mut self,
|
||
pseudo_element: PseudoElement,
|
||
) -> ArcRefCell<InnerDOMLayoutData> {
|
||
let data: ArcRefCell<InnerDOMLayoutData> = Default::default();
|
||
self.pseudo_boxes.push(PseudoLayoutData {
|
||
pseudo: pseudo_element,
|
||
data: data.clone(),
|
||
});
|
||
data
|
||
}
|
||
|
||
fn fragments(&self) -> Vec<Fragment> {
|
||
self.self_box
|
||
.borrow()
|
||
.as_ref()
|
||
.and_then(|layout_box| layout_box.with_base(LayoutBoxBase::fragments))
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
fn repair_style(&self, node: &ServoThreadSafeLayoutNode, context: &SharedStyleContext) {
|
||
if let Some(layout_object) = &*self.self_box.borrow() {
|
||
layout_object.repair_style(context, node, &node.style(context));
|
||
}
|
||
|
||
for pseudo_layout_data in self.pseudo_boxes.iter() {
|
||
let Some(node_with_pseudo) = node.with_pseudo(pseudo_layout_data.pseudo) else {
|
||
continue;
|
||
};
|
||
pseudo_layout_data
|
||
.data
|
||
.borrow()
|
||
.repair_style(&node_with_pseudo, context);
|
||
}
|
||
}
|
||
|
||
fn with_layout_box_base(&self, callback: impl Fn(&LayoutBoxBase)) {
|
||
if let Some(data) = self.self_box.borrow().as_ref() {
|
||
data.with_base(callback);
|
||
}
|
||
}
|
||
|
||
fn with_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase)) {
|
||
self.with_layout_box_base(&callback);
|
||
for pseudo_layout_data in self.pseudo_boxes.iter() {
|
||
pseudo_layout_data
|
||
.data
|
||
.borrow()
|
||
.with_layout_box_base(&callback);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A box that is stored in one of the `DOMLayoutData` slots.
|
||
#[derive(Debug, MallocSizeOf)]
|
||
pub(super) enum LayoutBox {
|
||
DisplayContents(SharedInlineStyles),
|
||
BlockLevel(ArcRefCell<BlockLevelBox>),
|
||
InlineLevel(InlineItem),
|
||
FlexLevel(ArcRefCell<FlexLevelBox>),
|
||
TableLevelBox(TableLevelBox),
|
||
TaffyItemBox(ArcRefCell<TaffyItemBox>),
|
||
}
|
||
|
||
impl LayoutBox {
|
||
pub(crate) fn with_base<T>(&self, callback: impl FnOnce(&LayoutBoxBase) -> T) -> Option<T> {
|
||
Some(match self {
|
||
LayoutBox::DisplayContents(..) => return None,
|
||
LayoutBox::BlockLevel(block_level_box) => block_level_box.borrow().with_base(callback),
|
||
LayoutBox::InlineLevel(inline_item) => inline_item.with_base(callback),
|
||
LayoutBox::FlexLevel(flex_level_box) => flex_level_box.borrow().with_base(callback),
|
||
LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box.borrow().with_base(callback),
|
||
LayoutBox::TableLevelBox(table_box) => table_box.with_base(callback),
|
||
})
|
||
}
|
||
|
||
pub(crate) fn with_base_mut<T>(
|
||
&mut self,
|
||
callback: impl FnOnce(&mut LayoutBoxBase) -> T,
|
||
) -> Option<T> {
|
||
Some(match self {
|
||
LayoutBox::DisplayContents(..) => return None,
|
||
LayoutBox::BlockLevel(block_level_box) => {
|
||
block_level_box.borrow_mut().with_base_mut(callback)
|
||
},
|
||
LayoutBox::InlineLevel(inline_item) => inline_item.with_base_mut(callback),
|
||
LayoutBox::FlexLevel(flex_level_box) => {
|
||
flex_level_box.borrow_mut().with_base_mut(callback)
|
||
},
|
||
LayoutBox::TaffyItemBox(taffy_item_box) => {
|
||
taffy_item_box.borrow_mut().with_base_mut(callback)
|
||
},
|
||
LayoutBox::TableLevelBox(table_box) => table_box.with_base_mut(callback),
|
||
})
|
||
}
|
||
|
||
fn repair_style(
|
||
&self,
|
||
context: &SharedStyleContext,
|
||
node: &ServoThreadSafeLayoutNode,
|
||
new_style: &ServoArc<ComputedValues>,
|
||
) {
|
||
match self {
|
||
LayoutBox::DisplayContents(inline_shared_styles) => {
|
||
*inline_shared_styles.style.borrow_mut() = new_style.clone();
|
||
*inline_shared_styles.selected.borrow_mut() = node.selected_style();
|
||
},
|
||
LayoutBox::BlockLevel(block_level_box) => {
|
||
block_level_box
|
||
.borrow_mut()
|
||
.repair_style(context, node, new_style);
|
||
},
|
||
LayoutBox::InlineLevel(inline_item) => {
|
||
inline_item.repair_style(context, node, new_style);
|
||
},
|
||
LayoutBox::FlexLevel(flex_level_box) => flex_level_box
|
||
.borrow_mut()
|
||
.repair_style(context, node, new_style),
|
||
LayoutBox::TableLevelBox(table_level_box) => {
|
||
table_level_box.repair_style(context, node, new_style)
|
||
},
|
||
LayoutBox::TaffyItemBox(taffy_item_box) => taffy_item_box
|
||
.borrow_mut()
|
||
.repair_style(context, node, new_style),
|
||
}
|
||
}
|
||
|
||
fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
|
||
match self {
|
||
Self::DisplayContents(_) => {
|
||
// This box can't have children, its contents get reparented to its parent.
|
||
// Therefore, no need to do anything.
|
||
},
|
||
Self::BlockLevel(block_level_box) => {
|
||
block_level_box.borrow().attached_to_tree(layout_box)
|
||
},
|
||
Self::InlineLevel(inline_item) => inline_item.attached_to_tree(layout_box),
|
||
Self::FlexLevel(flex_level_box) => flex_level_box.borrow().attached_to_tree(layout_box),
|
||
Self::TableLevelBox(table_level_box) => table_level_box.attached_to_tree(layout_box),
|
||
Self::TaffyItemBox(taffy_item_box) => {
|
||
taffy_item_box.borrow().attached_to_tree(layout_box)
|
||
},
|
||
}
|
||
}
|
||
|
||
fn downgrade(&self) -> WeakLayoutBox {
|
||
match self {
|
||
Self::DisplayContents(inline_shared_styles) => {
|
||
WeakLayoutBox::DisplayContents(inline_shared_styles.clone())
|
||
},
|
||
Self::BlockLevel(block_level_box) => {
|
||
WeakLayoutBox::BlockLevel(block_level_box.downgrade())
|
||
},
|
||
Self::InlineLevel(inline_item) => WeakLayoutBox::InlineLevel(inline_item.downgrade()),
|
||
Self::FlexLevel(flex_level_box) => WeakLayoutBox::FlexLevel(flex_level_box.downgrade()),
|
||
Self::TableLevelBox(table_level_box) => {
|
||
WeakLayoutBox::TableLevelBox(table_level_box.downgrade())
|
||
},
|
||
Self::TaffyItemBox(taffy_item_box) => {
|
||
WeakLayoutBox::TaffyItemBox(taffy_item_box.downgrade())
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, Debug, MallocSizeOf)]
|
||
pub(super) enum WeakLayoutBox {
|
||
DisplayContents(SharedInlineStyles),
|
||
BlockLevel(WeakRefCell<BlockLevelBox>),
|
||
InlineLevel(WeakInlineItem),
|
||
FlexLevel(WeakRefCell<FlexLevelBox>),
|
||
TableLevelBox(WeakTableLevelBox),
|
||
TaffyItemBox(WeakRefCell<TaffyItemBox>),
|
||
}
|
||
|
||
impl WeakLayoutBox {
|
||
pub(crate) fn upgrade(&self) -> Option<LayoutBox> {
|
||
Some(match self {
|
||
Self::DisplayContents(inline_shared_styles) => {
|
||
LayoutBox::DisplayContents(inline_shared_styles.clone())
|
||
},
|
||
Self::BlockLevel(block_level_box) => LayoutBox::BlockLevel(block_level_box.upgrade()?),
|
||
Self::InlineLevel(inline_item) => LayoutBox::InlineLevel(inline_item.upgrade()?),
|
||
Self::FlexLevel(flex_level_box) => LayoutBox::FlexLevel(flex_level_box.upgrade()?),
|
||
Self::TableLevelBox(table_level_box) => {
|
||
LayoutBox::TableLevelBox(table_level_box.upgrade()?)
|
||
},
|
||
Self::TaffyItemBox(taffy_item_box) => {
|
||
LayoutBox::TaffyItemBox(taffy_item_box.upgrade()?)
|
||
},
|
||
})
|
||
}
|
||
}
|
||
|
||
/// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data
|
||
/// structure interior mutability, as we will need to mutate the layout data of
|
||
/// non-mutable DOM nodes.
|
||
#[derive(Default, MallocSizeOf)]
|
||
pub struct DOMLayoutData(AtomicRefCell<InnerDOMLayoutData>);
|
||
|
||
// The implementation of this trait allows the data to be stored in the DOM.
|
||
impl LayoutDataTrait for DOMLayoutData {}
|
||
impl GenericLayoutDataTrait for DOMLayoutData {
|
||
fn as_any(&self) -> &dyn std::any::Any {
|
||
self
|
||
}
|
||
}
|
||
|
||
pub struct BoxSlot<'dom> {
|
||
pub(crate) slot: ArcRefCell<Option<LayoutBox>>,
|
||
pub(crate) marker: PhantomData<&'dom ()>,
|
||
}
|
||
|
||
impl From<ArcRefCell<Option<LayoutBox>>> for BoxSlot<'_> {
|
||
fn from(slot: ArcRefCell<Option<LayoutBox>>) -> Self {
|
||
Self {
|
||
slot,
|
||
marker: PhantomData,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A mutable reference to a `LayoutBox` stored in a DOM element.
|
||
impl BoxSlot<'_> {
|
||
pub(crate) fn set(self, layout_box: LayoutBox) {
|
||
layout_box.attached_to_tree(layout_box.downgrade());
|
||
*self.slot.borrow_mut() = Some(layout_box);
|
||
}
|
||
|
||
pub(crate) fn take_layout_box_if_undamaged(&self, damage: LayoutDamage) -> Option<LayoutBox> {
|
||
if damage.has_box_damage() {
|
||
return None;
|
||
}
|
||
self.slot.borrow_mut().take()
|
||
}
|
||
}
|
||
|
||
impl Drop for BoxSlot<'_> {
|
||
fn drop(&mut self) {
|
||
if !std::thread::panicking() {
|
||
assert!(self.slot.borrow().is_some(), "failed to set a layout box");
|
||
}
|
||
}
|
||
}
|
||
|
||
pub(crate) trait NodeExt<'dom> {
|
||
/// Returns the image if it’s loaded, and its size in image pixels
|
||
/// adjusted for `image_density`.
|
||
fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)>;
|
||
fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)>;
|
||
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)>;
|
||
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)>;
|
||
fn as_svg(&self) -> Option<SVGElementData<'dom>>;
|
||
fn as_typeless_object_with_data_attribute(&self) -> Option<String>;
|
||
|
||
fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>;
|
||
fn inner_layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>;
|
||
fn box_slot(&self) -> BoxSlot<'dom>;
|
||
|
||
/// Remove boxes for the element itself, and all of its pseudo-element boxes.
|
||
fn unset_all_boxes(&self);
|
||
|
||
/// Remove all pseudo-element boxes for this element.
|
||
fn unset_all_pseudo_boxes(&self);
|
||
|
||
fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment>;
|
||
fn with_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase));
|
||
|
||
fn repair_style(&self, context: &SharedStyleContext);
|
||
fn take_restyle_damage(&self) -> LayoutDamage;
|
||
}
|
||
|
||
impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
||
fn as_image(&self) -> Option<(Option<Image>, PhysicalSize<f64>)> {
|
||
let (resource, metadata) = self.image_data()?;
|
||
let width = metadata.map(|metadata| metadata.width).unwrap_or_default();
|
||
let height = metadata.map(|metadata| metadata.height).unwrap_or_default();
|
||
let (mut width, mut height) = (width as f64, height as f64);
|
||
if let Some(density) = self.image_density().filter(|density| *density != 1.) {
|
||
width /= density;
|
||
height /= density;
|
||
}
|
||
Some((resource, PhysicalSize::new(width, height)))
|
||
}
|
||
|
||
fn as_svg(&self) -> Option<SVGElementData<'dom>> {
|
||
self.svg_data()
|
||
}
|
||
|
||
fn as_video(&self) -> Option<(Option<webrender_api::ImageKey>, Option<PhysicalSize<f64>>)> {
|
||
let data = self.media_data()?;
|
||
let natural_size = if let Some(frame) = data.current_frame {
|
||
Some(PhysicalSize::new(frame.width.into(), frame.height.into()))
|
||
} else {
|
||
data.metadata
|
||
.map(|meta| PhysicalSize::new(meta.width.into(), meta.height.into()))
|
||
};
|
||
Some((
|
||
data.current_frame.map(|frame| frame.image_key),
|
||
natural_size,
|
||
))
|
||
}
|
||
|
||
fn as_canvas(&self) -> Option<(CanvasInfo, PhysicalSize<f64>)> {
|
||
let canvas_data = self.canvas_data()?;
|
||
let source = canvas_data.image_key;
|
||
Some((
|
||
CanvasInfo { source },
|
||
PhysicalSize::new(canvas_data.width.into(), canvas_data.height.into()),
|
||
))
|
||
}
|
||
|
||
fn as_iframe(&self) -> Option<(PipelineId, BrowsingContextId)> {
|
||
match (self.iframe_pipeline_id(), self.iframe_browsing_context_id()) {
|
||
(Some(pipeline_id), Some(browsing_context_id)) => {
|
||
Some((pipeline_id, browsing_context_id))
|
||
},
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
fn as_typeless_object_with_data_attribute(&self) -> Option<String> {
|
||
if self.type_id() !=
|
||
Some(ScriptLayoutNodeType::Element(
|
||
LayoutElementType::HTMLObjectElement,
|
||
))
|
||
{
|
||
return None;
|
||
}
|
||
|
||
// TODO: This is the what the legacy layout system did, but really if Servo
|
||
// supports any `<object>` that's an image, it should support those with URLs
|
||
// and `type` attributes with image mime types.
|
||
let element = self.as_element()?;
|
||
if element.get_attr(&ns!(), &local_name!("type")).is_some() {
|
||
return None;
|
||
}
|
||
element
|
||
.get_attr(&ns!(), &local_name!("data"))
|
||
.map(|string| string.to_owned())
|
||
}
|
||
|
||
fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData> {
|
||
if self.layout_data().is_none() {
|
||
self.initialize_layout_data::<DOMLayoutData>();
|
||
}
|
||
self.layout_data()
|
||
.unwrap()
|
||
.as_any()
|
||
.downcast_ref::<DOMLayoutData>()
|
||
.unwrap()
|
||
.0
|
||
.borrow_mut()
|
||
}
|
||
|
||
fn inner_layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> {
|
||
self.layout_data().map(|data| {
|
||
data.as_any()
|
||
.downcast_ref::<DOMLayoutData>()
|
||
.unwrap()
|
||
.0
|
||
.borrow()
|
||
})
|
||
}
|
||
|
||
fn box_slot(&self) -> BoxSlot<'dom> {
|
||
let pseudo_element_chain = self.pseudo_element_chain();
|
||
let Some(primary) = pseudo_element_chain.primary else {
|
||
return self.ensure_inner_layout_data().self_box.clone().into();
|
||
};
|
||
|
||
let Some(secondary) = pseudo_element_chain.secondary else {
|
||
let primary_layout_data = self
|
||
.ensure_inner_layout_data()
|
||
.create_pseudo_layout_data(primary);
|
||
return primary_layout_data.borrow().self_box.clone().into();
|
||
};
|
||
|
||
// It's *very* important that this not borrow the element's main
|
||
// `InnerLayoutData`. Primary pseudo-elements are processed at the same recursion
|
||
// level as the main data, so the `BoxSlot` is created sequentially with other
|
||
// primary pseudo-elements and the element itself. The secondary pseudo-element is
|
||
// one level deep, so could be happening in parallel with the primary
|
||
// pseudo-elements or main element layout.
|
||
let primary_layout_data = self
|
||
.inner_layout_data()
|
||
.expect("Should already have element InnerLayoutData here.")
|
||
.pseudo_layout_data(primary)
|
||
.expect("Should already have primary pseudo-element InnerLayoutData here");
|
||
let secondary_layout_data = primary_layout_data
|
||
.borrow_mut()
|
||
.create_pseudo_layout_data(secondary);
|
||
secondary_layout_data.borrow().self_box.clone().into()
|
||
}
|
||
|
||
fn unset_all_boxes(&self) {
|
||
let mut layout_data = self.ensure_inner_layout_data();
|
||
*layout_data.self_box.borrow_mut() = None;
|
||
layout_data.pseudo_boxes.clear();
|
||
|
||
// Stylo already takes care of removing all layout data
|
||
// for DOM descendants of elements with `display: none`.
|
||
}
|
||
|
||
fn unset_all_pseudo_boxes(&self) {
|
||
self.ensure_inner_layout_data().pseudo_boxes.clear();
|
||
}
|
||
|
||
fn with_layout_box_base_including_pseudos(&self, callback: impl Fn(&LayoutBoxBase)) {
|
||
if let Some(inner_layout_data) = self.inner_layout_data() {
|
||
inner_layout_data.with_layout_box_base_including_pseudos(callback);
|
||
}
|
||
}
|
||
|
||
fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment> {
|
||
let Some(layout_data) = self.inner_layout_data() else {
|
||
return vec![];
|
||
};
|
||
match pseudo_element {
|
||
Some(pseudo_element) => layout_data
|
||
.pseudo_layout_data(pseudo_element)
|
||
.map(|pseudo_layout_data| pseudo_layout_data.borrow().fragments())
|
||
.unwrap_or_default(),
|
||
None => layout_data.fragments(),
|
||
}
|
||
}
|
||
|
||
fn repair_style(&self, context: &SharedStyleContext) {
|
||
if let Some(layout_data) = self.inner_layout_data() {
|
||
layout_data.repair_style(self, context);
|
||
}
|
||
}
|
||
|
||
fn take_restyle_damage(&self) -> LayoutDamage {
|
||
let damage = self
|
||
.style_data()
|
||
.map(|style_data| std::mem::take(&mut style_data.element_data.borrow_mut().damage))
|
||
.unwrap_or_else(RestyleDamage::reconstruct);
|
||
LayoutDamage::from_bits_retain(damage.bits())
|
||
}
|
||
}
|