Remove legacy layout (layout 2013) (#35943)

We were already not compiling it and not running tests on it by default.
So it's simpler to just completely remove it.

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-03-13 08:26:57 +01:00 committed by GitHub
parent f93006af95
commit 7594dc6991
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17224 changed files with 23 additions and 1584835 deletions

View file

@ -1,341 +0,0 @@
/* 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 app_units::Au;
use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D};
use style::computed_values::background_attachment::single_value::T as BackgroundAttachment;
use style::computed_values::background_clip::single_value::T as BackgroundClip;
use style::computed_values::background_origin::single_value::T as BackgroundOrigin;
use style::properties::style_structs::Background;
use style::values::computed::{BackgroundSize, NonNegativeLengthPercentageOrAuto};
use style::values::specified::background::BackgroundRepeatKeyword;
use webrender_api::BorderRadius;
use crate::display_list::border;
/// Placment information for both image and gradient backgrounds.
#[derive(Clone, Copy, Debug)]
pub struct BackgroundPlacement {
/// Rendering bounds. The background will start in the uppper-left corner
/// and fill the whole area.
pub bounds: Rect<Au>,
/// Background tile size. Some backgrounds are repeated. These are the
/// dimensions of a single image of the background.
pub tile_size: Size2D<Au>,
/// Spacing between tiles. Some backgrounds are not repeated seamless
/// but have seams between them like tiles in real life.
pub tile_spacing: Size2D<Au>,
/// A clip area. While the background is rendered according to all the
/// measures above it is only shown within these bounds.
pub clip_rect: Rect<Au>,
/// Rounded corners for the clip_rect.
pub clip_radii: BorderRadius,
}
/// Access element at index modulo the array length.
///
/// Obviously it does not work with empty arrays.
///
/// This is used for multiple layered background images.
/// See: <https://drafts.csswg.org/css-backgrounds-3/#layering>
pub fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
&arr[index % arr.len()]
}
/// For a given area and an image compute how big the
/// image should be displayed on the background.
fn compute_background_image_size(
bg_size: &BackgroundSize,
bounds_size: Size2D<Au>,
intrinsic_size: Option<Size2D<Au>>,
) -> Size2D<Au> {
match intrinsic_size {
None => match bg_size {
BackgroundSize::Cover | BackgroundSize::Contain => bounds_size,
BackgroundSize::ExplicitSize { width, height } => Size2D::new(
width
.to_used_value(bounds_size.width)
.unwrap_or(bounds_size.width),
height
.to_used_value(bounds_size.height)
.unwrap_or(bounds_size.height),
),
},
Some(own_size) => {
// If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
// wide.
let image_aspect_ratio = own_size.width.to_f32_px() / own_size.height.to_f32_px();
let bounds_aspect_ratio =
bounds_size.width.to_f32_px() / bounds_size.height.to_f32_px();
match (bg_size, image_aspect_ratio < bounds_aspect_ratio) {
(BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => Size2D::new(
bounds_size.width,
bounds_size.width.scale_by(image_aspect_ratio.recip()),
),
(BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => Size2D::new(
bounds_size.height.scale_by(image_aspect_ratio),
bounds_size.height,
),
(
BackgroundSize::ExplicitSize {
width,
height: NonNegativeLengthPercentageOrAuto::Auto,
},
_,
) => {
let width = width
.to_used_value(bounds_size.width)
.unwrap_or(own_size.width);
Size2D::new(width, width.scale_by(image_aspect_ratio.recip()))
},
(
BackgroundSize::ExplicitSize {
width: NonNegativeLengthPercentageOrAuto::Auto,
height,
},
_,
) => {
let height = height
.to_used_value(bounds_size.height)
.unwrap_or(own_size.height);
Size2D::new(height.scale_by(image_aspect_ratio), height)
},
(BackgroundSize::ExplicitSize { width, height }, _) => Size2D::new(
width
.to_used_value(bounds_size.width)
.unwrap_or(own_size.width),
height
.to_used_value(bounds_size.height)
.unwrap_or(own_size.height),
),
}
},
}
}
/// Compute a rounded clip rect for the background.
pub fn clip(
bg_clip: BackgroundClip,
absolute_bounds: Rect<Au>,
border: SideOffsets2D<Au>,
border_padding: SideOffsets2D<Au>,
border_radii: BorderRadius,
) -> (Rect<Au>, BorderRadius) {
match bg_clip {
BackgroundClip::BorderBox => (absolute_bounds, border_radii),
BackgroundClip::PaddingBox => (
absolute_bounds.inner_rect(border),
border::inner_radii(border_radii, border),
),
BackgroundClip::ContentBox => (
absolute_bounds.inner_rect(border_padding),
border::inner_radii(border_radii, border_padding),
),
}
}
/// Determines where to place an element background image or gradient.
///
/// Images have their resolution as intrinsic size while gradients have
/// no intrinsic size.
///
/// Return `None` if the background size is zero, otherwise a [`BackgroundPlacement`].
#[allow(clippy::too_many_arguments)]
pub fn placement(
bg: &Background,
viewport_size: Size2D<Au>,
absolute_bounds: Rect<Au>,
intrinsic_size: Option<Size2D<Au>>,
border: SideOffsets2D<Au>,
border_padding: SideOffsets2D<Au>,
border_radii: BorderRadius,
index: usize,
) -> Option<BackgroundPlacement> {
let bg_attachment = *get_cyclic(&bg.background_attachment.0, index);
let bg_clip = *get_cyclic(&bg.background_clip.0, index);
let bg_origin = *get_cyclic(&bg.background_origin.0, index);
let bg_position_x = get_cyclic(&bg.background_position_x.0, index);
let bg_position_y = get_cyclic(&bg.background_position_y.0, index);
let bg_repeat = get_cyclic(&bg.background_repeat.0, index);
let bg_size = get_cyclic(&bg.background_size.0, index);
let (clip_rect, clip_radii) = clip(
bg_clip,
absolute_bounds,
border,
border_padding,
border_radii,
);
let mut bounds = match bg_attachment {
BackgroundAttachment::Scroll => match bg_origin {
BackgroundOrigin::BorderBox => absolute_bounds,
BackgroundOrigin::PaddingBox => absolute_bounds.inner_rect(border),
BackgroundOrigin::ContentBox => absolute_bounds.inner_rect(border_padding),
},
BackgroundAttachment::Fixed => Rect::new(Point2D::origin(), viewport_size),
};
let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size);
if tile_size.is_empty() {
return None;
}
let mut tile_spacing = Size2D::zero();
let own_position = bounds.size - tile_size;
let pos_x = bg_position_x.to_used_value(own_position.width);
let pos_y = bg_position_y.to_used_value(own_position.height);
tile_image_axis(
bg_repeat.0,
&mut bounds.origin.x,
&mut bounds.size.width,
&mut tile_size.width,
&mut tile_spacing.width,
pos_x,
clip_rect.origin.x,
clip_rect.size.width,
);
tile_image_axis(
bg_repeat.1,
&mut bounds.origin.y,
&mut bounds.size.height,
&mut tile_size.height,
&mut tile_spacing.height,
pos_y,
clip_rect.origin.y,
clip_rect.size.height,
);
if tile_size.is_empty() {
return None;
}
Some(BackgroundPlacement {
bounds,
tile_size,
tile_spacing,
clip_rect,
clip_radii,
})
}
fn tile_image_round(
position: &mut Au,
size: &mut Au,
absolute_anchor_origin: Au,
image_size: &mut Au,
) {
if *size == Au(0) || *image_size == Au(0) {
*position = Au(0);
*size = Au(0);
return;
}
let number_of_tiles = (size.to_f32_px() / image_size.to_f32_px()).round().max(1.0);
*image_size = *size / (number_of_tiles as i32);
tile_image(position, size, absolute_anchor_origin, *image_size);
}
fn tile_image_spaced(
position: &mut Au,
size: &mut Au,
tile_spacing: &mut Au,
absolute_anchor_origin: Au,
image_size: Au,
) {
if *size == Au(0) || image_size == Au(0) {
*position = Au(0);
*size = Au(0);
*tile_spacing = Au(0);
return;
}
// Per the spec, if the space available is not enough for two images, just tile as
// normal but only display a single tile.
if image_size * 2 >= *size {
tile_image(position, size, absolute_anchor_origin, image_size);
*tile_spacing = Au(0);
*size = image_size;
return;
}
// Take the box size, remove room for two tiles on the edges, and then calculate how many
// other tiles fit in between them.
let size_remaining = *size - (image_size * 2);
let num_middle_tiles = (size_remaining.to_f32_px() / image_size.to_f32_px()).floor() as i32;
// Allocate the remaining space as padding between tiles. background-position is ignored
// as per the spec, so the position is just the box origin. We are also ignoring
// background-attachment here, which seems unspecced when combined with
// background-repeat: space.
let space_for_middle_tiles = image_size * num_middle_tiles;
*tile_spacing = (size_remaining - space_for_middle_tiles) / (num_middle_tiles + 1);
}
/// Tile an image
fn tile_image(position: &mut Au, size: &mut Au, absolute_anchor_origin: Au, image_size: Au) {
// Avoid division by zero below!
// Images with a zero width or height are not displayed.
// Therefore the positions do not matter and can be left unchanged.
// NOTE: A possible optimization is not to build
// display items in this case at all.
if image_size == Au(0) {
return;
}
let delta_pixels = absolute_anchor_origin - *position;
let image_size_px = image_size.to_f32_px();
let tile_count = ((delta_pixels.to_f32_px() + image_size_px - 1.0) / image_size_px).floor();
let offset = image_size * (tile_count as i32);
let new_position = absolute_anchor_origin - offset;
*size = *position - new_position + *size;
*position = new_position;
}
/// For either the x or the y axis adjust various values to account for tiling.
///
/// This is done separately for both axes because the repeat keywords may differ.
#[allow(clippy::too_many_arguments)]
fn tile_image_axis(
repeat: BackgroundRepeatKeyword,
position: &mut Au,
size: &mut Au,
tile_size: &mut Au,
tile_spacing: &mut Au,
offset: Au,
clip_origin: Au,
clip_size: Au,
) {
let absolute_anchor_origin = *position + offset;
match repeat {
BackgroundRepeatKeyword::NoRepeat => {
*position += offset;
*size = *tile_size;
},
BackgroundRepeatKeyword::Repeat => {
*position = clip_origin;
*size = clip_size;
tile_image(position, size, absolute_anchor_origin, *tile_size);
},
BackgroundRepeatKeyword::Space => {
tile_image_spaced(
position,
size,
tile_spacing,
absolute_anchor_origin,
*tile_size,
);
let combined_tile_size = *tile_size + *tile_spacing;
*position = clip_origin;
*size = clip_size;
tile_image(position, size, absolute_anchor_origin, combined_tile_size);
},
BackgroundRepeatKeyword::Round => {
tile_image_round(position, size, absolute_anchor_origin, tile_size);
*position = clip_origin;
*size = clip_size;
tile_image(position, size, absolute_anchor_origin, *tile_size);
},
}
}

View file

@ -1,207 +0,0 @@
/* 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 app_units::Au;
use euclid::default::{Rect, SideOffsets2D as UntypedSideOffsets2D, Size2D as UntypedSize2D};
use euclid::{SideOffsets2D, Size2D};
use style::computed_values::border_image_outset::T as BorderImageOutset;
use style::properties::style_structs::Border;
use style::values::computed::{
BorderCornerRadius, BorderImageSideWidth, BorderImageWidth, NonNegativeLengthOrNumber,
NumberOrPercentage,
};
use style::values::generics::NonNegative;
use style::values::generics::rect::Rect as StyleRect;
use webrender_api::units::{LayoutSideOffsets, LayoutSize};
use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF, NormalBorder};
use crate::display_list::ToLayout;
/// Computes a border radius size against the containing size.
///
/// Note that percentages in `border-radius` are resolved against the relevant
/// box dimension instead of only against the width per [1]:
///
/// > Percentages: Refer to corresponding dimension of the border box.
///
/// [1]: https://drafts.csswg.org/css-backgrounds-3/#border-radius
fn corner_radius(
radius: &BorderCornerRadius,
containing_size: UntypedSize2D<Au>,
) -> UntypedSize2D<Au> {
let w = radius.0.width().to_used_value(containing_size.width);
let h = radius.0.height().to_used_value(containing_size.height);
Size2D::new(w, h)
}
fn scaled_radii(radii: BorderRadius, factor: f32) -> BorderRadius {
BorderRadius {
top_left: radii.top_left * factor,
top_right: radii.top_right * factor,
bottom_left: radii.bottom_left * factor,
bottom_right: radii.bottom_right * factor,
}
}
fn overlapping_radii(size: LayoutSize, radii: BorderRadius) -> BorderRadius {
// No two corners' border radii may add up to more than the length of the edge
// between them. To prevent that, all radii are scaled down uniformly.
fn scale_factor(radius_a: f32, radius_b: f32, edge_length: f32) -> f32 {
let required = radius_a + radius_b;
if required <= edge_length {
1.0
} else {
edge_length / required
}
}
let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
let bottom_factor = scale_factor(
radii.bottom_left.width,
radii.bottom_right.width,
size.width,
);
let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
let right_factor = scale_factor(
radii.top_right.height,
radii.bottom_right.height,
size.height,
);
let min_factor = top_factor
.min(bottom_factor)
.min(left_factor)
.min(right_factor);
if min_factor < 1.0 {
scaled_radii(radii, min_factor)
} else {
radii
}
}
/// Determine the four corner radii of a border.
///
/// Radii may either be absolute or relative to the absolute bounds.
/// Each corner radius has a width and a height which may differ.
/// Lastly overlapping radii are shrank so they don't collide anymore.
pub fn radii(abs_bounds: Rect<Au>, border_style: &Border) -> BorderRadius {
// TODO(cgaebel): Support border radii even in the case of multiple border widths.
// This is an extension of supporting elliptical radii. For now, all percentage
// radii will be relative to the width.
overlapping_radii(
abs_bounds.size.to_layout(),
BorderRadius {
top_left: corner_radius(&border_style.border_top_left_radius, abs_bounds.size)
.to_layout(),
top_right: corner_radius(&border_style.border_top_right_radius, abs_bounds.size)
.to_layout(),
bottom_right: corner_radius(&border_style.border_bottom_right_radius, abs_bounds.size)
.to_layout(),
bottom_left: corner_radius(&border_style.border_bottom_left_radius, abs_bounds.size)
.to_layout(),
},
)
}
/// Calculates radii for the inner side.
///
/// Radii usually describe the outer side of a border but for the lines to look nice
/// the inner radii need to be smaller depending on the line width.
///
/// This is used to determine clipping areas.
pub fn inner_radii(mut radii: BorderRadius, offsets: UntypedSideOffsets2D<Au>) -> BorderRadius {
fn inner_length(x: f32, offset: Au) -> f32 {
0.0_f32.max(x - offset.to_f32_px())
}
radii.top_left.width = inner_length(radii.top_left.width, offsets.left);
radii.bottom_left.width = inner_length(radii.bottom_left.width, offsets.left);
radii.top_right.width = inner_length(radii.top_right.width, offsets.right);
radii.bottom_right.width = inner_length(radii.bottom_right.width, offsets.right);
radii.top_left.height = inner_length(radii.top_left.height, offsets.top);
radii.top_right.height = inner_length(radii.top_right.height, offsets.top);
radii.bottom_left.height = inner_length(radii.bottom_left.height, offsets.bottom);
radii.bottom_right.height = inner_length(radii.bottom_right.height, offsets.bottom);
radii
}
/// Creates a four-sided border with square corners and uniform color and width.
pub fn simple(color: ColorF, style: BorderStyle) -> NormalBorder {
let side = BorderSide { color, style };
NormalBorder {
left: side,
right: side,
top: side,
bottom: side,
radius: BorderRadius::zero(),
do_aa: true,
}
}
fn side_image_outset(outset: NonNegativeLengthOrNumber, border_width: Au) -> Au {
match outset {
NonNegativeLengthOrNumber::Length(length) => length.into(),
NonNegativeLengthOrNumber::Number(factor) => border_width.scale_by(factor.0),
}
}
/// Compute the additional border-image area.
pub fn image_outset(
outset: BorderImageOutset,
border: UntypedSideOffsets2D<Au>,
) -> UntypedSideOffsets2D<Au> {
SideOffsets2D::new(
side_image_outset(outset.0, border.top),
side_image_outset(outset.1, border.right),
side_image_outset(outset.2, border.bottom),
side_image_outset(outset.3, border.left),
)
}
fn side_image_width(
border_image_width: &BorderImageSideWidth,
border_width: f32,
total_length: Au,
) -> f32 {
match border_image_width {
BorderImageSideWidth::LengthPercentage(v) => v.to_used_value(total_length).to_f32_px(),
BorderImageSideWidth::Number(x) => border_width * x.0,
BorderImageSideWidth::Auto => border_width,
}
}
pub fn image_width(
width: &BorderImageWidth,
border: LayoutSideOffsets,
border_area: UntypedSize2D<Au>,
) -> LayoutSideOffsets {
LayoutSideOffsets::new(
side_image_width(&width.0, border.top, border_area.height),
side_image_width(&width.1, border.right, border_area.width),
side_image_width(&width.2, border.bottom, border_area.height),
side_image_width(&width.3, border.left, border_area.width),
)
}
fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
match value.0 {
NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
NumberOrPercentage::Number(n) => n.round() as i32,
}
}
pub fn image_slice<U>(
border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
size: Size2D<i32, U>,
) -> SideOffsets2D<i32, U> {
SideOffsets2D::new(
resolve_percentage(border_image_slice.0, size.height),
resolve_percentage(border_image_slice.1, size.width),
resolve_percentage(border_image_slice.2, size.height),
resolve_percentage(border_image_slice.3, size.width),
)
}

File diff suppressed because it is too large Load diff

View file

@ -1,184 +0,0 @@
/* 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 app_units::Au;
use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D};
use style::color::{AbsoluteColor, ColorSpace};
use style::computed_values::image_rendering::T as ImageRendering;
use style::computed_values::mix_blend_mode::T as MixBlendMode;
use style::computed_values::transform_style::T as TransformStyle;
use style::values::computed::{BorderStyle, Filter};
use style::values::specified::border::BorderImageRepeatKeyword;
use webrender_api as wr;
pub trait ToLayout {
type Type;
fn to_layout(&self) -> Self::Type;
}
pub trait FilterToLayout {
type Type;
fn to_layout(&self, current_color: &AbsoluteColor) -> Self::Type;
}
impl ToLayout for BorderStyle {
type Type = wr::BorderStyle;
fn to_layout(&self) -> Self::Type {
match *self {
BorderStyle::None => wr::BorderStyle::None,
BorderStyle::Solid => wr::BorderStyle::Solid,
BorderStyle::Double => wr::BorderStyle::Double,
BorderStyle::Dotted => wr::BorderStyle::Dotted,
BorderStyle::Dashed => wr::BorderStyle::Dashed,
BorderStyle::Hidden => wr::BorderStyle::Hidden,
BorderStyle::Groove => wr::BorderStyle::Groove,
BorderStyle::Ridge => wr::BorderStyle::Ridge,
BorderStyle::Inset => wr::BorderStyle::Inset,
BorderStyle::Outset => wr::BorderStyle::Outset,
}
}
}
impl FilterToLayout for Filter {
type Type = wr::FilterOp;
fn to_layout(&self, current_color: &AbsoluteColor) -> Self::Type {
match *self {
Filter::Blur(radius) => wr::FilterOp::Blur(radius.px(), radius.px()),
Filter::Brightness(amount) => wr::FilterOp::Brightness(amount.0),
Filter::Contrast(amount) => wr::FilterOp::Contrast(amount.0),
Filter::Grayscale(amount) => wr::FilterOp::Grayscale(amount.0),
Filter::HueRotate(angle) => wr::FilterOp::HueRotate(angle.radians()),
Filter::Invert(amount) => wr::FilterOp::Invert(amount.0),
Filter::Opacity(amount) => wr::FilterOp::Opacity(amount.0.into(), amount.0),
Filter::Saturate(amount) => wr::FilterOp::Saturate(amount.0),
Filter::Sepia(amount) => wr::FilterOp::Sepia(amount.0),
Filter::DropShadow(ref shadow) => wr::FilterOp::DropShadow(wr::Shadow {
blur_radius: shadow.blur.px(),
offset: wr::units::LayoutVector2D::new(
shadow.horizontal.px(),
shadow.vertical.px(),
),
color: shadow
.color
.clone()
.resolve_to_absolute(current_color)
.to_layout(),
}),
// Statically check that Url is impossible.
Filter::Url(ref url) => match *url {},
}
}
}
impl ToLayout for ImageRendering {
type Type = wr::ImageRendering;
fn to_layout(&self) -> Self::Type {
match *self {
ImageRendering::Auto => wr::ImageRendering::Auto,
ImageRendering::CrispEdges => wr::ImageRendering::CrispEdges,
ImageRendering::Pixelated => wr::ImageRendering::Pixelated,
}
}
}
impl ToLayout for MixBlendMode {
type Type = wr::MixBlendMode;
fn to_layout(&self) -> Self::Type {
match *self {
MixBlendMode::Normal => wr::MixBlendMode::Normal,
MixBlendMode::Multiply => wr::MixBlendMode::Multiply,
MixBlendMode::Screen => wr::MixBlendMode::Screen,
MixBlendMode::Overlay => wr::MixBlendMode::Overlay,
MixBlendMode::Darken => wr::MixBlendMode::Darken,
MixBlendMode::Lighten => wr::MixBlendMode::Lighten,
MixBlendMode::ColorDodge => wr::MixBlendMode::ColorDodge,
MixBlendMode::ColorBurn => wr::MixBlendMode::ColorBurn,
MixBlendMode::HardLight => wr::MixBlendMode::HardLight,
MixBlendMode::SoftLight => wr::MixBlendMode::SoftLight,
MixBlendMode::Difference => wr::MixBlendMode::Difference,
MixBlendMode::Exclusion => wr::MixBlendMode::Exclusion,
MixBlendMode::Hue => wr::MixBlendMode::Hue,
MixBlendMode::Saturation => wr::MixBlendMode::Saturation,
MixBlendMode::Color => wr::MixBlendMode::Color,
MixBlendMode::Luminosity => wr::MixBlendMode::Luminosity,
MixBlendMode::PlusLighter => wr::MixBlendMode::PlusLighter,
}
}
}
impl ToLayout for TransformStyle {
type Type = wr::TransformStyle;
fn to_layout(&self) -> Self::Type {
match *self {
TransformStyle::Flat => wr::TransformStyle::Flat,
TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D,
}
}
}
impl ToLayout for AbsoluteColor {
type Type = wr::ColorF;
fn to_layout(&self) -> Self::Type {
let rgba = self.to_color_space(ColorSpace::Srgb);
wr::ColorF::new(
rgba.components.0.clamp(0.0, 1.0),
rgba.components.1.clamp(0.0, 1.0),
rgba.components.2.clamp(0.0, 1.0),
rgba.alpha,
)
}
}
impl ToLayout for Point2D<Au> {
type Type = wr::units::LayoutPoint;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}
impl ToLayout for Rect<Au> {
type Type = wr::units::LayoutRect;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutRect::from_origin_and_size(self.origin.to_layout(), self.size.to_layout())
}
}
impl ToLayout for SideOffsets2D<Au> {
type Type = wr::units::LayoutSideOffsets;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutSideOffsets::new(
self.top.to_f32_px(),
self.right.to_f32_px(),
self.bottom.to_f32_px(),
self.left.to_f32_px(),
)
}
}
impl ToLayout for Size2D<Au> {
type Type = wr::units::LayoutSize;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
}
}
impl ToLayout for Vector2D<Au> {
type Type = wr::units::LayoutVector2D;
fn to_layout(&self) -> Self::Type {
wr::units::LayoutVector2D::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}
impl ToLayout for BorderImageRepeatKeyword {
type Type = wr::RepeatMode;
fn to_layout(&self) -> Self::Type {
match *self {
BorderImageRepeatKeyword::Stretch => wr::RepeatMode::Stretch,
BorderImageRepeatKeyword::Repeat => wr::RepeatMode::Repeat,
BorderImageRepeatKeyword::Round => wr::RepeatMode::Round,
BorderImageRepeatKeyword::Space => wr::RepeatMode::Space,
}
}
}

View file

@ -1,349 +0,0 @@
/* 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 app_units::Au;
use euclid::default::{Point2D, Size2D, Vector2D};
use style::color::mix::ColorInterpolationMethod;
use style::properties::ComputedValues;
use style::values::computed::image::{EndingShape, LineDirection};
use style::values::computed::{Angle, Color, LengthPercentage, Percentage, Position};
use style::values::generics::image::{
Circle, ColorStop, Ellipse, GradientFlags, GradientItem, ShapeExtent,
};
use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient};
use crate::display_list::ToLayout;
/// A helper data structure for gradients.
#[derive(Clone, Copy)]
struct StopRun {
start_offset: f32,
end_offset: f32,
start_index: usize,
stop_count: usize,
}
/// Determines the radius of a circle if it was not explicitly provided.
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
fn circle_size_keyword(
keyword: ShapeExtent,
size: &Size2D<Au>,
center: &Point2D<Au>,
) -> Size2D<Au> {
let radius = match keyword {
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
let dist = distance_to_sides(size, center, ::std::cmp::min);
::std::cmp::min(dist.width, dist.height)
},
ShapeExtent::FarthestSide => {
let dist = distance_to_sides(size, center, ::std::cmp::max);
::std::cmp::max(dist.width, dist.height)
},
ShapeExtent::ClosestCorner => distance_to_corner(size, center, ::std::cmp::min),
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
distance_to_corner(size, center, ::std::cmp::max)
},
};
Size2D::new(radius, radius)
}
/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
fn ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
where
F: Fn(Au, Au) -> Au,
{
let dist = distance_to_sides(size, center, cmp);
Size2D::new(
dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
dist.height
.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
)
}
/// Determines the radius of an ellipse if it was not explicitly provided.
/// <https://drafts.csswg.org/css-images-3/#typedef-size>
fn ellipse_size_keyword(
keyword: ShapeExtent,
size: &Size2D<Au>,
center: &Point2D<Au>,
) -> Size2D<Au> {
match keyword {
ShapeExtent::ClosestSide | ShapeExtent::Contain => {
distance_to_sides(size, center, ::std::cmp::min)
},
ShapeExtent::FarthestSide => distance_to_sides(size, center, ::std::cmp::max),
ShapeExtent::ClosestCorner => ellipse_radius(size, center, ::std::cmp::min),
ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
ellipse_radius(size, center, ::std::cmp::max)
},
}
}
fn convert_gradient_stops(
style: &ComputedValues,
gradient_items: &[GradientItem<Color, LengthPercentage>],
total_length: Au,
) -> GradientBuilder {
// Determine the position of each stop per CSS-IMAGES § 3.4.
// Only keep the color stops, discard the color interpolation hints.
let mut stop_items = gradient_items
.iter()
.filter_map(|item| match item {
GradientItem::SimpleColorStop(color) => Some(ColorStop {
color,
position: None,
}),
GradientItem::ComplexColorStop {
color,
ref position,
} => Some(ColorStop {
color,
position: Some(position.clone()),
}),
_ => None,
})
.collect::<Vec<_>>();
assert!(!stop_items.is_empty());
// Run the algorithm from
// https://drafts.csswg.org/css-images-3/#color-stop-syntax
// Step 1:
// If the first color stop does not have a position, set its position to 0%.
{
let first = stop_items.first_mut().unwrap();
if first.position.is_none() {
first.position = Some(LengthPercentage::new_percent(Percentage(0.)));
}
}
// If the last color stop does not have a position, set its position to 100%.
{
let last = stop_items.last_mut().unwrap();
if last.position.is_none() {
last.position = Some(LengthPercentage::new_percent(Percentage(1.0)));
}
}
// Step 2: Move any stops placed before earlier stops to the
// same position as the preceding stop.
//
// FIXME(emilio): Once we know the offsets, it seems like converting the
// positions to absolute at once then process that would be cheaper.
let mut last_stop_position = stop_items
.first()
.unwrap()
.position
.as_ref()
.unwrap()
.clone();
for stop in stop_items.iter_mut().skip(1) {
if let Some(ref pos) = stop.position {
if position_to_offset(&last_stop_position, total_length) >
position_to_offset(pos, total_length)
{
stop.position = Some(last_stop_position);
}
last_stop_position = stop.position.as_ref().unwrap().clone();
}
}
// Step 3: Evenly space stops without position.
let mut stops = GradientBuilder::new();
let mut stop_run = None;
for (i, stop) in stop_items.iter().enumerate() {
let offset = match stop.position {
None => {
if stop_run.is_none() {
// Initialize a new stop run.
// `unwrap()` here should never fail because this is the beginning of
// a stop run, which is always bounded by a length or percentage.
let start_offset = position_to_offset(
stop_items[i - 1].position.as_ref().unwrap(),
total_length,
);
// `unwrap()` here should never fail because this is the end of
// a stop run, which is always bounded by a length or percentage.
let (end_index, end_stop) = stop_items[(i + 1)..]
.iter()
.enumerate()
.find(|(_, stop)| stop.position.is_some())
.unwrap();
let end_offset =
position_to_offset(end_stop.position.as_ref().unwrap(), total_length);
stop_run = Some(StopRun {
start_offset,
end_offset,
start_index: i - 1,
stop_count: end_index,
})
}
let stop_run = stop_run.unwrap();
let stop_run_length = stop_run.end_offset - stop_run.start_offset;
stop_run.start_offset +
stop_run_length * (i - stop_run.start_index) as f32 /
((2 + stop_run.stop_count) as f32)
},
Some(ref position) => {
stop_run = None;
position_to_offset(position, total_length)
},
};
assert!(offset.is_finite());
stops.push(GradientStop {
offset,
color: style.resolve_color(stop.color).to_layout(),
})
}
if stop_items.len() == 1 {
stops.push(stops.stops()[0])
}
stops
}
fn extend_mode(repeating: bool) -> ExtendMode {
if repeating {
ExtendMode::Repeat
} else {
ExtendMode::Clamp
}
}
/// Returns the the distance to the nearest or farthest corner depending on the comperator.
fn distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
where
F: Fn(Au, Au) -> Au,
{
let dist = distance_to_sides(size, center, cmp);
Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
}
/// Returns the distance to the nearest or farthest sides depending on the comparator.
///
/// The first return value is horizontal distance the second vertical distance.
fn distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
where
F: Fn(Au, Au) -> Au,
{
let top_side = center.y;
let right_side = size.width - center.x;
let bottom_side = size.height - center.y;
let left_side = center.x;
Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
}
fn position_to_offset(position: &LengthPercentage, total_length: Au) -> f32 {
if total_length == Au(0) {
return 0.0;
}
position.to_used_value(total_length).0 as f32 / total_length.0 as f32
}
pub fn linear(
style: &ComputedValues,
size: Size2D<Au>,
stops: &[GradientItem<Color, LengthPercentage>],
direction: LineDirection,
_color_interpolation_method: &ColorInterpolationMethod,
flags: GradientFlags,
) -> (Gradient, Vec<GradientStop>) {
use style::values::specified::position::HorizontalPositionKeyword::*;
use style::values::specified::position::VerticalPositionKeyword::*;
let repeating = flags.contains(GradientFlags::REPEATING);
let angle = match direction {
LineDirection::Angle(angle) => angle.radians(),
LineDirection::Horizontal(x) => match x {
Left => Angle::from_degrees(270.).radians(),
Right => Angle::from_degrees(90.).radians(),
},
LineDirection::Vertical(y) => match y {
Top => Angle::from_degrees(0.).radians(),
Bottom => Angle::from_degrees(180.).radians(),
},
LineDirection::Corner(horizontal, vertical) => {
// This the angle for one of the diagonals of the box. Our angle
// will either be this one, this one + PI, or one of the other
// two perpendicular angles.
let atan = (size.height.to_f32_px() / size.width.to_f32_px()).atan();
match (horizontal, vertical) {
(Right, Bottom) => ::std::f32::consts::PI - atan,
(Left, Bottom) => ::std::f32::consts::PI + atan,
(Right, Top) => atan,
(Left, Top) => -atan,
}
},
};
// Get correct gradient line length, based on:
// https://drafts.csswg.org/css-images-3/#linear-gradients
let dir = Point2D::new(angle.sin(), -angle.cos());
let line_length =
(dir.x * size.width.to_f32_px()).abs() + (dir.y * size.height.to_f32_px()).abs();
let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();
// This is the vector between the center and the ending point; i.e. half
// of the distance between the starting point and the ending point.
let delta = Vector2D::new(
Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0),
);
// This is the length of the gradient line.
let length = Au::from_f32_px((delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));
let mut builder = convert_gradient_stops(style, stops, length);
let center = Point2D::new(size.width / 2, size.height / 2);
(
builder.gradient(
(center - delta).to_layout(),
(center + delta).to_layout(),
extend_mode(repeating),
),
builder.into_stops(),
)
}
pub fn radial(
style: &ComputedValues,
size: Size2D<Au>,
stops: &[GradientItem<Color, LengthPercentage>],
shape: &EndingShape,
center: &Position,
_color_interpolation_method: &ColorInterpolationMethod,
flags: GradientFlags,
) -> (RadialGradient, Vec<GradientStop>) {
let repeating = flags.contains(GradientFlags::REPEATING);
let center = Point2D::new(
center.horizontal.to_used_value(size.width),
center.vertical.to_used_value(size.height),
);
let radius = match shape {
EndingShape::Circle(Circle::Radius(length)) => {
let length = Au::from(*length);
Size2D::new(length, length)
},
EndingShape::Circle(Circle::Extent(extent)) => circle_size_keyword(*extent, &size, &center),
EndingShape::Ellipse(Ellipse::Radii(x, y)) => {
Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height))
},
EndingShape::Ellipse(Ellipse::Extent(extent)) => {
ellipse_size_keyword(*extent, &size, &center)
},
};
let mut builder = convert_gradient_stops(style, stops, radius.width);
(
builder.radial_gradient(
center.to_layout(),
radius.to_layout(),
extend_mode(repeating),
),
builder.into_stops(),
)
}

View file

@ -1,761 +0,0 @@
/* 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/. */
//! Servo heavily uses display lists, which are retained-mode lists of painting commands to
//! perform. Using a list instead of painting elements in immediate mode allows transforms, hit
//! testing, and invalidation to be performed using the same primitives as painting. It also allows
//! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw.
//!
//! Display items describe relatively high-level drawing operations (for example, entire borders
//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
//! low-level drawing primitives.
use std::cmp::Ordering;
use std::collections::HashMap;
use std::{f32, fmt};
use base::id::PipelineId;
use base::print_tree::PrintTree;
use embedder_traits::Cursor;
use euclid::{SideOffsets2D, Vector2D};
use pixels::Image;
use serde::Serialize;
use servo_geometry::MaxRect;
use style::computed_values::_servo_top_layer::T as InTopLayer;
pub use style::dom::OpaqueNode;
use webrender_api as wr;
use webrender_api::units::{LayoutPixel, LayoutRect, LayoutTransform};
use webrender_api::{
BorderRadius, ClipChainId, ClipMode, CommonItemProperties, ComplexClipRegion, ExternalScrollId,
FilterOp, GlyphInstance, GradientStop, ImageKey, MixBlendMode, PrimitiveFlags, Shadow,
SpatialId, StickyOffsetBounds, TransformStyle,
};
use webrender_traits::display_list::{AxesScrollSensitivity, ScrollTreeNodeId};
use super::StackingContextId;
/// The factor that we multiply the blur radius by in order to inflate the boundaries of display
/// items that involve a blur. This ensures that the display item boundaries include all the ink.
pub static BLUR_INFLATION_FACTOR: i32 = 3;
/// An index into the vector of ClipScrollNodes. During WebRender conversion these nodes
/// are given ClipIds.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct ClipScrollNodeIndex(usize);
impl ClipScrollNodeIndex {
pub fn root_scroll_node() -> ClipScrollNodeIndex {
ClipScrollNodeIndex(1)
}
pub fn root_reference_frame() -> ClipScrollNodeIndex {
ClipScrollNodeIndex(0)
}
pub fn new(index: usize) -> ClipScrollNodeIndex {
assert_ne!(index, 0, "Use the root_reference_frame constructor");
assert_ne!(index, 1, "Use the root_scroll_node constructor");
ClipScrollNodeIndex(index)
}
pub fn is_root_scroll_node(&self) -> bool {
*self == Self::root_scroll_node()
}
pub fn to_define_item(&self) -> DisplayItem {
DisplayItem::DefineClipScrollNode(Box::new(DefineClipScrollNodeItem {
base: BaseDisplayItem::empty(),
node_index: *self,
}))
}
pub fn to_index(self) -> usize {
self.0
}
}
/// A set of indices into the clip scroll node vector for a given item.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct ClippingAndScrolling {
pub scrolling: ClipScrollNodeIndex,
pub clipping: Option<ClipScrollNodeIndex>,
}
impl ClippingAndScrolling {
pub fn simple(scrolling: ClipScrollNodeIndex) -> ClippingAndScrolling {
ClippingAndScrolling {
scrolling,
clipping: None,
}
}
pub fn new(scrolling: ClipScrollNodeIndex, clipping: ClipScrollNodeIndex) -> Self {
ClippingAndScrolling {
scrolling,
clipping: Some(clipping),
}
}
}
#[derive(Serialize)]
pub struct DisplayList {
pub list: Vec<DisplayItem>,
pub clip_scroll_nodes: Vec<ClipScrollNode>,
}
impl DisplayList {
/// Return the bounds of this display list based on the dimensions of the root
/// stacking context.
pub fn bounds(&self) -> LayoutRect {
match self.list.first() {
Some(DisplayItem::PushStackingContext(item)) => item.stacking_context.bounds,
Some(_) => unreachable!("Root element of display list not stacking context."),
None => LayoutRect::zero(),
}
}
pub fn print(&self) {
let mut print_tree = PrintTree::new("Display List".to_owned());
self.print_with_tree(&mut print_tree);
}
pub fn print_with_tree(&self, print_tree: &mut PrintTree) {
print_tree.new_level("ClipScrollNodes".to_owned());
for node in &self.clip_scroll_nodes {
print_tree.add_item(format!("{:?}", node));
}
print_tree.end_level();
print_tree.new_level("Items".to_owned());
for item in &self.list {
print_tree.add_item(format!(
"{:?} StackingContext: {:?} {:?}",
item,
item.base().stacking_context_id,
item.clipping_and_scrolling()
));
}
print_tree.end_level();
}
}
/// Display list sections that make up a stacking context. Each section here refers
/// to the steps in CSS 2.1 Appendix E.
///
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum DisplayListSection {
BackgroundAndBorders,
BlockBackgroundsAndBorders,
Content,
Outlines,
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum StackingContextType {
Real,
PseudoPositioned,
PseudoFloat,
}
#[derive(Clone, Serialize)]
/// Represents one CSS stacking context, which may or may not have a hardware layer.
pub struct StackingContext {
/// The ID of this StackingContext for uniquely identifying it.
pub id: StackingContextId,
/// The type of this StackingContext. Used for collecting and sorting.
pub context_type: StackingContextType,
/// The position and size of this stacking context.
pub bounds: LayoutRect,
/// The overflow rect for this stacking context in its coordinate system.
pub overflow: LayoutRect,
/// The `z-index` for this stacking context.
pub z_index: i32,
/// Whether this is the top layer.
pub in_top_layer: InTopLayer,
/// CSS filters to be applied to this stacking context (including opacity).
pub filters: Vec<FilterOp>,
/// The blend mode with which this stacking context blends with its backdrop.
pub mix_blend_mode: MixBlendMode,
/// A transform to be applied to this stacking context.
pub transform: Option<LayoutTransform>,
/// The transform style of this stacking context.
pub transform_style: TransformStyle,
/// The perspective matrix to be applied to children.
pub perspective: Option<LayoutTransform>,
/// The clip and scroll info for this StackingContext.
pub parent_clipping_and_scrolling: ClippingAndScrolling,
/// The index of the reference frame that this stacking context establishes.
pub established_reference_frame: Option<ClipScrollNodeIndex>,
}
impl StackingContext {
/// Creates a new stacking context.
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn new(
id: StackingContextId,
context_type: StackingContextType,
bounds: LayoutRect,
overflow: LayoutRect,
z_index: i32,
in_top_layer: InTopLayer,
filters: Vec<FilterOp>,
mix_blend_mode: MixBlendMode,
transform: Option<LayoutTransform>,
transform_style: TransformStyle,
perspective: Option<LayoutTransform>,
parent_clipping_and_scrolling: ClippingAndScrolling,
established_reference_frame: Option<ClipScrollNodeIndex>,
) -> StackingContext {
if let Some(ref t) = transform {
// These are used as scale values by webrender, and it can't handle
// divisors of 0 when scaling.
assert_ne!(t.m11, 0.);
assert_ne!(t.m22, 0.);
}
StackingContext {
id,
context_type,
bounds,
overflow,
z_index,
in_top_layer,
filters,
mix_blend_mode,
transform,
transform_style,
perspective,
parent_clipping_and_scrolling,
established_reference_frame,
}
}
#[inline]
pub fn root() -> StackingContext {
StackingContext::new(
StackingContextId::root(),
StackingContextType::Real,
LayoutRect::zero(),
LayoutRect::zero(),
0,
InTopLayer::None,
vec![],
MixBlendMode::Normal,
None,
TransformStyle::Flat,
None,
ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()),
None,
)
}
pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) {
let mut base_item = BaseDisplayItem::empty();
base_item.stacking_context_id = self.id;
base_item.clipping_and_scrolling = self.parent_clipping_and_scrolling;
let pop_item = DisplayItem::PopStackingContext(Box::new(PopStackingContextItem {
base: base_item.clone(),
stacking_context_id: self.id,
established_reference_frame: self.established_reference_frame.is_some(),
}));
let push_item = DisplayItem::PushStackingContext(Box::new(PushStackingContextItem {
base: base_item,
stacking_context: self,
}));
(push_item, pop_item)
}
}
impl Ord for StackingContext {
fn cmp(&self, other: &Self) -> Ordering {
if self.in_top_layer == InTopLayer::Top {
if other.in_top_layer == InTopLayer::Top {
return Ordering::Equal;
} else {
return Ordering::Greater;
}
} else if other.in_top_layer == InTopLayer::Top {
return Ordering::Less;
}
if self.z_index != 0 || other.z_index != 0 {
return self.z_index.cmp(&other.z_index);
}
match (self.context_type, other.context_type) {
(StackingContextType::PseudoFloat, StackingContextType::PseudoFloat) => Ordering::Equal,
(StackingContextType::PseudoFloat, _) => Ordering::Less,
(_, StackingContextType::PseudoFloat) => Ordering::Greater,
(_, _) => Ordering::Equal,
}
}
}
impl PartialOrd for StackingContext {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for StackingContext {}
impl PartialEq for StackingContext {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl fmt::Debug for StackingContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let type_string = if self.context_type == StackingContextType::Real {
"StackingContext"
} else {
"Pseudo-StackingContext"
};
write!(
f,
"{} at {:?} with overflow {:?}: {:?}",
type_string, self.bounds, self.overflow, self.id
)
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct StickyFrameData {
pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
pub vertical_offset_bounds: StickyOffsetBounds,
pub horizontal_offset_bounds: StickyOffsetBounds,
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub enum ClipType {
Rounded(ComplexClipRegion),
Rect,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ClipScrollNodeType {
Placeholder,
ScrollFrame(AxesScrollSensitivity, ExternalScrollId),
StickyFrame(StickyFrameData),
Clip(ClipType),
}
/// Defines a clip scroll node.
#[derive(Clone, Debug, Serialize)]
pub struct ClipScrollNode {
/// The index of the parent of this ClipScrollNode.
pub parent_index: ClipScrollNodeIndex,
/// The position of this scroll root's frame in the parent stacking context.
pub clip: ClippingRegion,
/// The rect of the contents that can be scrolled inside of the scroll root.
pub content_rect: LayoutRect,
/// The type of this ClipScrollNode.
pub node_type: ClipScrollNodeType,
/// The WebRender spatial id of this node assigned during WebRender conversion.
pub scroll_node_id: Option<ScrollTreeNodeId>,
/// The WebRender clip id of this node assigned during WebRender conversion.
pub clip_chain_id: Option<ClipChainId>,
}
impl ClipScrollNode {
pub fn placeholder() -> ClipScrollNode {
ClipScrollNode {
parent_index: ClipScrollNodeIndex(0),
clip: ClippingRegion::from_rect(LayoutRect::zero()),
content_rect: LayoutRect::zero(),
node_type: ClipScrollNodeType::Placeholder,
scroll_node_id: None,
clip_chain_id: None,
}
}
pub fn is_placeholder(&self) -> bool {
self.node_type == ClipScrollNodeType::Placeholder
}
pub fn rounded(
clip_rect: LayoutRect,
radii: BorderRadius,
parent_index: ClipScrollNodeIndex,
) -> ClipScrollNode {
let complex_region = ComplexClipRegion {
rect: clip_rect,
radii,
mode: ClipMode::Clip,
};
ClipScrollNode {
parent_index,
clip: ClippingRegion::from_rect(clip_rect),
content_rect: LayoutRect::zero(), // content_rect isn't important for clips.
node_type: ClipScrollNodeType::Clip(ClipType::Rounded(complex_region)),
scroll_node_id: None,
clip_chain_id: None,
}
}
}
/// One drawing command in the list.
#[derive(Clone, Serialize)]
pub enum DisplayItem {
Rectangle(Box<CommonDisplayItem<wr::RectangleDisplayItem>>),
Text(Box<CommonDisplayItem<wr::TextDisplayItem, Vec<GlyphInstance>>>),
Image(Box<CommonDisplayItem<wr::ImageDisplayItem>>),
RepeatingImage(Box<CommonDisplayItem<wr::RepeatingImageDisplayItem>>),
Border(Box<CommonDisplayItem<wr::BorderDisplayItem, Vec<GradientStop>>>),
Gradient(Box<CommonDisplayItem<wr::GradientDisplayItem, Vec<GradientStop>>>),
RadialGradient(Box<CommonDisplayItem<wr::RadialGradientDisplayItem, Vec<GradientStop>>>),
Line(Box<CommonDisplayItem<wr::LineDisplayItem>>),
BoxShadow(Box<CommonDisplayItem<wr::BoxShadowDisplayItem>>),
PushTextShadow(Box<PushTextShadowDisplayItem>),
PopAllTextShadows(Box<PopAllTextShadowsDisplayItem>),
Iframe(Box<IframeDisplayItem>),
PushStackingContext(Box<PushStackingContextItem>),
PopStackingContext(Box<PopStackingContextItem>),
DefineClipScrollNode(Box<DefineClipScrollNodeItem>),
}
/// Information common to all display items.
#[derive(Clone, Serialize)]
pub struct BaseDisplayItem {
/// Metadata attached to this display item.
pub metadata: DisplayItemMetadata,
/// The clip rectangle to use for this item.
pub clip_rect: LayoutRect,
/// The section of the display list that this item belongs to.
pub section: DisplayListSection,
/// The id of the stacking context this item belongs to.
pub stacking_context_id: StackingContextId,
/// The clip and scroll info for this item.
pub clipping_and_scrolling: ClippingAndScrolling,
}
impl BaseDisplayItem {
#[inline(always)]
pub fn new(
metadata: DisplayItemMetadata,
clip_rect: LayoutRect,
section: DisplayListSection,
stacking_context_id: StackingContextId,
clipping_and_scrolling: ClippingAndScrolling,
) -> BaseDisplayItem {
BaseDisplayItem {
metadata,
clip_rect,
section,
stacking_context_id,
clipping_and_scrolling,
}
}
#[inline(always)]
pub fn empty() -> BaseDisplayItem {
BaseDisplayItem {
metadata: DisplayItemMetadata {
node: OpaqueNode(0),
unique_id: 0,
cursor: None,
},
// Create a rectangle of maximal size.
clip_rect: LayoutRect::max_rect(),
section: DisplayListSection::Content,
stacking_context_id: StackingContextId::root(),
clipping_and_scrolling: ClippingAndScrolling::simple(
ClipScrollNodeIndex::root_scroll_node(),
),
}
}
}
pub fn empty_common_item_properties() -> CommonItemProperties {
CommonItemProperties {
clip_rect: LayoutRect::max_rect(),
clip_chain_id: ClipChainId::INVALID,
spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()),
flags: PrimitiveFlags::empty(),
}
}
/// A clipping region for a display item. Currently, this can describe rectangles, rounded
/// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms
/// are not supported because those are handled by the higher-level `StackingContext` abstraction.
#[derive(Clone, PartialEq, Serialize)]
pub struct ClippingRegion {
/// The main rectangular region. This does not include any corners.
pub main: LayoutRect,
}
impl ClippingRegion {
/// Returns an empty clipping region that, if set, will result in no pixels being visible.
#[inline]
pub fn empty() -> ClippingRegion {
ClippingRegion {
main: LayoutRect::zero(),
}
}
/// Returns an all-encompassing clipping region that clips no pixels out.
#[inline]
pub fn max() -> ClippingRegion {
ClippingRegion {
main: LayoutRect::max_rect(),
}
}
/// Returns a clipping region that represents the given rectangle.
#[inline]
pub fn from_rect(rect: LayoutRect) -> ClippingRegion {
ClippingRegion { main: rect }
}
}
impl fmt::Debug for ClippingRegion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if *self == ClippingRegion::max() {
write!(f, "ClippingRegion::Max")
} else if *self == ClippingRegion::empty() {
write!(f, "ClippingRegion::Empty")
} else {
write!(f, "ClippingRegion(Rect={:?})", self.main,)
}
}
}
/// Metadata attached to each display item. This is useful for performing auxiliary threads with
/// the display list involving hit testing: finding the originating DOM node and determining the
/// cursor to use when the element is hovered over.
#[derive(Clone, Copy, Serialize)]
pub struct DisplayItemMetadata {
/// The DOM node from which this display item originated.
pub node: OpaqueNode,
/// The unique fragment id of the fragment of this item.
pub unique_id: u64,
/// The value of the `cursor` property when the mouse hovers over this display item. If `None`,
/// this display item is ineligible for pointer events (`pointer-events: none`).
pub cursor: Option<Cursor>,
}
#[derive(Clone, Eq, PartialEq, Serialize)]
pub enum TextOrientation {
Upright,
SidewaysLeft,
SidewaysRight,
}
/// Paints an iframe.
#[derive(Clone, Serialize)]
pub struct IframeDisplayItem {
pub base: BaseDisplayItem,
pub iframe: PipelineId,
pub bounds: LayoutRect,
}
#[derive(Clone, Serialize)]
pub struct CommonDisplayItem<T, U = ()> {
pub base: BaseDisplayItem,
pub item: T,
pub data: U,
}
impl<T> CommonDisplayItem<T> {
pub fn new(base: BaseDisplayItem, item: T) -> Box<CommonDisplayItem<T>> {
Box::new(CommonDisplayItem {
base,
item,
data: (),
})
}
}
impl<T, U> CommonDisplayItem<T, U> {
pub fn with_data(base: BaseDisplayItem, item: T, data: U) -> Box<CommonDisplayItem<T, U>> {
Box::new(CommonDisplayItem { base, item, data })
}
}
/// Defines a text shadow that affects all items until the paired PopTextShadow.
#[derive(Clone, Serialize)]
pub struct PushTextShadowDisplayItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub shadow: Shadow,
}
/// Defines a text shadow that affects all items until the next PopTextShadow.
#[derive(Clone, Serialize)]
pub struct PopAllTextShadowsDisplayItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
}
/// Defines a stacking context.
#[derive(Clone, Serialize)]
pub struct PushStackingContextItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub stacking_context: StackingContext,
}
/// Defines a stacking context.
#[derive(Clone, Serialize)]
pub struct PopStackingContextItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
pub stacking_context_id: StackingContextId,
pub established_reference_frame: bool,
}
/// Starts a group of items inside a particular scroll root.
#[derive(Clone, Serialize)]
pub struct DefineClipScrollNodeItem {
/// Fields common to all display items.
pub base: BaseDisplayItem,
/// The scroll root that this item starts.
pub node_index: ClipScrollNodeIndex,
}
impl DisplayItem {
pub fn base(&self) -> &BaseDisplayItem {
match *self {
DisplayItem::Rectangle(ref rect) => &rect.base,
DisplayItem::Text(ref text) => &text.base,
DisplayItem::Image(ref image_item) => &image_item.base,
DisplayItem::RepeatingImage(ref image_item) => &image_item.base,
DisplayItem::Border(ref border) => &border.base,
DisplayItem::Gradient(ref gradient) => &gradient.base,
DisplayItem::RadialGradient(ref gradient) => &gradient.base,
DisplayItem::Line(ref line) => &line.base,
DisplayItem::BoxShadow(ref box_shadow) => &box_shadow.base,
DisplayItem::PushTextShadow(ref push_text_shadow) => &push_text_shadow.base,
DisplayItem::PopAllTextShadows(ref pop_text_shadow) => &pop_text_shadow.base,
DisplayItem::Iframe(ref iframe) => &iframe.base,
DisplayItem::PushStackingContext(ref stacking_context) => &stacking_context.base,
DisplayItem::PopStackingContext(ref item) => &item.base,
DisplayItem::DefineClipScrollNode(ref item) => &item.base,
}
}
pub fn clipping_and_scrolling(&self) -> ClippingAndScrolling {
self.base().clipping_and_scrolling
}
pub fn stacking_context_id(&self) -> StackingContextId {
self.base().stacking_context_id
}
pub fn section(&self) -> DisplayListSection {
self.base().section
}
pub fn bounds(&self) -> LayoutRect {
match *self {
DisplayItem::Rectangle(ref item) => item.item.common.clip_rect,
DisplayItem::Text(ref item) => item.item.bounds,
DisplayItem::Image(ref item) => item.item.bounds,
DisplayItem::RepeatingImage(ref item) => item.item.bounds,
DisplayItem::Border(ref item) => item.item.bounds,
DisplayItem::Gradient(ref item) => item.item.bounds,
DisplayItem::RadialGradient(ref item) => item.item.bounds,
DisplayItem::Line(ref item) => item.item.area,
DisplayItem::BoxShadow(ref item) => item.item.box_bounds,
DisplayItem::PushTextShadow(_) => LayoutRect::zero(),
DisplayItem::PopAllTextShadows(_) => LayoutRect::zero(),
DisplayItem::Iframe(ref item) => item.bounds,
DisplayItem::PushStackingContext(ref item) => item.stacking_context.bounds,
DisplayItem::PopStackingContext(_) => LayoutRect::zero(),
DisplayItem::DefineClipScrollNode(_) => LayoutRect::zero(),
}
}
}
impl fmt::Debug for DisplayItem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let DisplayItem::PushStackingContext(ref item) = *self {
return write!(f, "PushStackingContext({:?})", item.stacking_context);
}
if let DisplayItem::PopStackingContext(ref item) = *self {
return write!(f, "PopStackingContext({:?}", item.stacking_context_id);
}
if let DisplayItem::DefineClipScrollNode(ref item) = *self {
return write!(f, "DefineClipScrollNode({:?}", item.node_index);
}
write!(
f,
"{} @ {:?} {:?}",
match *self {
DisplayItem::Rectangle(_) => "Rectangle",
DisplayItem::Text(_) => "Text",
DisplayItem::Image(_) => "Image",
DisplayItem::RepeatingImage(_) => "RepeatingImage",
DisplayItem::Border(_) => "Border",
DisplayItem::Gradient(_) => "Gradient",
DisplayItem::RadialGradient(_) => "RadialGradient",
DisplayItem::Line(_) => "Line",
DisplayItem::BoxShadow(_) => "BoxShadow",
DisplayItem::PushTextShadow(_) => "PushTextShadow",
DisplayItem::PopAllTextShadows(_) => "PopTextShadow",
DisplayItem::Iframe(_) => "Iframe",
DisplayItem::PushStackingContext(_) |
DisplayItem::PopStackingContext(_) |
DisplayItem::DefineClipScrollNode(_) => "",
},
self.bounds(),
self.base().clip_rect
)
}
}
#[derive(Clone, Copy, Serialize)]
pub struct WebRenderImageInfo {
pub width: u32,
pub height: u32,
pub key: Option<ImageKey>,
}
impl WebRenderImageInfo {
#[inline]
pub fn from_image(image: &Image) -> WebRenderImageInfo {
WebRenderImageInfo {
width: image.width,
height: image.height,
key: image.id,
}
}
}
/// The type of the scroll offset list. This is only populated if WebRender is in use.
pub type ScrollOffsetMap = HashMap<ExternalScrollId, Vector2D<f32, LayoutPixel>>;

