mirror of
https://github.com/servo/servo
synced 2026-05-05 06:32:13 +02:00
This change reworks the layout DOM wrappers so that they are simpler and easier to reason about. The main changes here: **Combine layout wrappers into one interface:** - `LayoutNode`/`ThreadSafeLayoutNode` is combined into `LayoutNode`: The idea here is that `LayoutNode` is always thread-safe when used in layout as long as no `unsafe` calls are used. These interfaces only expose what is necessary for layout. - `LayoutElement`/`ThreadSafeLayoutElement` is combined into `LayoutElement`: See above. **Expose two new interfaces to be used *only* with `stylo` and `selectors`:** `DangerousStyleNode` and `DangerousStyleElement`. `stylo` and `selectors` have a different way of ensuring safety that is incompatible with Servo's layout (access all of the DOM tree anywhere, but ensure that writing only happens from a single-thread). These types only implement things like `TElement`, `TNode` and are not intended to be used by layout at all. All traits and implementations are moved to files that are named after the struct or trait inside them, in order to better understand what one is looking at. The main goals here are: - Make it easier to reason about the safe use of the DOM APIs. - Remove the interdependencies between the `stylo` and `selectors` interface implementations and the layout interface. This helps with the first point as well and makes it simpler to know where a method is implemented. - Reduce the amount of code. - Make it possible to eliminate `TrustedNodeAddress` in the future. - Document and bring the method naming up to modern Rust conventions. This is a lot of code changes, but is very well tested by the WPT tests. Unfortunately, it is difficult to make a change like this iteratively. In addition, this new design comes with new documentation at servo/book#225. Testing: This should not change behavior so should be covered by existing WPT tests. Signed-off-by: Martin Robinson <mrobinson@fastmail.fm>
295 lines
10 KiB
Rust
295 lines
10 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::ops::Deref;
|
|
|
|
use app_units::Au;
|
|
use atomic_refcell::AtomicRef;
|
|
use bitflags::bitflags;
|
|
use html5ever::local_name;
|
|
use layout_api::{LayoutElement, LayoutNode, PseudoElementChain, combine_id_with_fragment_type};
|
|
use malloc_size_of::malloc_size_of_is_0;
|
|
use malloc_size_of_derive::MallocSizeOf;
|
|
use script::layout_dom::ServoLayoutNode;
|
|
use servo_arc::Arc as ServoArc;
|
|
use style::dom::OpaqueNode;
|
|
use style::properties::ComputedValues;
|
|
use style::selector_parser::PseudoElement;
|
|
|
|
use crate::SharedStyle;
|
|
use crate::dom_traversal::NodeAndStyleInfo;
|
|
use crate::geom::PhysicalRect;
|
|
|
|
pub(crate) enum BaseFragmentStyleRef<'a> {
|
|
Owned(&'a ServoArc<ComputedValues>),
|
|
Shared(AtomicRef<'a, ServoArc<ComputedValues>>),
|
|
}
|
|
|
|
impl<'a> Deref for BaseFragmentStyleRef<'a> {
|
|
type Target = ServoArc<ComputedValues>;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
match self {
|
|
BaseFragmentStyleRef::Owned(style) => style,
|
|
BaseFragmentStyleRef::Shared(style_ref) => style_ref.deref(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, MallocSizeOf)]
|
|
pub(crate) enum BaseFragmentStyle {
|
|
Owned(ServoArc<ComputedValues>),
|
|
Shared(SharedStyle),
|
|
}
|
|
|
|
impl From<ServoArc<ComputedValues>> for BaseFragmentStyle {
|
|
fn from(style: ServoArc<ComputedValues>) -> Self {
|
|
BaseFragmentStyle::Owned(style)
|
|
}
|
|
}
|
|
|
|
impl From<SharedStyle> for BaseFragmentStyle {
|
|
fn from(style: SharedStyle) -> Self {
|
|
BaseFragmentStyle::Shared(style)
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for BaseFragmentStyle {
|
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
BaseFragmentStyle::Owned(..) => write!(formatter, "BaseFragmentStyle::Owned"),
|
|
BaseFragmentStyle::Shared(..) => write!(formatter, "BaseFragmentStyle::Shared"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, MallocSizeOf)]
|
|
pub(crate) enum FragmentStatus {
|
|
/// This is a brand new fragment.
|
|
#[default]
|
|
New,
|
|
/// The style of the fragment has changed.
|
|
StyleChanged,
|
|
/// The fragment hasn't changed.
|
|
Clean,
|
|
}
|
|
|
|
/// This data structure stores fields that are common to all non-base
|
|
/// Fragment types and should generally be the first member of all
|
|
/// concrete fragments.
|
|
#[derive(Clone, Debug, MallocSizeOf)]
|
|
pub(crate) struct BaseFragment {
|
|
/// A tag which identifies the DOM node and pseudo element of this
|
|
/// Fragment's content. If this fragment is for an anonymous box,
|
|
/// the tag will be None.
|
|
pub tag: Option<Tag>,
|
|
|
|
/// Flags which various information about this fragment used during
|
|
/// layout.
|
|
pub flags: FragmentFlags,
|
|
|
|
/// The style for this [`BaseFragment`]. Depending on the fragment type this is either
|
|
/// a shared or non-shared style.
|
|
pub style: BaseFragmentStyle,
|
|
|
|
/// The content rect of this fragment in the parent fragment's content rectangle. This
|
|
/// does not include padding, border, or margin -- it only includes content. This is
|
|
/// relative to the parent containing block.
|
|
pub rect: PhysicalRect<Au>,
|
|
|
|
/// A [`FragmentStatus`] used to track fragment reuse when collecting reflow statistics.
|
|
pub status: FragmentStatus,
|
|
}
|
|
|
|
impl BaseFragment {
|
|
pub(crate) fn new(
|
|
base_fragment_info: BaseFragmentInfo,
|
|
style: BaseFragmentStyle,
|
|
rect: PhysicalRect<Au>,
|
|
) -> Self {
|
|
Self {
|
|
tag: base_fragment_info.tag,
|
|
flags: base_fragment_info.flags,
|
|
style,
|
|
rect,
|
|
status: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn is_anonymous(&self) -> bool {
|
|
self.tag.is_none()
|
|
}
|
|
|
|
pub(crate) fn repair_style(&mut self, style: &ServoArc<ComputedValues>) {
|
|
self.style = style.clone().into();
|
|
self.status = FragmentStatus::StyleChanged;
|
|
}
|
|
|
|
pub(crate) fn style<'a>(&'a self) -> BaseFragmentStyleRef<'a> {
|
|
match &self.style {
|
|
BaseFragmentStyle::Owned(computed_values) => {
|
|
BaseFragmentStyleRef::Owned(computed_values)
|
|
},
|
|
BaseFragmentStyle::Shared(shared_style) => {
|
|
BaseFragmentStyleRef::Shared(shared_style.borrow())
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Information necessary to construct a new BaseFragment.
|
|
#[derive(Clone, Copy, Debug, MallocSizeOf)]
|
|
pub(crate) struct BaseFragmentInfo {
|
|
/// The tag to use for the new BaseFragment, if it is not an anonymous Fragment.
|
|
pub tag: Option<Tag>,
|
|
|
|
/// The flags to use for the new BaseFragment.
|
|
pub flags: FragmentFlags,
|
|
}
|
|
|
|
impl BaseFragmentInfo {
|
|
pub(crate) fn anonymous() -> Self {
|
|
Self {
|
|
tag: None,
|
|
flags: FragmentFlags::empty(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn new_for_testing(id: usize) -> Self {
|
|
Self {
|
|
tag: Some(Tag {
|
|
node: OpaqueNode(id),
|
|
pseudo_element_chain: Default::default(),
|
|
}),
|
|
flags: FragmentFlags::empty(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn is_anonymous(&self) -> bool {
|
|
self.tag.is_none()
|
|
}
|
|
}
|
|
|
|
impl From<&NodeAndStyleInfo<'_>> for BaseFragmentInfo {
|
|
fn from(info: &NodeAndStyleInfo) -> Self {
|
|
info.node.into()
|
|
}
|
|
}
|
|
|
|
impl From<ServoLayoutNode<'_>> for BaseFragmentInfo {
|
|
fn from(node: ServoLayoutNode) -> Self {
|
|
let pseudo_element_chain = node.pseudo_element_chain();
|
|
let mut flags = FragmentFlags::empty();
|
|
|
|
// Anonymous boxes should not have a tag, because they should not take part in hit testing.
|
|
//
|
|
// TODO(mrobinson): It seems that anonymous boxes should take part in hit testing in some
|
|
// cases, but currently this means that the order of hit test results isn't as expected for
|
|
// some WPT tests. This needs more investigation.
|
|
if matches!(
|
|
pseudo_element_chain.innermost(),
|
|
Some(PseudoElement::ServoAnonymousBox) |
|
|
Some(PseudoElement::ServoAnonymousTable) |
|
|
Some(PseudoElement::ServoAnonymousTableCell) |
|
|
Some(PseudoElement::ServoAnonymousTableRow)
|
|
) {
|
|
return Self::anonymous();
|
|
}
|
|
|
|
if let Some(element) = node.as_html_element() {
|
|
if element.is_body_element_of_html_element_root() {
|
|
flags.insert(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT);
|
|
}
|
|
|
|
match element.local_name() {
|
|
&local_name!("br") => {
|
|
flags.insert(FragmentFlags::IS_BR_ELEMENT);
|
|
},
|
|
&local_name!("table") | &local_name!("th") | &local_name!("td") => {
|
|
flags.insert(FragmentFlags::IS_TABLE_TH_OR_TD_ELEMENT);
|
|
},
|
|
_ => {},
|
|
}
|
|
|
|
if element.is_root() {
|
|
flags.insert(FragmentFlags::IS_ROOT_ELEMENT);
|
|
}
|
|
};
|
|
|
|
Self {
|
|
tag: Some(node.into()),
|
|
flags,
|
|
}
|
|
}
|
|
}
|
|
|
|
bitflags! {
|
|
/// Flags used to track various information about a DOM node during layout.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub(crate) struct FragmentFlags: u16 {
|
|
/// Whether or not the node that created this fragment is a `<body>` element on an HTML document.
|
|
const IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT = 1 << 0;
|
|
/// Whether or not the node that created this Fragment is a `<br>` element.
|
|
const IS_BR_ELEMENT = 1 << 1;
|
|
/// Whether or not the node that created this Fragment is a widget. Widgets behave similarly to
|
|
/// replaced elements, e.g. they are atomic when inline-level, and their automatic inline size
|
|
/// doesn't stretch when block-level.
|
|
/// <https://drafts.csswg.org/css-ui/#widget>
|
|
const IS_WIDGET = 1 << 2;
|
|
/// Whether or not this Fragment is a flex item or a grid item.
|
|
const IS_FLEX_OR_GRID_ITEM = 1 << 3;
|
|
/// Whether or not this Fragment was created to contain a replaced element or is
|
|
/// a replaced element.
|
|
const IS_REPLACED = 1 << 4;
|
|
/// Whether or not the node that created was a `<table>`, `<th>` or
|
|
/// `<td>` element. Note that this does *not* include elements with
|
|
/// `display: table` or `display: table-cell`.
|
|
const IS_TABLE_TH_OR_TD_ELEMENT = 1 << 5;
|
|
/// Whether or not this Fragment was created to contain a list item marker
|
|
/// with a used value of `list-style-position: outside`.
|
|
const IS_OUTSIDE_LIST_ITEM_MARKER = 1 << 6;
|
|
/// Avoid painting the borders, backgrounds, and drop shadow for this fragment, this is used
|
|
/// for empty table cells when 'empty-cells' is 'hide' and also table wrappers. This flag
|
|
/// doesn't avoid hit-testing nor does it prevent the painting outlines.
|
|
const DO_NOT_PAINT = 1 << 7;
|
|
/// Whether or not the size of this fragment depends on the block size of its container
|
|
/// and the fragment can be a flex item. This flag is used to cache items during flex
|
|
/// layout.
|
|
const SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM = 1 << 8;
|
|
/// Whether or not the node that created this fragment is the root element.
|
|
const IS_ROOT_ELEMENT = 1 << 9;
|
|
/// If element has propagated the overflow value to viewport.
|
|
const PROPAGATED_OVERFLOW_TO_VIEWPORT = 1 << 10;
|
|
/// Whether or not this is a table cell that is part of a collapsed row or column.
|
|
/// In that case it should not be painted.
|
|
const IS_COLLAPSED = 1 << 11;
|
|
|
|
}
|
|
}
|
|
|
|
malloc_size_of_is_0!(FragmentFlags);
|
|
|
|
/// A data structure used to hold DOM and pseudo-element information about
|
|
/// a particular layout object.
|
|
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
|
pub(crate) struct Tag {
|
|
pub(crate) node: OpaqueNode,
|
|
pub(crate) pseudo_element_chain: PseudoElementChain,
|
|
}
|
|
|
|
impl Tag {
|
|
pub(crate) fn to_display_list_fragment_id(self) -> u64 {
|
|
combine_id_with_fragment_type(self.node.id(), self.pseudo_element_chain.primary.into())
|
|
}
|
|
}
|
|
|
|
impl From<ServoLayoutNode<'_>> for Tag {
|
|
fn from(node: ServoLayoutNode<'_>) -> Self {
|
|
Self {
|
|
node: node.opaque(),
|
|
pseudo_element_chain: node.pseudo_element_chain(),
|
|
}
|
|
}
|
|
}
|