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

Correctly paint the CSS canvas’ background #26414

Merged
merged 8 commits into from May 15, 2020

Correctly paint the CSS canvas’ background

  • Loading branch information
SimonSapin committed May 15, 2020
commit 1f6efbf9e94542bde390cf9edfe56a03b09c351e
@@ -6,6 +6,7 @@ use crate::replaced::IntrinsicSizes;
use euclid::{Size2D, Vector2D};
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;
@@ -31,17 +32,34 @@ fn get_cyclic<T>(values: &[T], layer_index: usize) -> &T {
&values[layer_index % values.len()]
}

pub(super) enum Source<'a> {
Fragment,
Canvas {
style: &'a ComputedValues,

// Theoretically the painting area is the infinite 2D plane,
// but WebRender doesn’t really do infinite so this is the part of it that can be visible.
painting_area: units::LayoutRect,
},
}

pub(super) fn painting_area<'a>(
fragment_builder: &'a super::BuilderForBoxFragment,
source: &'a Source,
builder: &mut super::DisplayListBuilder,
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)),
let (painting_area, clip) = match source {
Source::Canvas { painting_area, .. } => (painting_area, None),
Source::Fragment => {
let fb = fragment_builder;
let b = fb.fragment.style.get_background();
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)),
}
},
};
// The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`:
let mut common = builder.common_properties(*painting_area);
@@ -53,12 +71,17 @@ pub(super) fn painting_area<'a>(