View file

@ -1,41 +0,0 @@
/* 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 malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
pub use self::builder::{
BorderPaintingMode, DisplayListBuildState, IndexableText, StackingContextCollectionFlags,
StackingContextCollectionState,
};
pub use self::conversions::{FilterToLayout, ToLayout};
mod background;
mod border;
mod builder;
pub(crate) mod conversions;
mod gradient;
pub mod items;
mod webrender_helpers;
/// A unique ID for every stacking context.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct StackingContextId(
/// The identifier for this StackingContext, derived from the Flow's memory address
/// and fragment type. As a space optimization, these are combined into a single word.
pub u64,
);
impl StackingContextId {
/// Returns the stacking context ID for the outer document/layout root.
#[inline]
pub fn root() -> StackingContextId {
StackingContextId(0)
}
pub fn next(&self) -> StackingContextId {
let StackingContextId(id) = *self;
StackingContextId(id + 1)
}
}

View file

@ -1,553 +0,0 @@
/* 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/. */
// TODO(gw): This contains helper traits and implementations for converting Servo display lists
// into WebRender display lists. In the future, this step should be completely removed.
// This might be achieved by sharing types between WR and Servo display lists, or
// completely converting layout to directly generate WebRender display lists, for example.
use base::WebRenderEpochToU16;
use base::id::PipelineId;
use log::trace;
use webrender_api::units::{LayoutPoint, LayoutSize, LayoutVector2D};
use webrender_api::{
self, ClipChainId, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem,
DisplayListBuilder, Epoch, HasScrollLinkedEffect, PrimitiveFlags, PropertyBinding, RasterSpace,
ReferenceFrameKind, SpaceAndClipInfo, SpatialId, SpatialTreeItemKey,
};
use webrender_traits::display_list::{
AxesScrollSensitivity, CompositorDisplayListInfo, ScrollSensitivity, ScrollTreeNodeId,
ScrollableNodeInfo,
};
use crate::display_list::items::{
BaseDisplayItem, ClipScrollNode, ClipScrollNodeType, ClipType, DisplayItem, DisplayList,
StackingContextType,
};
struct ClipScrollState<'a> {
clip_scroll_nodes: &'a mut Vec<ClipScrollNode>,
compositor_info: CompositorDisplayListInfo,
stacking_context_offset: Vec<LayoutVector2D>,
}
impl<'a> ClipScrollState<'a> {
fn new(
clip_scroll_nodes: &'a mut Vec<ClipScrollNode>,
compositor_info: CompositorDisplayListInfo,
) -> Self {
let mut state = ClipScrollState {
clip_scroll_nodes,
compositor_info,
stacking_context_offset: Vec::new(),
};
// We need to register the WebRender root reference frame and root scroll node ids
// here manually, because WebRender and the CompositorDisplayListInfo create them
// automatically. We also follow the "old" WebRender API for clip/scroll for now,
// hence both arrays are initialized based on FIRST_SPATIAL_NODE_INDEX, while
// FIRST_CLIP_NODE_INDEX is not taken into account.
state.clip_scroll_nodes[0].scroll_node_id =
Some(state.compositor_info.root_reference_frame_id);
state.clip_scroll_nodes[1].scroll_node_id = Some(state.compositor_info.root_scroll_node_id);
let root_clip_chain = ClipChainId::INVALID;
state.add_clip_node_mapping(0, root_clip_chain);
state.add_clip_node_mapping(1, root_clip_chain);
state
}
fn webrender_clip_id_for_index(&mut self, index: usize) -> ClipChainId {
self.clip_scroll_nodes[index]
.clip_chain_id
.expect("Tried to access WebRender ClipId before definining it.")
}
fn webrender_spatial_id_for_index(&mut self, index: usize) -> SpatialId {
self.clip_scroll_nodes[index]
.scroll_node_id
.expect("Tried to use WebRender parent SpatialId before it was defined.")
.spatial_id
}
fn add_clip_node_mapping(&mut self, index: usize, webrender_id: ClipChainId) {
self.clip_scroll_nodes[index].clip_chain_id = Some(webrender_id);
}
fn scroll_node_id_from_index(&self, index: usize) -> ScrollTreeNodeId {
self.clip_scroll_nodes[index]
.scroll_node_id
.expect("Tried to use WebRender parent SpatialId before it was defined.")
}
fn register_spatial_node(
&mut self,
index: usize,
spatial_id: SpatialId,
parent_index: Option<usize>,
scroll_info: Option<ScrollableNodeInfo>,
) {
let parent_scroll_node_id = parent_index.map(|index| self.scroll_node_id_from_index(index));
self.clip_scroll_nodes[index].scroll_node_id =
Some(self.compositor_info.scroll_tree.add_scroll_tree_node(
parent_scroll_node_id.as_ref(),
spatial_id,
scroll_info,
));
}
fn add_spatial_node_mapping_to_parent_index(&mut self, index: usize, parent_index: usize) {
self.clip_scroll_nodes[index].scroll_node_id =
self.clip_scroll_nodes[parent_index].scroll_node_id
}
pub fn define_clip_chain<I>(
&self,
builder: &mut DisplayListBuilder,
parent: ClipChainId,
clips: I,
) -> ClipChainId
where
I: IntoIterator<Item = ClipId>,
I::IntoIter: ExactSizeIterator + Clone,
{
// We use INVALID to mean "no clipping", but that cannot be passed as an argument
// to `define_clip_chain()`, so it must be converted into `None`.
let parent = match parent {
ClipChainId::INVALID => None,
parent => Some(parent),
};
builder.define_clip_chain(parent, clips)
}
fn stacking_context_offset(&self) -> LayoutVector2D {
self.stacking_context_offset
.last()
.cloned()
.unwrap_or_default()
}
fn push_stacking_context_offset(&mut self, offset: LayoutVector2D) {
self.stacking_context_offset.push(offset);
}
fn pop_stacking_context_offset(&mut self) {
self.stacking_context_offset.pop();
}
}
/// Contentful paint, for the purpose of
/// <https://w3c.github.io/paint-timing/#first-contentful-paint>
/// (i.e. the display list contains items of type text,
/// image, non-white canvas or SVG). Used by metrics.
pub struct IsContentful(pub bool);
impl DisplayList {
pub fn convert_to_webrender(
&mut self,
pipeline_id: PipelineId,
viewport_size: LayoutSize,
epoch: Epoch,
dump_display_list: bool,
) -> (DisplayListBuilder, CompositorDisplayListInfo, IsContentful) {
let webrender_pipeline = pipeline_id.into();
let mut builder = DisplayListBuilder::new(webrender_pipeline);
builder.begin();
if dump_display_list {
builder.dump_serialized_display_list();
}
let content_size = self.bounds().size();
let mut state = ClipScrollState::new(
&mut self.clip_scroll_nodes,
CompositorDisplayListInfo::new(
viewport_size,
content_size,
webrender_pipeline,
epoch,
AxesScrollSensitivity {
x: ScrollSensitivity::ScriptAndInputEvents,
y: ScrollSensitivity::ScriptAndInputEvents,
},
),
);
let mut is_contentful = IsContentful(false);
for item in &mut self.list {
is_contentful.0 |= item.convert_to_webrender(&mut state, &mut builder).0;
}
(builder, state.compositor_info, is_contentful)
}
}
impl DisplayItem {
fn get_spatial_tree_item_key(
&self,
builder: &DisplayListBuilder,
node_index: usize,
) -> SpatialTreeItemKey {
let pipeline_tag = ((builder.pipeline_id.0 as u64) << 32) | builder.pipeline_id.1 as u64;
SpatialTreeItemKey::new(pipeline_tag, node_index as u64)
}
fn convert_to_webrender(
&mut self,
state: &mut ClipScrollState,
builder: &mut DisplayListBuilder,
) -> IsContentful {
// Note: for each time of a display item, if we register one of `clip_ids` or `spatial_ids`,
// we also register the other one as inherited from the current state or the stack.
// This is not an ideal behavior, but it is compatible with the old WebRender model
// of the clip-scroll tree.
let clip_and_scroll_indices = self.base().clipping_and_scrolling;
trace!("converting {:?}", clip_and_scroll_indices);
let current_scrolling_index = clip_and_scroll_indices.scrolling.to_index();
let current_scroll_node_id = state.scroll_node_id_from_index(current_scrolling_index);
let internal_clip_id = clip_and_scroll_indices
.clipping
.unwrap_or(clip_and_scroll_indices.scrolling);
let current_clip_chain_id = state.webrender_clip_id_for_index(internal_clip_id.to_index());
let hit_test_bounds = self.bounds().intersection(&self.base().clip_rect);
let stacking_context_offset = state.stacking_context_offset();
let build_common_item_properties = |base: &BaseDisplayItem| {
CommonItemProperties {
clip_rect: base.clip_rect.translate(stacking_context_offset),
spatial_id: current_scroll_node_id.spatial_id,
clip_chain_id: current_clip_chain_id,
// TODO(gw): Make use of the WR backface visibility functionality.
flags: PrimitiveFlags::default(),
}
};
let mut push_hit_test = |base: &BaseDisplayItem| {
let bounds = match hit_test_bounds {
Some(bounds) => bounds,
None => return,
}
.translate(stacking_context_offset);
let cursor = match base.metadata.cursor {
Some(cursor) => cursor,
None => return,
};
let hit_test_index = state.compositor_info.add_hit_test_info(
base.metadata.node.0 as u64,
Some(cursor),
current_scroll_node_id,
);
builder.push_hit_test(
bounds,
current_clip_chain_id,
current_scroll_node_id.spatial_id,
PrimitiveFlags::default(),
(hit_test_index as u64, state.compositor_info.epoch.as_u16()),
);
};
match *self {
DisplayItem::Rectangle(ref mut item) => {
let mut rect_item = item.item;
rect_item.common = build_common_item_properties(&item.base);
rect_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::Rectangle(rect_item));
IsContentful(false)
},
DisplayItem::Text(ref mut item) => {
let mut text_item = item.item;
text_item.bounds = text_item.bounds.translate(stacking_context_offset);
text_item.common = build_common_item_properties(&item.base);
push_hit_test(&item.base);
builder.push_text(
&text_item.common,
text_item.bounds,
&item.data,
text_item.font_key,
text_item.color,
text_item.glyph_options,
);
IsContentful(true)
},
DisplayItem::Image(ref mut item) => {
let mut image_item = item.item;
image_item.common = build_common_item_properties(&item.base);
image_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::Image(image_item));
IsContentful(true)
},
DisplayItem::RepeatingImage(ref mut item) => {
let mut image_item = item.item;
image_item.common = build_common_item_properties(&item.base);
image_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::RepeatingImage(image_item));
IsContentful(true)
},
DisplayItem::Border(ref mut item) => {
let mut border_item = item.item;
border_item.common = build_common_item_properties(&item.base);
border_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
if !item.data.is_empty() {
builder.push_stops(item.data.as_ref());
}
builder.push_item(&WrDisplayItem::Border(border_item));
IsContentful(false)
},
DisplayItem::Gradient(ref mut item) => {
let mut gradient_item = item.item;
gradient_item.common = build_common_item_properties(&item.base);
gradient_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_stops(item.data.as_ref());
builder.push_item(&WrDisplayItem::Gradient(gradient_item));
IsContentful(false)
},
DisplayItem::RadialGradient(ref mut item) => {
let mut gradient_item = item.item;
gradient_item.common = build_common_item_properties(&item.base);
gradient_item.bounds = item.item.bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_stops(item.data.as_ref());
builder.push_item(&WrDisplayItem::RadialGradient(gradient_item));
IsContentful(false)
},
DisplayItem::Line(ref mut item) => {
let mut line_item = item.item;
line_item.common = build_common_item_properties(&item.base);
line_item.area = item.item.area.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::Line(line_item));
IsContentful(false)
},
DisplayItem::BoxShadow(ref mut item) => {
let mut shadow_item = item.item;
shadow_item.common = build_common_item_properties(&item.base);
shadow_item.box_bounds = item.item.box_bounds.translate(stacking_context_offset);
push_hit_test(&item.base);
builder.push_item(&WrDisplayItem::BoxShadow(shadow_item));
IsContentful(false)
},
DisplayItem::PushTextShadow(ref mut item) => {
let common = build_common_item_properties(&item.base);
push_hit_test(&item.base);
builder.push_shadow(
&SpaceAndClipInfo {
spatial_id: common.spatial_id,
clip_chain_id: common.clip_chain_id,
},
item.shadow,
true,
);
IsContentful(false)
},
DisplayItem::PopAllTextShadows(_) => {
builder.push_item(&WrDisplayItem::PopAllShadows);
IsContentful(false)
},
DisplayItem::Iframe(ref mut item) => {
let common = build_common_item_properties(&item.base);
push_hit_test(&item.base);
builder.push_iframe(
item.bounds.translate(stacking_context_offset),
common.clip_rect,
&SpaceAndClipInfo {
spatial_id: common.spatial_id,
clip_chain_id: common.clip_chain_id,
},
item.iframe.into(),
true,
);
IsContentful(false)
},
DisplayItem::PushStackingContext(ref item) => {
let stacking_context = &item.stacking_context;
debug_assert_eq!(stacking_context.context_type, StackingContextType::Real);
//let mut info = LayoutPrimitiveInfo::new(stacking_context.bounds);
let mut bounds = stacking_context.bounds;
let spatial_id =
if let Some(frame_index) = stacking_context.established_reference_frame {
let (transform, ref_frame) =
match (stacking_context.transform, stacking_context.perspective) {
(None, Some(p)) => (
p,
ReferenceFrameKind::Perspective {
scrolling_relative_to: None,
},
),
(Some(t), None) => (
t,
ReferenceFrameKind::Transform {
is_2d_scale_translation: false,
should_snap: false,
paired_with_perspective: false,
},
),
(Some(t), Some(p)) => (
p.then(&t),
ReferenceFrameKind::Perspective {
scrolling_relative_to: None,
},
),
(None, None) => unreachable!(),
};
let index = frame_index.to_index();
let new_spatial_id = builder.push_reference_frame(
stacking_context.bounds.min + state.stacking_context_offset(),
current_scroll_node_id.spatial_id,
stacking_context.transform_style,
PropertyBinding::Value(transform),
ref_frame,
self.get_spatial_tree_item_key(builder, index),
);
state.add_clip_node_mapping(index, current_clip_chain_id);
state.register_spatial_node(
index,
new_spatial_id,
Some(current_scrolling_index),
None,
);
bounds.min = LayoutPoint::zero();
new_spatial_id
} else {
current_scroll_node_id.spatial_id
};
// TODO(jdm): WebRender now requires us to create stacking context items
// with the IS_BLEND_CONTAINER flag enabled if any children
// of the stacking context have a blend mode applied.
// This will require additional tracking during layout
// before we start collecting stacking contexts so that
// information will be available when we reach this point.
state.push_stacking_context_offset(
(bounds.min + state.stacking_context_offset()).to_vector(),
);
builder.push_stacking_context(
LayoutPoint::zero(),
spatial_id,
PrimitiveFlags::default(),
None,
stacking_context.transform_style,
stacking_context.mix_blend_mode,
&stacking_context.filters,
&[],
&[],
RasterSpace::Screen,
Default::default(),
None, // snapshot
);
IsContentful(false)
},
DisplayItem::PopStackingContext(ref item) => {
state.pop_stacking_context_offset();
builder.pop_stacking_context();
if item.established_reference_frame {
builder.pop_reference_frame();
}
IsContentful(false)
},
DisplayItem::DefineClipScrollNode(ref mut item) => {
let index = item.node_index.to_index();
let node = state.clip_scroll_nodes[index].clone();
let item_rect = node.clip.main.translate(stacking_context_offset);
let parent_index = node.parent_index.to_index();
let parent_spatial_id = state.webrender_spatial_id_for_index(parent_index);
let parent_clip_chain_id = state.webrender_clip_id_for_index(parent_index);
match node.node_type {
ClipScrollNodeType::Clip(clip_type) => {
let clip_id = match clip_type {
ClipType::Rect => {
builder.define_clip_rect(parent_spatial_id, item_rect)
},
ClipType::Rounded(complex) => {
builder.define_clip_rounded_rect(parent_spatial_id, complex)
},
};
let clip_chain_id =
state.define_clip_chain(builder, parent_clip_chain_id, [clip_id]);
state.add_clip_node_mapping(index, clip_chain_id);
state.add_spatial_node_mapping_to_parent_index(index, parent_index);
},
ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => {
let clip_id = builder.define_clip_rect(parent_spatial_id, item_rect);
let clip_chain_id =
state.define_clip_chain(builder, parent_clip_chain_id, [clip_id]);
state.add_clip_node_mapping(index, clip_chain_id);
let spatial_id = builder.define_scroll_frame(
parent_spatial_id,
external_id,
node.content_rect,
item_rect,
LayoutVector2D::zero(), /* external_scroll_offset */
0, /* scroll_offset_generation */
HasScrollLinkedEffect::No,
self.get_spatial_tree_item_key(builder, index),
);
state.register_spatial_node(
index,
spatial_id,
Some(parent_index),
Some(ScrollableNodeInfo {
external_id,
scrollable_size: node.content_rect.size() - item_rect.size(),
scroll_sensitivity,
offset: LayoutVector2D::zero(),
}),
);
},
ClipScrollNodeType::StickyFrame(ref sticky_data) => {
// TODO: Add define_sticky_frame_with_parent to WebRender.
let id = builder.define_sticky_frame(
parent_spatial_id,
item_rect,
sticky_data.margins,
sticky_data.vertical_offset_bounds,
sticky_data.horizontal_offset_bounds,
LayoutVector2D::zero(), /* previously_applied_offset */
self.get_spatial_tree_item_key(builder, index),
None, /* transform */
);
state.add_clip_node_mapping(index, parent_clip_chain_id);
state.register_spatial_node(index, id, Some(current_scrolling_index), None);
},
ClipScrollNodeType::Placeholder => {
unreachable!("Found DefineClipScrollNode for Placeholder type node.");
},
};
IsContentful(false)
},
}
}
}