Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

layout_2020: Properly handle HTML element background #26121

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Some generated files are not rendered by default. Learn more.

@@ -15,6 +15,7 @@ doctest = false
[dependencies]
app_units = "0.7"
atomic_refcell = "0.1.6"
bitflags = "1.0"
canvas_traits = {path = "../canvas_traits"}
cssparser = "0.27"
embedder_traits = {path = "../embedder_traits"}
@@ -2,10 +2,13 @@
* 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 crate::dom_traversal::NodeFlags;
use crate::replaced::IntrinsicSizes;
use euclid::{Size2D, Vector2D};
use servo_arc::Arc as ServoArc;
use style::computed_values::background_clip::single_value::T as Clip;
use style::computed_values::background_origin::single_value::T as Origin;
use style::properties::ComputedValues;
use style::values::computed::background::BackgroundSize as Size;
use style::values::computed::{Length, LengthPercentage};
use style::values::specified::background::BackgroundRepeat as RepeatXY;
@@ -34,17 +37,50 @@ fn get_cyclic<T>(values: &[T], layer_index: usize) -> &T {
pub(super) fn painting_area<'a>(
fragment_builder: &'a super::BuilderForBoxFragment,
builder: &mut super::DisplayListBuilder,
background_style: &ServoArc<ComputedValues>,
layer_index: usize,
) -> (&'a units::LayoutRect, wr::CommonItemProperties) {
let fb = fragment_builder;
let b = fb.fragment.style.get_background();
let (painting_area, clip) = match get_cyclic(&b.background_clip.0, layer_index) {
Clip::ContentBox => (fb.content_rect(), fb.content_edge_clip(builder)),
Clip::PaddingBox => (fb.padding_rect(), fb.padding_edge_clip(builder)),
Clip::BorderBox => (&fb.border_rect, fb.border_edge_clip(builder)),
) -> (units::LayoutRect, wr::CommonItemProperties) {
// https://drafts.csswg.org/css-backgrounds/#special-backgrounds
// The spec says that the for the root element, the painting area should be an infinite
// surface area over which the document is rendered. We simply paint the union of the
// scrollable overflow of the element and the viewport, which is a decent simulation.
let (painting_area, clip) = if fragment_builder
.fragment
.flags
.contains(NodeFlags::IS_HTML_ELEMENT)
{
let containing_block = &fragment_builder.containing_block;
let scrollable_overflow = fragment_builder
.fragment
.scrollable_overflow(containing_block);
let max_point = units::LayoutSize::new(
scrollable_overflow
.max_x()
.max(containing_block.max_x())
.px(),
scrollable_overflow
.max_y()
.max(containing_block.max_y())
.px(),
);
(
units::LayoutRect::new(units::LayoutPoint::zero(), max_point),
None,
)
} else {
let fb = fragment_builder;
match get_cyclic(
&background_style.get_background().background_clip.0,
layer_index,
) {
Clip::ContentBox => (*fb.content_rect(), fb.content_edge_clip(builder)),
Clip::PaddingBox => (*fb.padding_rect(), fb.padding_edge_clip(builder)),
Clip::BorderBox => (fb.border_rect, fb.border_edge_clip(builder)),
}
};

// The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`:
let mut common = builder.common_properties(*painting_area);
let mut common = builder.common_properties(painting_area);
if let Some(clip_id) = clip {
common.clip_id = clip_id
}
@@ -54,12 +90,14 @@ pub(super) fn painting_area<'a>(
pub(super) fn layout_layer(
fragment_builder: &mut super::BuilderForBoxFragment,
builder: &mut super::DisplayListBuilder,
background_style: &ServoArc<ComputedValues>,
layer_index: usize,
intrinsic: IntrinsicSizes,
) -> Option<BackgroundLayer> {
let b = fragment_builder.fragment.style.get_background();
let (painting_area, common) = painting_area(fragment_builder, builder, layer_index);
let (painting_area, common) =
painting_area(fragment_builder, builder, background_style, layer_index);

let b = background_style.get_background();
let positioning_area = match get_cyclic(&b.background_origin.0, layer_index) {
Origin::ContentBox => fragment_builder.content_rect(),
Origin::PaddingBox => fragment_builder.padding_rect(),
@@ -4,6 +4,7 @@

use crate::context::LayoutContext;
use crate::display_list::conversions::ToWebRender;
use crate::dom_traversal::NodeFlags;
use crate::fragments::{BoxFragment, Fragment, TextFragment};
use crate::geom::{PhysicalPoint, PhysicalRect};
use crate::replaced::IntrinsicSizes;
@@ -12,6 +13,7 @@ use euclid::{Point2D, SideOffsets2D, Size2D};
use gfx::text::glyph::GlyphStore;
use mitochondria::OnceCell;
use net_traits::image_cache::UsePlaceholder;
use servo_arc::Arc as ServoArc;
use std::sync::Arc;
use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
use style::dom::OpaqueNode;
@@ -49,6 +51,10 @@ pub struct DisplayListBuilder<'a> {
/// (i.e. the display list contains items of type text,
/// image, non-white canvas or SVG). Used by metrics.
pub is_contentful: bool,

/// The style to apply to use when painting the canvas background of the root
/// <html> element.
pub root_canvas_style: Option<ServoArc<ComputedValues>>,
}

impl<'a> DisplayListBuilder<'a> {
@@ -62,6 +68,7 @@ impl<'a> DisplayListBuilder<'a> {
is_contentful: false,
context,
wr: wr::DisplayListBuilder::new(pipeline_id, viewport_size),
root_canvas_style: None,
}
}

@@ -78,7 +85,9 @@ impl Fragment {
containing_block: &PhysicalRect<Length>,
) {
match self {
Fragment::Box(b) => BuilderForBoxFragment::new(b, containing_block).build(builder),
Fragment::Box(fragment) => {
BuilderForBoxFragment::new(fragment, containing_block).build(builder);
},
Fragment::AbsoluteOrFixedPositioned(_) => {},
Fragment::Anonymous(_) => {},
Fragment::Image(i) => {
@@ -257,6 +266,18 @@ impl<'a> BuilderForBoxFragment<'a> {
}
}

fn background_style(&self, builder: &DisplayListBuilder) -> ServoArc<ComputedValues> {
if self.fragment.flags.contains(NodeFlags::IS_HTML_ELEMENT) {
builder
.root_canvas_style
.as_ref()
.unwrap_or(&self.fragment.style)
.clone()
} else {
self.fragment.style.clone()
}
}

fn content_rect(&self) -> &units::LayoutRect {
self.content_rect.init_once(|| {
self.fragment
@@ -333,19 +354,35 @@ impl<'a> BuilderForBoxFragment<'a> {
}

fn build_background(&mut self, builder: &mut DisplayListBuilder) {
// If this is the body element of the page and our style used on the html element
// then we shouldn't paint our background for our own fragment.
if self.fragment.flags.contains(NodeFlags::IS_BODY_ELEMENT) &&
builder
.root_canvas_style
.as_ref()
.map_or(false, |root_style| {
ServoArc::ptr_eq(root_style, &self.fragment.style)
})
{
return;
}

use style::values::computed::image::{Image, ImageLayer};
let b = self.fragment.style.get_background();
let background_color = self.fragment.style.resolve_color(b.background_color);
let background_style = self.background_style(builder).clone();
let background = background_style.get_background();
let background_color = background_style.resolve_color(background.background_color);

if background_color.alpha > 0 {
// https://drafts.csswg.org/css-backgrounds/#background-color
// “The background color is clipped according to the background-clip
// value associated with the bottom-most background image layer.”
let layer_index = b.background_image.0.len() - 1;
let (_, common) = background::painting_area(self, builder, layer_index);
let layer_index = background.background_image.0.len() - 1;
let (_, common) =
background::painting_area(self, builder, &background_style, layer_index);
builder.wr.push_rect(&common, rgba(background_color))
}
// Reverse because the property is top layer first, we want to paint bottom layer first.
for (index, layer) in b.background_image.0.iter().enumerate().rev() {
for (index, layer) in background.background_image.0.iter().enumerate().rev() {
match layer {
ImageLayer::None => {},
ImageLayer::Image(image) => match image {
@@ -355,10 +392,14 @@ impl<'a> BuilderForBoxFragment<'a> {
height: None,
ratio: None,
};
if let Some(layer) =
&background::layout_layer(self, builder, index, intrinsic)
{
gradient::build(&self.fragment.style, gradient, layer, builder)
if let Some(layer) = &background::layout_layer(
self,
builder,
&background_style,
index,
intrinsic,
) {
gradient::build(&background_style, gradient, layer, builder)
}
},
Image::Url(image_url) => {
@@ -393,11 +434,15 @@ impl<'a> BuilderForBoxFragment<'a> {
ratio: Some(width as f32 / height as f32),
};

if let Some(layer) =
background::layout_layer(self, builder, index, intrinsic)
{
if let Some(layer) = background::layout_layer(
self,
builder,
&background_style,
index,
intrinsic,
) {
let image_rendering =
image_rendering(self.fragment.style.clone_image_rendering());
image_rendering(background_style.clone_image_rendering());
if layer.repeat {
builder.wr.push_repeating_image(
&layer.common,
@@ -5,6 +5,7 @@
use crate::cell::ArcRefCell;
use crate::display_list::conversions::ToWebRender;
use crate::display_list::DisplayListBuilder;
use crate::dom_traversal::NodeFlags;
use crate::fragments::{
AbsoluteOrFixedPositionedFragment, AnonymousFragment, BoxFragment, Fragment,
};
@@ -70,6 +71,10 @@ pub(crate) struct StackingContextBuilder<'a> {
nearest_reference_frame: wr::SpatialId,

wr: &'a mut wr::DisplayListBuilder,

/// The style to apply to use when painting the canvas background of the root
/// <html> element.
pub root_canvas_style: Option<ServoArc<ComputedValues>>,
}

impl<'a> StackingContextBuilder<'a> {
@@ -78,6 +83,7 @@ impl<'a> StackingContextBuilder<'a> {
current_space_and_clip: wr::SpaceAndClipInfo::root_scroll(wr.pipeline_id),
nearest_reference_frame: wr::SpatialId::root_reference_frame(wr.pipeline_id),
wr,
root_canvas_style: None,
}
}

@@ -92,6 +98,29 @@ impl<'a> StackingContextBuilder<'a> {

result
}

fn set_root_canvas_background_if_necessary(&mut self, fragment: &BoxFragment) {
// https://drafts.csswg.org/css-backgrounds/#special-backgrounds
// If the first child element is the root element and doesn't have a non-transparent
// background specified, we should apply the style of the <body> element to it.

// If we've already set a root canvas style, don't override it.
if self.root_canvas_style.is_some() {
return;
}

// We should never prefer a style that specifies a transparent background for
// use as the root canvas background style.
if !fragment.style.has_nontransparent_background() {
return;
}

if fragment.flags.contains(NodeFlags::IS_HTML_ELEMENT) {
self.root_canvas_style = Some(fragment.style.clone());
} else if fragment.flags.contains(NodeFlags::IS_BODY_ELEMENT) {
self.root_canvas_style = Some(fragment.style.clone());
}
}
}

#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
@@ -378,6 +407,15 @@ impl BoxFragment {
}

fn get_stacking_context_section(&self) -> StackingContextSection {
// https://drafts.csswg.org/css2/zindex.html
// The root HTML and BODY elements are considered as part of the root
// stacking context background.
if self.flags.contains(NodeFlags::IS_HTML_ELEMENT) ||
self.flags.contains(NodeFlags::IS_BODY_ELEMENT)
{
return StackingContextSection::BackgroundsAndBorders;
}

if self.get_stacking_context_type().is_some() {
return StackingContextSection::BackgroundsAndBorders;
}
@@ -420,6 +458,8 @@ impl BoxFragment {
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
) {
builder.set_root_canvas_background_if_necessary(self);

builder.clipping_and_scrolling_scope(|builder| {
self.adjust_spatial_id_for_positioning(builder);

@@ -355,6 +355,17 @@ impl Drop for BoxSlot<'_> {
}
}

bitflags! {
#[derive(Serialize)]
pub(crate) struct NodeFlags: u8 {
#[doc = "This node is an <html> element."]
const IS_HTML_ELEMENT = 0b001;

#[doc = "This node is a <body> element."]
const IS_BODY_ELEMENT = 0b010;
}
}

pub(crate) trait NodeExt<'dom>: 'dom + Copy + LayoutNode<'dom> + Send + Sync {
fn is_element(self) -> bool;
fn as_text(self) -> Option<Cow<'dom, str>>;
@@ -374,6 +385,8 @@ pub(crate) trait NodeExt<'dom>: 'dom + Copy + LayoutNode<'dom> + Send + Sync {
fn pseudo_element_box_slot(&self, which: WhichPseudoElement) -> BoxSlot<'dom>;
fn unset_pseudo_element_box(self, which: WhichPseudoElement);
fn unset_boxes_in_subtree(self);

fn get_node_flags(&self) -> NodeFlags;
}

impl<'dom, T> NodeExt<'dom> for T
@@ -519,4 +532,15 @@ where
}
}
}

fn get_node_flags(&self) -> NodeFlags {
let mut flags = NodeFlags::empty();
if self.to_threadsafe().is_html_element_for_layout() {
flags |= NodeFlags::IS_HTML_ELEMENT;
} else if self.to_threadsafe().is_body_element_for_layout() {
flags |= NodeFlags::IS_BODY_ELEMENT;
}

flags
}
}
@@ -432,6 +432,7 @@ where
first_fragment: true,
last_fragment: false,
children: vec![],
flags: node.get_node_flags(),
});

// `unwrap` doesn’t panic here because `is_replaced` returned `false`.
@@ -492,6 +493,7 @@ where
// are obviously not the last fragment.
last_fragment: false,
children: std::mem::take(&mut ongoing.children),
flags: ongoing.flags,
};
ongoing.first_fragment = false;
fragmented
@@ -699,6 +701,7 @@ where
tag: node.as_opaque(),
contents,
style,
flags: node.get_node_flags(),
});
(block_level_box, contains_floats)
},
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.