pub(super) fn layout_layer(
fragment_builder: &mut super::BuilderForBoxFragment,
source: &Source,
builder: &mut super::DisplayListBuilder,
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 style = match *source {
Source::Canvas { style, .. } => style,
Source::Fragment => &fragment_builder.fragment.style,
};
let b = style.get_background();
let (painting_area, common) = painting_area(fragment_builder, source, builder, layer_index);

let positioning_area = match get_cyclic(&b.background_origin.0, layer_index) {
Origin::ContentBox => fragment_builder.content_rect(),
@@ -41,6 +41,7 @@ pub struct DisplayListBuilder<'a> {
/// The current SpatialId and ClipId information for this `DisplayListBuilder`.
current_space_and_clip: wr::SpaceAndClipInfo,

element_for_canvas_background: OpaqueNode,
pub context: &'a LayoutContext<'a>,
pub wr: wr::DisplayListBuilder,

@@ -55,13 +56,14 @@ impl<'a> DisplayListBuilder<'a> {
pub fn new(
pipeline_id: wr::PipelineId,
context: &'a LayoutContext,
viewport_size: wr::units::LayoutSize,
fragment_tree: &crate::FragmentTree,
) -> Self {
Self {
current_space_and_clip: wr::SpaceAndClipInfo::root_scroll(pipeline_id),
element_for_canvas_background: fragment_tree.canvas_background.from_element,
is_contentful: false,
context,
wr: wr::DisplayListBuilder::new(pipeline_id, viewport_size),
wr: wr::DisplayListBuilder::new(pipeline_id, fragment_tree.scrollable_overflow()),
}
}

@@ -333,19 +335,40 @@ impl<'a> BuilderForBoxFragment<'a> {
}

fn build_background(&mut self, builder: &mut DisplayListBuilder) {
use style::values::computed::image::Image;
let b = self.fragment.style.get_background();
let background_color = self.fragment.style.resolve_color(b.background_color);
if self.fragment.tag == builder.element_for_canvas_background {
// This background is already painted for the canvas, don’t paint it again here.
return;
}

let source = background::Source::Fragment;
let style = &self.fragment.style;
let b = style.get_background();
let background_color = style.resolve_color(b.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 (bounds, common) = background::painting_area(self, builder, layer_index);
let (bounds, common) = background::painting_area(self, &source, builder, layer_index);
builder
.wr
.push_rect(&common, *bounds, rgba(background_color))
}

self.build_background_image(builder, source);
}

fn build_background_image(
&mut self,
builder: &mut DisplayListBuilder,
source: background::Source<'a>,
) {
use style::values::computed::image::Image;
let style = match source {
background::Source::Canvas { style, .. } => style,
background::Source::Fragment => &self.fragment.style,
};
let b = style.get_background();
// Reverse because the property is top layer first, we want to paint bottom layer first.
for (index, image) in b.background_image.0.iter().enumerate().rev() {
match image {
@@ -356,9 +379,10 @@ impl<'a> BuilderForBoxFragment<'a> {
height: None,
ratio: None,
};
if let Some(layer) = &background::layout_layer(self, builder, index, intrinsic)
if let Some(layer) =
&background::layout_layer(self, &source, builder, index, intrinsic)
{
gradient::build(&self.fragment.style, &gradient, layer, builder)
gradient::build(&style, &gradient, layer, builder)
}
},
Image::Url(ref image_url) => {
@@ -393,9 +417,10 @@ impl<'a> BuilderForBoxFragment<'a> {
ratio: Some(width as f32 / height as f32),
};

if let Some(layer) = background::layout_layer(self, builder, index, intrinsic) {
let image_rendering =
image_rendering(self.fragment.style.clone_image_rendering());
if let Some(layer) =
background::layout_layer(self, &source, builder, index, intrinsic)
{
let image_rendering = image_rendering(style.clone_image_rendering());
if layer.repeat {
builder.wr.push_repeating_image(
&layer.common,
@@ -94,7 +94,7 @@ impl<'a> StackingContextBuilder<'a> {
}
}

#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) enum StackingContextSection {
BackgroundsAndBorders,
BlockBackgroundsAndBorders,
@@ -253,7 +253,124 @@ impl StackingContext {
true
}

pub(crate) fn build_display_list<'a>(&self, builder: &'a mut DisplayListBuilder) {
/// https://drafts.csswg.org/css-backgrounds/#special-backgrounds
///
/// This is only called for the root `StackingContext`
pub(crate) fn build_canvas_background_display_list(
&self,
builder: &mut DisplayListBuilder,
fragment_tree: &crate::FragmentTree,
containing_block_rect: &PhysicalRect<Length>,
) {
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`
return;
};

// The painting area is theoretically the infinite 2D plane,
// but we need a rectangle with finite coordinates.
//
// 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
.initial_containing_block
.union(&fragment_tree.scrollable_overflow)
.to_webrender();

let background_color = style.resolve_color(style.get_background().background_color);
if background_color.alpha > 0 {
let common = builder.common_properties(painting_area);
let color = super::rgba(background_color);
builder.wr.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.fragments.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.stacking_contexts
.first()
.and_then(|first_child_stacking_context| {
first_child_stacking_context.fragments.first()
})
});

macro_rules! debug_panic {
($msg: expr) => {
if cfg!(debug_assertions) {
panic!($msg)
}
};
}

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`
debug_panic!("`CanvasBackground::for_root_element` should have returned `style: None`");
return;
};

let fragment = first_stacking_context_fragment.fragment.borrow();
let box_fragment = if let Fragment::Box(box_fragment) = &*fragment {
box_fragment
} else {
debug_panic!("Expected a box-generated fragment");
return;
};

// The `StackingContextFragment` we found is for the root DOM element:
debug_assert_eq!(
box_fragment.tag,
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_space_and_clip.spatial_id =
first_stacking_context_fragment.space_and_clip.spatial_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.origin -= reference_frame_data.origin.to_webrender().to_vector();
if let Some(transformed) = reference_frame_data
.transform
.inverse()
.and_then(|inversed| inversed.transform_rect(&painting_area))
{
painting_area = transformed
} else {
// The desired rect cannot be represented, so skip painting this background-image
return;
}
}

let mut fragment_builder = super::BuilderForBoxFragment::new(
box_fragment,
&first_stacking_context_fragment.containing_block,
);
let source = super::background::Source::Canvas {
style,
painting_area,
};
fragment_builder.build_background_image(builder, source);
}

pub(crate) fn build_display_list(&self, builder: &mut DisplayListBuilder) {
let pushed_context = self.push_webrender_stacking_context_if_necessary(builder);

// Properly order display items that make up a stacking context. "Steps" here
@@ -470,7 +587,8 @@ impl BoxFragment {
) {
// If we are creating a stacking context, we may also need to create a reference
// frame first.
let reference_frame_data = self.reference_frame_data_if_necessary(containing_block_info);
let reference_frame_data =
self.reference_frame_data_if_necessary(&containing_block_info.rect);

// WebRender reference frames establish a new coordinate system at their origin
// (the border box of the fragment). We need to ensure that any coordinates we
@@ -622,17 +740,16 @@ impl BoxFragment {
/// Optionally returns the data for building a reference frame, without yet building it.
fn reference_frame_data_if_necessary(
&self,
containing_block_info: &ContainingBlockInfo,
containing_block_rect: &PhysicalRect<Length>,
) -> Option<ReferenceFrameData> {
if !self.style.has_transform_or_perspective() {
return None;
}

let relative_border_rect = self
.border_rect()
.to_physical(self.style.writing_mode, &containing_block_info.rect);
let border_rect = relative_border_rect
.translate(containing_block_info.rect.origin.to_vector());
.to_physical(self.style.writing_mode, &containing_block_rect);
let border_rect = relative_border_rect.translate(containing_block_rect.origin.to_vector());
let untyped_border_rect = border_rect.to_untyped();

let transform = self.calculate_transform_matrix(&untyped_border_rect);
@@ -87,14 +87,12 @@ fn traverse_children_of<'dom, Node>(
{
traverse_pseudo_element(WhichPseudoElement::Before, parent_element, context, handler);

let mut next = parent_element.first_child();
while let Some(child) = next {
for child in iter_child_nodes(parent_element) {
if let Some(contents) = child.as_text() {
handler.handle_text(child, contents, &child.style(context));
} else if child.is_element() {
traverse_element(child, context, handler);
}
next = child.next_sibling();
}

traverse_pseudo_element(WhichPseudoElement::After, parent_element, context, handler);
@@ -485,3 +483,16 @@ where
// for DOM descendants of elements with `display: none`.
}
}

pub(crate) fn iter_child_nodes<'dom, Node>(parent: Node) -> impl Iterator<Item = Node>
where
Node: NodeExt<'dom>,
{
let mut next = parent.first_child();
std::iter::from_fn(move || {
next.map(|child| {
next = child.next_sibling();
child
})
})
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.