aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout')
-rw-r--r--components/layout/display_list/mod.rs26
-rw-r--r--components/layout/display_list/stacking_context.rs135
-rw-r--r--components/layout/flow/mod.rs2
-rw-r--r--components/layout/flow/root.rs70
-rw-r--r--components/layout/fragment_tree/base_fragment.rs6
-rw-r--r--components/layout/fragment_tree/box_fragment.rs12
-rw-r--r--components/layout/fragment_tree/fragment_tree.rs56
7 files changed, 116 insertions, 191 deletions
diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs
index 186272cda36..d6cbb50e4a1 100644
--- a/components/layout/display_list/mod.rs
+++ b/components/layout/display_list/mod.rs
@@ -153,10 +153,6 @@ pub(crate) struct DisplayListBuilder<'a> {
/// list building functions.
current_clip_chain_id: ClipChainId,
- /// The [OpaqueNode] handle to the node used to paint the page background
- /// if the background was a canvas.
- element_for_canvas_background: OpaqueNode,
-
/// A [LayoutContext] used to get information about the device pixel ratio
/// and get handles to WebRender images.
pub context: &'a LayoutContext<'a>,
@@ -169,6 +165,11 @@ pub(crate) struct DisplayListBuilder<'a> {
/// This data is collected during the traversal of the fragment tree and used
/// to paint the highlight at the very end.
inspector_highlight: Option<InspectorHighlight>,
+
+ /// Whether or not the `<body>` element should be painted. This is false if the root `<html>`
+ /// element inherits the `<body>`'s background to paint the page canvas background.
+ /// See <https://drafts.csswg.org/css-backgrounds/#body-background>.
+ paint_body_background: bool,
}
struct InspectorHighlight {
@@ -218,12 +219,12 @@ impl DisplayList {
current_scroll_node_id: self.compositor_info.root_reference_frame_id,
current_reference_frame_scroll_node_id: self.compositor_info.root_reference_frame_id,
current_clip_chain_id: ClipChainId::INVALID,
- element_for_canvas_background: fragment_tree.canvas_background.from_element,
context,
display_list: self,
inspector_highlight: context
.highlighted_dom_node
.map(InspectorHighlight::for_node),
+ paint_body_background: true,
};
fragment_tree.build_display_list(&mut builder, root_stacking_context);
@@ -999,12 +1000,17 @@ impl<'a> BuilderForBoxFragment<'a> {
}
fn build_background(&mut self, builder: &mut DisplayListBuilder) {
- if self
- .fragment
- .base
- .is_for_node(builder.element_for_canvas_background)
+ let flags = self.fragment.base.flags;
+
+ // The root element's background is painted separately as it might inherit the `<body>`'s
+ // background.
+ if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
+ return;
+ }
+ // If the `<body>` background was inherited by the root element, don't paint it again here.
+ if !builder.paint_body_background &&
+ flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
{
- // This background is already painted for the canvas, don’t paint it again here.
return;
}
diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs
index b044b713260..27fa73a680c 100644
--- a/components/layout/display_list/stacking_context.rs
+++ b/components/layout/display_list/stacking_context.rs
@@ -582,15 +582,42 @@ impl StackingContext {
&self,
builder: &mut DisplayListBuilder,
fragment_tree: &crate::FragmentTree,
- containing_block_rect: &PhysicalRect<Au>,
) {
- let style = if let Some(style) = &fragment_tree.canvas_background.style {
- style
- } else {
- // The root element has `display: none`,
- // or the canvas background is taken from `<body>` which has `display: none`
+ let Some(root_fragment) = fragment_tree.root_fragments.iter().find(|fragment| {
+ fragment
+ .base()
+ .is_some_and(|base| base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT))
+ }) else {
return;
};
+ let root_fragment = match root_fragment {
+ Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
+ _ => return,
+ }
+ .borrow();
+
+ let source_style = {
+ // > For documents whose root element is an HTML HTML element or an XHTML html element
+ // > [HTML]: if the computed value of background-image on the root element is none and its
+ // > background-color is transparent, user agents must instead propagate the computed
+ // > values of the background properties from that element’s first HTML BODY or XHTML body
+ // > child element.
+ if root_fragment.style.background_is_transparent() {
+ let body_fragment = fragment_tree.body_fragment();
+ builder.paint_body_background = body_fragment.is_none();
+ body_fragment
+ .map(|body_fragment| body_fragment.borrow().style.clone())
+ .unwrap_or(root_fragment.style.clone())
+ } else {
+ root_fragment.style.clone()
+ }
+ };
+
+ // This can happen if the root fragment does not have a `<body>` child (either because it is
+ // `display: none` or `display: contents`) or if the `<body>`'s background is transparent.
+ if source_style.background_is_transparent() {
+ return;
+ }
// The painting area is theoretically the infinite 2D plane,
// but we need a rectangle with finite coordinates.
@@ -598,14 +625,15 @@ impl StackingContext {
// If the document is smaller than the viewport (and doesn’t scroll),
// we still want to paint the rest of the viewport.
// If it’s larger, we also want to paint areas reachable after scrolling.
- let mut painting_area = fragment_tree
+ let painting_area = fragment_tree
.initial_containing_block
.union(&fragment_tree.scrollable_overflow)
.to_webrender();
- let background_color = style.resolve_color(&style.get_background().background_color);
+ let background_color =
+ source_style.resolve_color(&source_style.get_background().background_color);
if background_color.alpha > 0.0 {
- let common = builder.common_properties(painting_area, style);
+ let common = builder.common_properties(painting_area, &source_style);
let color = super::rgba(background_color);
builder
.display_list
@@ -613,97 +641,14 @@ impl StackingContext {
.push_rect(&common, painting_area, color)
}
- // `background-color` was comparatively easy,
- // but `background-image` needs a positioning area based on the root element.
- // Let’s find the corresponding fragment.
-
- // The fragment generated by the root element is the first one here, unless…
- let first_if_any = self.contents.first().or_else(|| {
- // There wasn’t any `StackingContextFragment` in the root `StackingContext`,
- // because the root element generates a stacking context. Let’s find that one.
- self.real_stacking_contexts_and_positioned_stacking_containers
- .first()
- .and_then(|first_child_stacking_context| {
- first_child_stacking_context.contents.first()
- })
- });
-
- macro_rules! debug_panic {
- ($msg: expr) => {
- if cfg!(debug_assertions) {
- panic!($msg);
- } else {
- warn!($msg);
- return;
- }
- };
- }
-
- let first_stacking_context_fragment = if let Some(first) = first_if_any {
- first
- } else {
- // This should only happen if the root element has `display: none`
- // TODO(servo#30569) revert to debug_panic!() once underlying bug is fixed
- log::warn!(
- "debug assertion failed! `CanvasBackground::for_root_element` should have returned `style: None`",
- );
- return;
- };
-
- let StackingContextContent::Fragment {
- fragment,
- scroll_node_id,
- containing_block,
- ..
- } = first_stacking_context_fragment
- else {
- debug_panic!("Expected a fragment, not a stacking container");
- };
- let box_fragment = match fragment {
- Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => box_fragment,
- _ => debug_panic!("Expected a box-generated fragment"),
- };
- let box_fragment = &*box_fragment.borrow();
-
- // The `StackingContextFragment` we found is for the root DOM element:
- debug_assert_eq!(
- fragment.tag().map(|tag| tag.node),
- Some(fragment_tree.canvas_background.root_element),
- );
-
- // The root element may have a CSS transform, and we want the canvas’
- // background image to be transformed. To do so, take its `SpatialId`
- // (but not its `ClipId`)
- builder.current_scroll_node_id = *scroll_node_id;
-
- // Now we need express the painting area rectangle in the local coordinate system,
- // which differs from the top-level coordinate system based on…
-
- // Convert the painting area rectangle to the local coordinate system of this `SpatialId`
- if let Some(reference_frame_data) =
- box_fragment.reference_frame_data_if_necessary(containing_block_rect)
- {
- painting_area.min -= reference_frame_data.origin.to_webrender().to_vector();
- if let Some(transformed) = reference_frame_data
- .transform
- .inverse()
- .and_then(|inversed| inversed.outer_transformed_rect(&painting_area.to_rect()))
- {
- painting_area = transformed.to_box2d();
- } else {
- // The desired rect cannot be represented, so skip painting this background-image
- return;
- }
- }
-
let mut fragment_builder = BuilderForBoxFragment::new(
- box_fragment,
- containing_block,
+ &root_fragment,
+ &fragment_tree.initial_containing_block,
false, /* is_hit_test_for_scrollable_overflow */
false, /* is_collapsed_table_borders */
);
let painter = super::background::BackgroundPainter {
- style,
+ style: &source_style,
painting_area_override: Some(painting_area),
positioning_area_override: None,
};
diff --git a/components/layout/flow/mod.rs b/components/layout/flow/mod.rs
index 0c326c4cc6d..e23193f3904 100644
--- a/components/layout/flow/mod.rs
+++ b/components/layout/flow/mod.rs
@@ -52,7 +52,7 @@ pub mod inline;
mod root;
pub(crate) use construct::BlockContainerBuilder;
-pub use root::{BoxTree, CanvasBackground};
+pub use root::BoxTree;
#[derive(Debug, MallocSizeOf)]
pub(crate) struct BlockFormattingContext {
diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs
index e813777b6fe..ec85f3574dc 100644
--- a/components/layout/flow/root.rs
+++ b/components/layout/flow/root.rs
@@ -12,7 +12,7 @@ use script_layout_interface::wrapper_traits::{
};
use script_layout_interface::{LayoutElementType, LayoutNodeType};
use servo_arc::Arc;
-use style::dom::{NodeInfo, OpaqueNode, TNode};
+use style::dom::{NodeInfo, TNode};
use style::properties::ComputedValues;
use style::values::computed::Overflow;
use style_traits::CSSPixel;
@@ -30,7 +30,7 @@ use crate::fragment_tree::FragmentTree;
use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
use crate::replaced::ReplacedContents;
-use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayInside};
+use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside};
use crate::taffy::{TaffyItemBox, TaffyItemBoxInner};
use crate::{DefiniteContainingBlock, PropagatedBoxTreeData};
@@ -40,9 +40,6 @@ pub struct BoxTree {
/// There may be zero if that element has `display: none`.
root: BlockFormattingContext,
- /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>
- canvas_background: CanvasBackground,
-
/// Whether or not the viewport should be sensitive to scrolling input events in two axes
viewport_scroll_sensitivity: AxesScrollSensitivity,
}
@@ -96,7 +93,6 @@ impl BoxTree {
contents,
contains_floats,
},
- canvas_background: CanvasBackground::for_root_element(context, root_element),
// From https://www.w3.org/TR/css-overflow-3/#overflow-propagation:
// > If visible is applied to the viewport, it must be interpreted as auto.
// > If clip is applied to the viewport, it must be interpreted as hidden.
@@ -425,69 +421,7 @@ impl BoxTree {
root_fragments,
scrollable_overflow,
physical_containing_block,
- self.canvas_background.clone(),
self.viewport_scroll_sensitivity,
)
}
}
-
-/// <https://drafts.csswg.org/css-backgrounds/#root-background>
-#[derive(Clone, MallocSizeOf)]
-pub struct CanvasBackground {
- /// DOM node for the root element
- pub root_element: OpaqueNode,
-
- /// The element whose style the canvas takes background properties from (see next field).
- /// This can be the root element (same as the previous field), or the HTML `<body>` element.
- /// See <https://drafts.csswg.org/css-backgrounds/#body-background>
- pub from_element: OpaqueNode,
-
- /// The computed styles to take background properties from.
- #[conditional_malloc_size_of]
- pub style: Option<Arc<ComputedValues>>,
-}
-
-impl CanvasBackground {
- fn for_root_element(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self {
- let root_style = root_element.style(context);
-
- let mut style = root_style;
- let mut from_element = root_element;
-
- // https://drafts.csswg.org/css-backgrounds/#body-background
- // “if the computed value of background-image on the root element is none
- // and its background-color is transparent”
- if style.background_is_transparent() &&
- // “For documents whose root element is an HTML `HTML` element
- // or an XHTML `html` element”
- root_element.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLHtmlElement) &&
- // Don’t try to access styles for an unstyled subtree
- !matches!(style.clone_display().into(), Display::None)
- {
- // “that element’s first HTML `BODY` or XHTML `body` child element”
- if let Some(body) = iter_child_nodes(root_element).find(|child| {
- child.is_element() &&
- child.type_id() ==
- LayoutNodeType::Element(LayoutElementType::HTMLBodyElement)
- }) {
- style = body.style(context);
- from_element = body;
- }
- }
-
- Self {
- root_element: root_element.opaque(),
- from_element: from_element.opaque(),
-
- // “However, if no boxes are generated for the element
- // whose background would be used for the canvas
- // (for example, if the root element has display: none),
- // then the canvas background is transparent.”
- style: if let Display::GeneratingBox(_) = style.clone_display().into() {
- Some(style)
- } else {
- None
- },
- }
- }
-}
diff --git a/components/layout/fragment_tree/base_fragment.rs b/components/layout/fragment_tree/base_fragment.rs
index 0cf6ee511cb..ff5df44c225 100644
--- a/components/layout/fragment_tree/base_fragment.rs
+++ b/components/layout/fragment_tree/base_fragment.rs
@@ -32,10 +32,8 @@ impl BaseFragment {
}
}
- /// Returns true if this fragment is non-anonymous and it is for the given
- /// OpaqueNode, regardless of the pseudo element.
- pub(crate) fn is_for_node(&self, node: OpaqueNode) -> bool {
- self.tag.map(|tag| tag.node == node).unwrap_or(false)
+ pub(crate) fn is_anonymous(&self) -> bool {
+ self.tag.is_none()
}
}
diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs
index 4cd8f278498..596556b296c 100644
--- a/components/layout/fragment_tree/box_fragment.rs
+++ b/components/layout/fragment_tree/box_fragment.rs
@@ -16,7 +16,7 @@ use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::values::specified::box_::DisplayOutside;
-use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment};
+use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, FragmentFlags};
use crate::ArcRefCell;
use crate::display_list::ToWebRender;
use crate::formatting_contexts::Baselines;
@@ -243,6 +243,16 @@ impl BoxFragment {
self.margin + self.border + self.padding
}
+ pub(crate) fn is_root_element(&self) -> bool {
+ self.base.flags.intersects(FragmentFlags::IS_ROOT_ELEMENT)
+ }
+
+ pub(crate) fn is_body_element_of_html_element_root(&self) -> bool {
+ self.base
+ .flags
+ .intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
+ }
+
pub fn print(&self, tree: &mut PrintTree) {
tree.new_level(format!(
"Box\
diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs
index 1499a50dacf..979bd0090fc 100644
--- a/components/layout/fragment_tree/fragment_tree.rs
+++ b/components/layout/fragment_tree/fragment_tree.rs
@@ -11,10 +11,10 @@ use malloc_size_of_derive::MallocSizeOf;
use style::animation::AnimationSetKey;
use webrender_api::units;
-use super::{ContainingBlockManager, Fragment};
+use super::{BoxFragment, ContainingBlockManager, Fragment};
+use crate::ArcRefCell;
use crate::context::LayoutContext;
use crate::display_list::StackingContext;
-use crate::flow::CanvasBackground;
use crate::geom::PhysicalRect;
#[derive(MallocSizeOf)]
@@ -36,9 +36,6 @@ pub struct FragmentTree {
/// The containing block used in the layout of this fragment tree.
pub(crate) initial_containing_block: PhysicalRect<Au>,
- /// <https://drafts.csswg.org/css-backgrounds/#special-backgrounds>
- pub(crate) canvas_background: CanvasBackground,
-
/// Whether or not the viewport is sensitive to scroll input events.
pub viewport_scroll_sensitivity: AxesScrollSensitivity,
}
@@ -49,14 +46,12 @@ impl FragmentTree {
root_fragments: Vec<Fragment>,
scrollable_overflow: PhysicalRect<Au>,
initial_containing_block: PhysicalRect<Au>,
- canvas_background: CanvasBackground,
viewport_scroll_sensitivity: AxesScrollSensitivity,
) -> Self {
let fragment_tree = Self {
root_fragments,
scrollable_overflow,
initial_containing_block,
- canvas_background,
viewport_scroll_sensitivity,
};
@@ -102,11 +97,7 @@ impl FragmentTree {
root_stacking_context: &StackingContext,
) {
// Paint the canvas’ background (if any) before/under everything else
- root_stacking_context.build_canvas_background_display_list(
- builder,
- self,
- &self.initial_containing_block,
- );
+ root_stacking_context.build_canvas_background_display_list(builder, self);
root_stacking_context.build_display_list(builder);
}
@@ -160,4 +151,45 @@ impl FragmentTree {
scroll_area
}
}
+
+ /// Find the `<body>` element's [`Fragment`], if it exists in this [`FragmentTree`].
+ pub(crate) fn body_fragment(&self) -> Option<ArcRefCell<BoxFragment>> {
+ fn find_body(children: &[Fragment]) -> Option<ArcRefCell<BoxFragment>> {
+ children.iter().find_map(|fragment| {
+ match fragment {
+ Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
+ let borrowed_box_fragment = box_fragment.borrow();
+ if borrowed_box_fragment.is_body_element_of_html_element_root() {
+ return Some(box_fragment.clone());
+ }
+
+ // The fragment for the `<body>` element is typically a child of the root (though,
+ // not if it's absolutely positioned), so we need to recurse into the children of
+ // the root to find it.
+ //
+ // Additionally, recurse into any anonymous fragments, as the `<body>` fragment may
+ // have created anonymous parents (for instance by creating an inline formatting context).
+ if borrowed_box_fragment.is_root_element() ||
+ borrowed_box_fragment.base.is_anonymous()
+ {
+ find_body(&borrowed_box_fragment.children)
+ } else {
+ None
+ }
+ },
+ Fragment::Positioning(positioning_context)
+ if positioning_context.borrow().base.is_anonymous() =>
+ {
+ // If the `<body>` element is a `display: inline` then it might be nested inside of a
+ // `PositioningFragment` for the purposes of putting it on the first line of the implied
+ // inline formatting context.
+ find_body(&positioning_context.borrow().children)
+ },
+ _ => None,
+ }
+ })
+ }
+
+ find_body(&self.root_fragments)
+ }
}