diff options
Diffstat (limited to 'components/layout_2020/display_list')
-rw-r--r-- | components/layout_2020/display_list/background.rs | 336 | ||||
-rw-r--r-- | components/layout_2020/display_list/border.rs | 199 | ||||
-rw-r--r-- | components/layout_2020/display_list/builder.rs | 3024 | ||||
-rw-r--r-- | components/layout_2020/display_list/conversions.rs | 167 | ||||
-rw-r--r-- | components/layout_2020/display_list/gradient.rs | 323 | ||||
-rw-r--r-- | components/layout_2020/display_list/items.rs | 795 | ||||
-rw-r--r-- | components/layout_2020/display_list/mod.rs | 19 | ||||
-rw-r--r-- | components/layout_2020/display_list/webrender_helpers.rs | 321 |
8 files changed, 5184 insertions, 0 deletions
diff --git a/components/layout_2020/display_list/background.rs b/components/layout_2020/display_list/background.rs new file mode 100644 index 00000000000..c6fa1691e46 --- /dev/null +++ b/components/layout_2020/display_list/background.rs @@ -0,0 +1,336 @@ +/* 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 crate::display_list::border; +use app_units::Au; +use euclid::{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; + +/// 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, + /// Whether or not the background is fixed to the viewport. + pub fixed: bool, +} + +/// 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. +/// +/// Photos have their resolution as intrinsic size while gradients have +/// no intrinsic size. +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, +) -> 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 fixed = false; + 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 => { + fixed = true; + Rect::new(Point2D::origin(), viewport_size) + }, + }; + + let mut tile_size = compute_background_image_size(bg_size, bounds.size, intrinsic_size); + + 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, + ); + + BackgroundPlacement { + bounds, + tile_size, + tile_spacing, + clip_rect, + clip_radii, + fixed, + } +} + +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 ajust various values to account for tiling. +/// +/// This is done separately for both axes because the repeat keywords may differ. +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); + }, + } +} diff --git a/components/layout_2020/display_list/border.rs b/components/layout_2020/display_list/border.rs new file mode 100644 index 00000000000..fff18fcf6c0 --- /dev/null +++ b/components/layout_2020/display_list/border.rs @@ -0,0 +1,199 @@ +/* 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 crate::display_list::ToLayout; +use app_units::Au; +use euclid::{Rect, SideOffsets2D, Size2D}; +use style::computed_values::border_image_outset::T as BorderImageOutset; +use style::properties::style_structs::Border; +use style::values::computed::NumberOrPercentage; +use style::values::computed::{BorderCornerRadius, BorderImageWidth}; +use style::values::computed::{BorderImageSideWidth, NonNegativeLengthOrNumber}; +use style::values::generics::rect::Rect as StyleRect; +use style::values::generics::NonNegative; +use webrender_api::units::{LayoutSideOffsets, LayoutSize}; +use webrender_api::{BorderRadius, BorderSide, BorderStyle, ColorF, NormalBorder}; + +/// 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: Size2D<Au>) -> Size2D<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: SideOffsets2D<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: SideOffsets2D<Au>) -> SideOffsets2D<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: Size2D<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( + border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>, + width: i32, + height: i32, +) -> SideOffsets2D<i32> { + SideOffsets2D::new( + resolve_percentage(border_image_slice.0, height), + resolve_percentage(border_image_slice.1, width), + resolve_percentage(border_image_slice.2, height), + resolve_percentage(border_image_slice.3, width), + ) +} diff --git a/components/layout_2020/display_list/builder.rs b/components/layout_2020/display_list/builder.rs new file mode 100644 index 00000000000..e6ead2505ca --- /dev/null +++ b/components/layout_2020/display_list/builder.rs @@ -0,0 +1,3024 @@ +/* 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/. */ + +//! Builds display lists from flows and fragments. +//! +//! Other browser engines sometimes call this "painting", but it is more accurately called display +//! list building, as the actual painting does not happen here—only deciding *what* we're going to +//! paint. + +use crate::block::BlockFlow; +use crate::context::LayoutContext; +use crate::display_list::background::{self, get_cyclic}; +use crate::display_list::border; +use crate::display_list::gradient; +use crate::display_list::items::{self, BaseDisplayItem, ClipScrollNode}; +use crate::display_list::items::{ClipScrollNodeIndex, ClipScrollNodeType, ClippingAndScrolling}; +use crate::display_list::items::{ClippingRegion, DisplayItem, DisplayItemMetadata, DisplayList}; +use crate::display_list::items::{CommonDisplayItem, DisplayListSection}; +use crate::display_list::items::{IframeDisplayItem, OpaqueNode}; +use crate::display_list::items::{PopAllTextShadowsDisplayItem, PushTextShadowDisplayItem}; +use crate::display_list::items::{StackingContext, StackingContextType, StickyFrameData}; +use crate::display_list::items::{TextOrientation, WebRenderImageInfo}; +use crate::display_list::ToLayout; +use crate::flow::{BaseFlow, Flow, FlowFlags}; +use crate::flow_ref::FlowRef; +use crate::fragment::SpecificFragmentInfo; +use crate::fragment::{CanvasFragmentSource, CoordinateSystem, Fragment, ScannedTextFragmentInfo}; +use crate::inline::InlineFragmentNodeFlags; +use crate::model::MaybeAuto; +use crate::table_cell::CollapsedBordersForCell; +use app_units::{Au, AU_PER_PX}; +use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg}; +use embedder_traits::Cursor; +use euclid::{rect, Point2D, Rect, SideOffsets2D, Size2D, TypedRect, TypedSize2D}; +use fnv::FnvHashMap; +use gfx::text::glyph::ByteIndex; +use gfx::text::TextRun; +use gfx_traits::{combine_id_with_fragment_type, FragmentType, StackingContextId}; +use ipc_channel::ipc; +use msg::constellation_msg::PipelineId; +use net_traits::image_cache::UsePlaceholder; +use range::Range; +use script_traits::IFrameSize; +use servo_config::opts; +use servo_geometry::{self, MaxRect}; +use std::default::Default; +use std::f32; +use std::mem; +use std::sync::Arc; +use style::computed_values::border_style::T as BorderStyle; +use style::computed_values::overflow_x::T as StyleOverflow; +use style::computed_values::pointer_events::T as PointerEvents; +use style::computed_values::position::T as StylePosition; +use style::computed_values::visibility::T as Visibility; +use style::logical_geometry::{LogicalMargin, LogicalPoint, LogicalRect}; +use style::properties::{style_structs, ComputedValues}; +use style::servo::restyle_damage::ServoRestyleDamage; +use style::values::computed::effects::SimpleShadow; +use style::values::computed::image::{Image, ImageLayer}; +use style::values::computed::{Gradient, LengthOrAuto}; +use style::values::generics::background::BackgroundSize; +use style::values::generics::image::{GradientKind, PaintWorklet}; +use style::values::specified::ui::CursorKind; +use style::values::{Either, RGBA}; +use style_traits::ToCss; +use webrender_api::units::{LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D}; +use webrender_api::{self, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, ColorF}; +use webrender_api::{ColorU, ExternalScrollId, FilterOp, GlyphInstance, ImageRendering, LineStyle}; +use webrender_api::{NinePatchBorder, NinePatchBorderSource, NormalBorder}; +use webrender_api::{ScrollSensitivity, StickyOffsetBounds}; + +static THREAD_TINT_COLORS: [ColorF; 8] = [ + ColorF { + r: 6.0 / 255.0, + g: 153.0 / 255.0, + b: 198.0 / 255.0, + a: 0.7, + }, + ColorF { + r: 255.0 / 255.0, + g: 212.0 / 255.0, + b: 83.0 / 255.0, + a: 0.7, + }, + ColorF { + r: 116.0 / 255.0, + g: 29.0 / 255.0, + b: 109.0 / 255.0, + a: 0.7, + }, + ColorF { + r: 204.0 / 255.0, + g: 158.0 / 255.0, + b: 199.0 / 255.0, + a: 0.7, + }, + ColorF { + r: 242.0 / 255.0, + g: 46.0 / 255.0, + b: 121.0 / 255.0, + a: 0.7, + }, + ColorF { + r: 116.0 / 255.0, + g: 203.0 / 255.0, + b: 196.0 / 255.0, + a: 0.7, + }, + ColorF { + r: 255.0 / 255.0, + g: 249.0 / 255.0, + b: 201.0 / 255.0, + a: 0.7, + }, + ColorF { + r: 137.0 / 255.0, + g: 196.0 / 255.0, + b: 78.0 / 255.0, + a: 0.7, + }, +]; + +pub struct InlineNodeBorderInfo { + is_first_fragment_of_element: bool, + is_last_fragment_of_element: bool, +} + +#[derive(Debug)] +struct StackingContextInfo { + children: Vec<StackingContext>, + clip_scroll_nodes: Vec<ClipScrollNodeIndex>, + real_stacking_context_id: StackingContextId, +} + +impl StackingContextInfo { + fn new(real_stacking_context_id: StackingContextId) -> StackingContextInfo { + StackingContextInfo { + children: Vec::new(), + clip_scroll_nodes: Vec::new(), + real_stacking_context_id, + } + } + + fn take_children(&mut self) -> Vec<StackingContext> { + mem::replace(&mut self.children, Vec::new()) + } +} + +pub struct StackingContextCollectionState { + /// The PipelineId of this stacking context collection. + pub pipeline_id: PipelineId, + + /// The root of the StackingContext tree. + pub root_stacking_context: StackingContext, + + /// StackingContext and ClipScrollNode children for each StackingContext. + stacking_context_info: FnvHashMap<StackingContextId, StackingContextInfo>, + + pub clip_scroll_nodes: Vec<ClipScrollNode>, + + /// The current stacking context id, used to keep track of state when building. + /// recursively building and processing the display list. + pub current_stacking_context_id: StackingContextId, + + /// The current reference frame ClipScrollNodeIndex. + pub current_real_stacking_context_id: StackingContextId, + + /// The next stacking context id that we will assign to a stacking context. + pub next_stacking_context_id: StackingContextId, + + /// The current reference frame id. This is used to assign items to the parent + /// reference frame when we encounter a fixed position stacking context. + pub current_parent_reference_frame_id: ClipScrollNodeIndex, + + /// The current clip and scroll info, used to keep track of state when + /// recursively building and processing the display list. + pub current_clipping_and_scrolling: ClippingAndScrolling, + + /// The clip and scroll info of the first ancestor which defines a containing block. + /// This is necessary because absolutely positioned items should be clipped + /// by their containing block's scroll root. + pub containing_block_clipping_and_scrolling: ClippingAndScrolling, + + /// A stack of clips used to cull display list entries that are outside the + /// rendered region. + pub clip_stack: Vec<Rect<Au>>, + + /// A stack of clips used to cull display list entries that are outside the + /// rendered region, but only collected at containing block boundaries. + pub containing_block_clip_stack: Vec<Rect<Au>>, + + /// The flow parent's content box, used to calculate sticky constraints. + parent_stacking_relative_content_box: Rect<Au>, +} + +impl StackingContextCollectionState { + pub fn new(pipeline_id: PipelineId) -> StackingContextCollectionState { + let root_clip_indices = + ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()); + + let mut stacking_context_info = FnvHashMap::default(); + stacking_context_info.insert( + StackingContextId::root(), + StackingContextInfo::new(StackingContextId::root()), + ); + + // We add two empty nodes to represent the WebRender root reference frame and + // root scroll nodes. WebRender adds these automatically and we add them here + // so that the ids in the array match up with the ones we assign during display + // list building. We ignore these two nodes during conversion to WebRender + // display lists. + let clip_scroll_nodes = vec![ClipScrollNode::placeholder(), ClipScrollNode::placeholder()]; + + StackingContextCollectionState { + pipeline_id: pipeline_id, + root_stacking_context: StackingContext::root(), + stacking_context_info, + clip_scroll_nodes, + current_stacking_context_id: StackingContextId::root(), + current_real_stacking_context_id: StackingContextId::root(), + next_stacking_context_id: StackingContextId::root().next(), + current_parent_reference_frame_id: ClipScrollNodeIndex::root_reference_frame(), + current_clipping_and_scrolling: root_clip_indices, + containing_block_clipping_and_scrolling: root_clip_indices, + clip_stack: Vec::new(), + containing_block_clip_stack: Vec::new(), + parent_stacking_relative_content_box: Rect::zero(), + } + } + + fn allocate_stacking_context_info( + &mut self, + stacking_context_type: StackingContextType, + ) -> StackingContextId { + let next_stacking_context_id = self.next_stacking_context_id.next(); + let allocated_id = + mem::replace(&mut self.next_stacking_context_id, next_stacking_context_id); + + let real_stacking_context_id = match stacking_context_type { + StackingContextType::Real => allocated_id, + _ => self.current_real_stacking_context_id, + }; + + self.stacking_context_info.insert( + allocated_id, + StackingContextInfo::new(real_stacking_context_id), + ); + + allocated_id + } + + fn add_stacking_context( + &mut self, + parent_id: StackingContextId, + stacking_context: StackingContext, + ) { + self.stacking_context_info + .get_mut(&parent_id) + .unwrap() + .children + .push(stacking_context); + } + + fn add_clip_scroll_node(&mut self, clip_scroll_node: ClipScrollNode) -> ClipScrollNodeIndex { + let is_placeholder = clip_scroll_node.is_placeholder(); + + self.clip_scroll_nodes.push(clip_scroll_node); + let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1); + + // If this node is a placeholder node (currently just reference frames), then don't add + // it to the stacking context list. Placeholder nodes are created automatically by + // WebRender and we don't want to explicitly create them in the display list. The node + // is just there to take up a spot in the global list of ClipScrollNodes. + if !is_placeholder { + // We want the scroll root to be defined before any possible item that could use it, + // so we make sure that it is added to the beginning of the parent "real" (non-pseudo) + // stacking context. This ensures that item reordering will not result in an item using + // the scroll root before it is defined. + self.stacking_context_info + .get_mut(&self.current_real_stacking_context_id) + .unwrap() + .clip_scroll_nodes + .push(index); + } + + index + } +} + +pub struct DisplayListBuildState<'a> { + /// A LayoutContext reference important for creating WebRender images. + pub layout_context: &'a LayoutContext<'a>, + + /// The root of the StackingContext tree. + pub root_stacking_context: StackingContext, + + /// StackingContext and ClipScrollNode children for each StackingContext. + stacking_context_info: FnvHashMap<StackingContextId, StackingContextInfo>, + + /// A vector of ClipScrollNodes which will be given ids during WebRender DL conversion. + pub clip_scroll_nodes: Vec<ClipScrollNode>, + + /// The items in this display list. + pub items: FnvHashMap<StackingContextId, Vec<DisplayItem>>, + + /// Whether or not we are processing an element that establishes scrolling overflow. Used + /// to determine what ClipScrollNode to place backgrounds and borders into. + pub processing_scrolling_overflow_element: bool, + + /// The current stacking context id, used to keep track of state when building. + /// recursively building and processing the display list. + pub current_stacking_context_id: StackingContextId, + + /// The current clip and scroll info, used to keep track of state when + /// recursively building and processing the display list. + pub current_clipping_and_scrolling: ClippingAndScrolling, + + /// Vector containing iframe sizes, used to inform the constellation about + /// new iframe sizes + pub iframe_sizes: Vec<IFrameSize>, + + /// Stores text runs to answer text queries used to place a cursor inside text. + pub indexable_text: IndexableText, +} + +impl<'a> DisplayListBuildState<'a> { + pub fn new( + layout_context: &'a LayoutContext, + state: StackingContextCollectionState, + ) -> DisplayListBuildState<'a> { + DisplayListBuildState { + layout_context: layout_context, + root_stacking_context: state.root_stacking_context, + items: FnvHashMap::default(), + stacking_context_info: state.stacking_context_info, + clip_scroll_nodes: state.clip_scroll_nodes, + processing_scrolling_overflow_element: false, + current_stacking_context_id: StackingContextId::root(), + current_clipping_and_scrolling: ClippingAndScrolling::simple( + ClipScrollNodeIndex::root_scroll_node(), + ), + iframe_sizes: Vec::new(), + indexable_text: IndexableText::default(), + } + } + + pub fn add_display_item(&mut self, display_item: DisplayItem) { + let items = self + .items + .entry(display_item.stacking_context_id()) + .or_insert(Vec::new()); + items.push(display_item); + } + + fn add_image_item(&mut self, base: BaseDisplayItem, item: webrender_api::ImageDisplayItem) { + if item.stretch_size == LayoutSize::zero() { + return; + } + self.add_display_item(DisplayItem::Image(CommonDisplayItem::new(base, item))) + } + + fn parent_clip_scroll_node_index(&self, index: ClipScrollNodeIndex) -> ClipScrollNodeIndex { + if index.is_root_scroll_node() { + return index; + } + + self.clip_scroll_nodes[index.to_index()].parent_index + } + + fn is_background_or_border_of_clip_scroll_node(&self, section: DisplayListSection) -> bool { + (section == DisplayListSection::BackgroundAndBorders || + section == DisplayListSection::BlockBackgroundsAndBorders) && + self.processing_scrolling_overflow_element + } + + pub fn create_base_display_item( + &self, + clip_rect: Rect<Au>, + node: OpaqueNode, + cursor: Option<Cursor>, + section: DisplayListSection, + ) -> BaseDisplayItem { + let clipping_and_scrolling = if self.is_background_or_border_of_clip_scroll_node(section) { + ClippingAndScrolling::simple( + self.parent_clip_scroll_node_index(self.current_clipping_and_scrolling.scrolling), + ) + } else { + self.current_clipping_and_scrolling + }; + self.create_base_display_item_with_clipping_and_scrolling( + clip_rect, + node, + cursor, + section, + clipping_and_scrolling, + ) + } + + fn create_base_display_item_with_clipping_and_scrolling( + &self, + clip_rect: Rect<Au>, + node: OpaqueNode, + cursor: Option<Cursor>, + section: DisplayListSection, + clipping_and_scrolling: ClippingAndScrolling, + ) -> BaseDisplayItem { + BaseDisplayItem::new( + DisplayItemMetadata { + node, + // Store cursor id in display list. + pointing: cursor.map(|x| x as u16), + }, + clip_rect.to_layout(), + section, + self.current_stacking_context_id, + clipping_and_scrolling, + ) + } + + fn add_late_clip_node(&mut self, rect: LayoutRect, radii: BorderRadius) -> ClipScrollNodeIndex { + let mut clip = ClippingRegion::from_rect(rect); + clip.intersect_with_rounded_rect(rect, radii); + + let node = ClipScrollNode { + parent_index: self.current_clipping_and_scrolling.scrolling, + clip, + content_rect: LayoutRect::zero(), // content_rect isn't important for clips. + node_type: ClipScrollNodeType::Clip, + }; + + // We want the scroll root to be defined before any possible item that could use it, + // so we make sure that it is added to the beginning of the parent "real" (non-pseudo) + // stacking context. This ensures that item reordering will not result in an item using + // the scroll root before it is defined. + self.clip_scroll_nodes.push(node); + let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1); + let real_stacking_context_id = + self.stacking_context_info[&self.current_stacking_context_id].real_stacking_context_id; + self.stacking_context_info + .get_mut(&real_stacking_context_id) + .unwrap() + .clip_scroll_nodes + .push(index); + + index + } + + pub fn to_display_list(mut self) -> DisplayList { + let mut list = Vec::new(); + let root_context = mem::replace(&mut self.root_stacking_context, StackingContext::root()); + + self.to_display_list_for_stacking_context(&mut list, root_context); + + DisplayList { + list: list, + clip_scroll_nodes: self.clip_scroll_nodes, + } + } + + fn to_display_list_for_stacking_context( + &mut self, + list: &mut Vec<DisplayItem>, + stacking_context: StackingContext, + ) { + let mut child_items = self + .items + .remove(&stacking_context.id) + .unwrap_or(Vec::new()); + child_items.sort_by(|a, b| a.base().section.cmp(&b.base().section)); + child_items.reverse(); + + let mut info = self + .stacking_context_info + .remove(&stacking_context.id) + .unwrap(); + + info.children.sort(); + + if stacking_context.context_type != StackingContextType::Real { + list.extend( + info.clip_scroll_nodes + .into_iter() + .map(|index| index.to_define_item()), + ); + self.to_display_list_for_items(list, child_items, info.children); + } else { + let (push_item, pop_item) = stacking_context.to_display_list_items(); + list.push(push_item); + list.extend( + info.clip_scroll_nodes + .into_iter() + .map(|index| index.to_define_item()), + ); + self.to_display_list_for_items(list, child_items, info.children); + list.push(pop_item); + } + } + + fn to_display_list_for_items( + &mut self, + list: &mut Vec<DisplayItem>, + mut child_items: Vec<DisplayItem>, + child_stacking_contexts: Vec<StackingContext>, + ) { + // Properly order display items that make up a stacking context. "Steps" here + // refer to the steps in CSS 2.1 Appendix E. + // Steps 1 and 2: Borders and background for the root. + while child_items.last().map_or(false, |child| { + child.section() == DisplayListSection::BackgroundAndBorders + }) { + list.push(child_items.pop().unwrap()); + } + + // Step 3: Positioned descendants with negative z-indices. + let mut child_stacking_contexts = child_stacking_contexts.into_iter().peekable(); + while child_stacking_contexts + .peek() + .map_or(false, |child| child.z_index < 0) + { + let context = child_stacking_contexts.next().unwrap(); + self.to_display_list_for_stacking_context(list, context); + } + + // Step 4: Block backgrounds and borders. + while child_items.last().map_or(false, |child| { + child.section() == DisplayListSection::BlockBackgroundsAndBorders + }) { + list.push(child_items.pop().unwrap()); + } + + // Step 5: Floats. + while child_stacking_contexts.peek().map_or(false, |child| { + child.context_type == StackingContextType::PseudoFloat + }) { + let context = child_stacking_contexts.next().unwrap(); + self.to_display_list_for_stacking_context(list, context); + } + + // Step 6 & 7: Content and inlines that generate stacking contexts. + while child_items.last().map_or(false, |child| { + child.section() == DisplayListSection::Content + }) { + list.push(child_items.pop().unwrap()); + } + + // Step 8 & 9: Positioned descendants with nonnegative, numeric z-indices. + for child in child_stacking_contexts { + self.to_display_list_for_stacking_context(list, child); + } + + // Step 10: Outlines. + for item in child_items.drain(..) { + list.push(item); + } + } + + fn clipping_and_scrolling_scope<R, F: FnOnce(&mut Self) -> R>(&mut self, function: F) -> R { + let previous_clipping_and_scrolling = self.current_clipping_and_scrolling; + let ret = function(self); + self.current_clipping_and_scrolling = previous_clipping_and_scrolling; + ret + } +} + +/// The logical width of an insertion point: at the moment, a one-pixel-wide line. +const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX); + +/// Get the border radius for the rectangle inside of a rounded border. This is useful +/// for building the clip for the content inside the border. +fn build_border_radius_for_inner_rect( + outer_rect: Rect<Au>, + style: &ComputedValues, +) -> BorderRadius { + let radii = border::radii(outer_rect, style.get_border()); + if radii.is_zero() { + return radii; + } + + // Since we are going to using the inner rectangle (outer rectangle minus + // border width), we need to adjust to border radius so that we are smaller + // rectangle with the same border curve. + let border_widths = style.logical_border_width().to_physical(style.writing_mode); + border::inner_radii(radii, border_widths) +} + +impl Fragment { + pub fn collect_stacking_contexts_for_blocklike_fragment( + &mut self, + state: &mut StackingContextCollectionState, + ) -> bool { + match self.specific { + SpecificFragmentInfo::InlineBlock(ref mut block_flow) => { + let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); + block_flow.collect_stacking_contexts(state); + true + }, + SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut block_flow) => { + let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); + block_flow.collect_stacking_contexts(state); + true + }, + SpecificFragmentInfo::InlineAbsolute(ref mut block_flow) => { + let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); + block_flow.collect_stacking_contexts(state); + true + }, + // FIXME: In the future, if #15144 is fixed we can remove this case. See #18510. + SpecificFragmentInfo::TruncatedFragment(ref mut info) => info + .full + .collect_stacking_contexts_for_blocklike_fragment(state), + _ => false, + } + } + + pub fn create_stacking_context_for_inline_block( + &mut self, + base: &BaseFlow, + state: &mut StackingContextCollectionState, + ) -> bool { + self.stacking_context_id = state.allocate_stacking_context_info(StackingContextType::Real); + + let established_reference_frame = if self.can_establish_reference_frame() { + // WebRender currently creates reference frames automatically, so just add + // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame. + self.established_reference_frame = + Some(state.add_clip_scroll_node(ClipScrollNode::placeholder())); + self.established_reference_frame + } else { + None + }; + + let current_stacking_context_id = state.current_stacking_context_id; + let stacking_context = self.create_stacking_context( + self.stacking_context_id, + &base, + StackingContextType::Real, + established_reference_frame, + state.current_clipping_and_scrolling, + ); + state.add_stacking_context(current_stacking_context_id, stacking_context); + true + } + + /// Adds the display items necessary to paint the background of this fragment to the display + /// list if necessary. + fn build_display_list_for_background_if_applicable( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + display_list_section: DisplayListSection, + absolute_bounds: Rect<Au>, + ) { + let background = style.get_background(); + let background_color = style.resolve_color(background.background_color); + // XXXManishearth the below method should ideally use an iterator over + // backgrounds + self.build_display_list_for_background_if_applicable_with_background( + state, + style, + background, + background_color, + display_list_section, + absolute_bounds, + ) + } + + /// Same as build_display_list_for_background_if_applicable, but lets you + /// override the actual background used + fn build_display_list_for_background_if_applicable_with_background( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + background: &style_structs::Background, + background_color: RGBA, + display_list_section: DisplayListSection, + absolute_bounds: Rect<Au>, + ) { + // FIXME: This causes a lot of background colors to be displayed when they are clearly not + // needed. We could use display list optimization to clean this up, but it still seems + // inefficient. What we really want is something like "nearest ancestor element that + // doesn't have a fragment". + + // Quote from CSS Backgrounds and Borders Module Level 3: + // + // > The background color is clipped according to the background-clip value associated + // > with the bottom-most background image layer. + let last_background_image_index = background.background_image.0.len() - 1; + let color_clip = *get_cyclic(&background.background_clip.0, last_background_image_index); + let (bounds, border_radii) = background::clip( + color_clip, + absolute_bounds, + style.logical_border_width().to_physical(style.writing_mode), + self.border_padding.to_physical(self.style.writing_mode), + border::radii(absolute_bounds, style.get_border()), + ); + + state.clipping_and_scrolling_scope(|state| { + if !border_radii.is_zero() { + let clip_id = state.add_late_clip_node(bounds.to_layout(), border_radii); + state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); + } + + let base = state.create_base_display_item( + bounds, + self.node, + get_cursor(&style, Cursor::Default), + display_list_section, + ); + state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( + base, + webrender_api::RectangleDisplayItem { + color: background_color.to_layout(), + common: items::empty_common_item_properties(), + }, + ))); + }); + + // The background image is painted on top of the background color. + // Implements background image, per spec: + // http://www.w3.org/TR/CSS21/colors.html#background + let background = style.get_background(); + for (i, background_image) in background.background_image.0.iter().enumerate().rev() { + let background_image = match *background_image { + ImageLayer::None => continue, + ImageLayer::Image(ref image) => image, + }; + + match *background_image { + Image::Gradient(ref gradient) => { + self.build_display_list_for_background_gradient( + state, + display_list_section, + absolute_bounds, + gradient, + style, + i, + ); + }, + Image::Url(ref image_url) => { + if let Some(url) = image_url.url() { + let webrender_image = state.layout_context.get_webrender_image_for_url( + self.node, + url.clone(), + UsePlaceholder::No, + ); + if let Some(webrender_image) = webrender_image { + self.build_display_list_for_webrender_image( + state, + style, + display_list_section, + absolute_bounds, + webrender_image, + i, + ); + } + } + }, + Image::PaintWorklet(ref paint_worklet) => { + let bounding_box = self.border_box - style.logical_border_width(); + let bounding_box_size = bounding_box.size.to_physical(style.writing_mode); + let background_size = + get_cyclic(&style.get_background().background_size.0, i).clone(); + let size = match background_size { + BackgroundSize::ExplicitSize { width, height } => Size2D::new( + width + .to_used_value(bounding_box_size.width) + .unwrap_or(bounding_box_size.width), + height + .to_used_value(bounding_box_size.height) + .unwrap_or(bounding_box_size.height), + ), + _ => bounding_box_size, + }; + let webrender_image = self.get_webrender_image_for_paint_worklet( + state, + style, + paint_worklet, + size, + ); + if let Some(webrender_image) = webrender_image { + self.build_display_list_for_webrender_image( + state, + style, + display_list_section, + absolute_bounds, + webrender_image, + i, + ); + } + }, + Image::Rect(_) => { + // TODO: Implement `-moz-image-rect` + }, + Image::Element(_) => { + // TODO: Implement `-moz-element` + }, + } + } + } + + /// Adds the display items necessary to paint a webrender image of this fragment to the + /// appropriate section of the display list. + fn build_display_list_for_webrender_image( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + display_list_section: DisplayListSection, + absolute_bounds: Rect<Au>, + webrender_image: WebRenderImageInfo, + index: usize, + ) { + debug!("(building display list) building background image"); + if webrender_image.key.is_none() { + return; + } + + let image = Size2D::new( + Au::from_px(webrender_image.width as i32), + Au::from_px(webrender_image.height as i32), + ); + let placement = background::placement( + style.get_background(), + state.layout_context.shared_context().viewport_size(), + absolute_bounds, + Some(image), + style.logical_border_width().to_physical(style.writing_mode), + self.border_padding.to_physical(self.style.writing_mode), + border::radii(absolute_bounds, style.get_border()), + index, + ); + + state.clipping_and_scrolling_scope(|state| { + if !placement.clip_radii.is_zero() { + let clip_id = + state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii); + state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); + } + + // Create the image display item. + let base = state.create_base_display_item( + placement.clip_rect, + self.node, + get_cursor(&style, Cursor::Default), + display_list_section, + ); + + debug!("(building display list) adding background image."); + state.add_image_item( + base, + webrender_api::ImageDisplayItem { + bounds: placement.bounds.to_f32_px(), + common: items::empty_common_item_properties(), + image_key: webrender_image.key.unwrap(), + stretch_size: placement.tile_size.to_layout(), + tile_spacing: placement.tile_spacing.to_layout(), + image_rendering: style.get_inherited_box().image_rendering.to_layout(), + alpha_type: webrender_api::AlphaType::PremultipliedAlpha, + color: webrender_api::ColorF::WHITE, + }, + ); + }); + } + + /// Calculates the webrender image for a paint worklet. + /// Returns None if the worklet is not registered. + /// If the worklet has missing image URLs, it passes them to the image cache for loading. + fn get_webrender_image_for_paint_worklet( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + paint_worklet: &PaintWorklet, + size_in_au: Size2D<Au>, + ) -> Option<WebRenderImageInfo> { + let device_pixel_ratio = state.layout_context.style_context.device_pixel_ratio(); + let size_in_px = + TypedSize2D::new(size_in_au.width.to_f32_px(), size_in_au.height.to_f32_px()); + + // TODO: less copying. + let name = paint_worklet.name.clone(); + let arguments = paint_worklet + .arguments + .iter() + .map(|argument| argument.to_css_string()) + .collect(); + + let draw_result = match state.layout_context.registered_painters.get(&name) { + Some(painter) => { + debug!( + "Drawing a paint image {}({},{}).", + name, size_in_px.width, size_in_px.height + ); + let properties = painter + .properties() + .iter() + .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id))) + .map(|(name, id)| (name.clone(), style.computed_value_to_string(id))) + .collect(); + painter.draw_a_paint_image(size_in_px, device_pixel_ratio, properties, arguments) + }, + None => { + debug!("Worklet {} called before registration.", name); + return None; + }, + }; + + if let Ok(draw_result) = draw_result { + let webrender_image = WebRenderImageInfo { + width: draw_result.width, + height: draw_result.height, + key: draw_result.image_key, + }; + + for url in draw_result.missing_image_urls.into_iter() { + debug!("Requesting missing image URL {}.", url); + state.layout_context.get_webrender_image_for_url( + self.node, + url, + UsePlaceholder::No, + ); + } + Some(webrender_image) + } else { + None + } + } + + /// Adds the display items necessary to paint the background linear gradient of this fragment + /// to the appropriate section of the display list. + fn build_display_list_for_background_gradient( + &self, + state: &mut DisplayListBuildState, + display_list_section: DisplayListSection, + absolute_bounds: Rect<Au>, + gradient: &Gradient, + style: &ComputedValues, + index: usize, + ) { + let placement = background::placement( + style.get_background(), + state.layout_context.shared_context().viewport_size(), + absolute_bounds, + None, + style.logical_border_width().to_physical(style.writing_mode), + self.border_padding.to_physical(self.style.writing_mode), + border::radii(absolute_bounds, style.get_border()), + index, + ); + + state.clipping_and_scrolling_scope(|state| { + if !placement.clip_radii.is_zero() { + let clip_id = + state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii); + state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); + } + + let base = state.create_base_display_item( + placement.clip_rect, + self.node, + get_cursor(&style, Cursor::Default), + display_list_section, + ); + + let display_item = match gradient.kind { + GradientKind::Linear(angle_or_corner) => { + let (gradient, stops) = gradient::linear( + style, + placement.tile_size, + &gradient.items[..], + angle_or_corner, + gradient.repeating, + ); + let item = webrender_api::GradientDisplayItem { + gradient, + bounds: placement.bounds.to_f32_px(), + common: items::empty_common_item_properties(), + tile_size: placement.tile_size.to_layout(), + tile_spacing: placement.tile_spacing.to_layout(), + }; + DisplayItem::Gradient(CommonDisplayItem::with_data(base, item, stops)) + }, + GradientKind::Radial(shape, center) => { + let (gradient, stops) = gradient::radial( + style, + placement.tile_size, + &gradient.items[..], + shape, + center, + gradient.repeating, + ); + let item = webrender_api::RadialGradientDisplayItem { + gradient, + bounds: placement.bounds.to_f32_px(), + common: items::empty_common_item_properties(), + tile_size: placement.tile_size.to_layout(), + tile_spacing: placement.tile_spacing.to_layout(), + }; + DisplayItem::RadialGradient(CommonDisplayItem::with_data(base, item, stops)) + }, + }; + state.add_display_item(display_item); + }); + } + + /// Adds the display items necessary to paint the box shadow of this fragment to the display + /// list if necessary. + fn build_display_list_for_box_shadow_if_applicable( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + display_list_section: DisplayListSection, + absolute_bounds: Rect<Au>, + clip: Rect<Au>, + ) { + // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back). + for box_shadow in style.get_effects().box_shadow.0.iter().rev() { + let base = state.create_base_display_item( + clip, + self.node, + get_cursor(&style, Cursor::Default), + display_list_section, + ); + let border_radius = border::radii(absolute_bounds, style.get_border()); + state.add_display_item(DisplayItem::BoxShadow(CommonDisplayItem::new( + base, + webrender_api::BoxShadowDisplayItem { + common: items::empty_common_item_properties(), + box_bounds: absolute_bounds.to_layout(), + color: style.resolve_color(box_shadow.base.color).to_layout(), + offset: LayoutVector2D::new( + box_shadow.base.horizontal.px(), + box_shadow.base.vertical.px(), + ), + blur_radius: box_shadow.base.blur.px(), + spread_radius: box_shadow.spread.px(), + border_radius: border_radius, + clip_mode: if box_shadow.inset { + BoxShadowClipMode::Inset + } else { + BoxShadowClipMode::Outset + }, + }, + ))); + } + } + + /// Adds the display items necessary to paint the borders of this fragment to a display list if + /// necessary. + fn build_display_list_for_borders_if_applicable( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + inline_info: Option<InlineNodeBorderInfo>, + border_painting_mode: BorderPaintingMode, + mut bounds: Rect<Au>, + display_list_section: DisplayListSection, + clip: Rect<Au>, + ) { + let mut border = style.logical_border_width(); + + if let Some(inline_info) = inline_info { + modify_border_width_for_inline_sides(&mut border, inline_info); + } + + match border_painting_mode { + BorderPaintingMode::Separate => {}, + BorderPaintingMode::Collapse(collapsed_borders) => { + collapsed_borders.adjust_border_widths_for_painting(&mut border) + }, + BorderPaintingMode::Hidden => return, + } + + let border_style_struct = style.get_border(); + let mut colors = SideOffsets2D::new( + border_style_struct.border_top_color, + border_style_struct.border_right_color, + border_style_struct.border_bottom_color, + border_style_struct.border_left_color, + ); + let mut border_style = SideOffsets2D::new( + border_style_struct.border_top_style, + border_style_struct.border_right_style, + border_style_struct.border_bottom_style, + border_style_struct.border_left_style, + ); + + if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode { + collapsed_borders.adjust_border_colors_and_styles_for_painting( + &mut colors, + &mut border_style, + style.writing_mode, + ); + } + + // If this border collapses, then we draw outside the boundaries we were given. + if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode { + collapsed_borders.adjust_border_bounds_for_painting(&mut bounds, style.writing_mode) + } + + // Append the border to the display list. + let base = state.create_base_display_item( + clip, + self.node, + get_cursor(&style, Cursor::Default), + display_list_section, + ); + + let border_radius = border::radii(bounds, border_style_struct); + let border_widths = border.to_physical(style.writing_mode); + + if let ImageLayer::Image(ref image) = border_style_struct.border_image_source { + if self + .build_display_list_for_border_image( + state, + style, + base.clone(), + bounds, + image, + border_widths, + ) + .is_some() + { + return; + } + // Fallback to rendering a solid border. + } + if border_widths == SideOffsets2D::zero() { + return; + } + let details = BorderDetails::Normal(NormalBorder { + left: BorderSide { + color: style.resolve_color(colors.left).to_layout(), + style: border_style.left.to_layout(), + }, + right: BorderSide { + color: style.resolve_color(colors.right).to_layout(), + style: border_style.right.to_layout(), + }, + top: BorderSide { + color: style.resolve_color(colors.top).to_layout(), + style: border_style.top.to_layout(), + }, + bottom: BorderSide { + color: style.resolve_color(colors.bottom).to_layout(), + style: border_style.bottom.to_layout(), + }, + radius: border_radius, + do_aa: true, + }); + state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( + base, + webrender_api::BorderDisplayItem { + bounds: bounds.to_layout(), + common: items::empty_common_item_properties(), + widths: border_widths.to_layout(), + details, + }, + Vec::new(), + ))); + } + + /// Add display item for image border. + /// + /// Returns `Some` if the addition was successful. + fn build_display_list_for_border_image( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + base: BaseDisplayItem, + bounds: Rect<Au>, + image: &Image, + border_width: SideOffsets2D<Au>, + ) -> Option<()> { + let border_style_struct = style.get_border(); + let border_image_outset = + border::image_outset(border_style_struct.border_image_outset, border_width); + let border_image_area = bounds.outer_rect(border_image_outset).size; + let border_image_width = border::image_width( + &border_style_struct.border_image_width, + border_width.to_layout(), + border_image_area, + ); + let border_image_repeat = &border_style_struct.border_image_repeat; + let border_image_fill = border_style_struct.border_image_slice.fill; + let border_image_slice = &border_style_struct.border_image_slice.offsets; + + let mut stops = Vec::new(); + let mut width = border_image_area.width.to_px() as u32; + let mut height = border_image_area.height.to_px() as u32; + let source = match image { + Image::Url(ref image_url) => { + let url = image_url.url()?; + let image = state.layout_context.get_webrender_image_for_url( + self.node, + url.clone(), + UsePlaceholder::No, + )?; + width = image.width; + height = image.height; + NinePatchBorderSource::Image(image.key?) + }, + Image::PaintWorklet(ref paint_worklet) => { + let image = self.get_webrender_image_for_paint_worklet( + state, + style, + paint_worklet, + border_image_area, + )?; + width = image.width; + height = image.height; + NinePatchBorderSource::Image(image.key?) + }, + Image::Gradient(ref gradient) => match gradient.kind { + GradientKind::Linear(angle_or_corner) => { + let (wr_gradient, linear_stops) = gradient::linear( + style, + border_image_area, + &gradient.items[..], + angle_or_corner, + gradient.repeating, + ); + stops = linear_stops; + NinePatchBorderSource::Gradient(wr_gradient) + }, + GradientKind::Radial(shape, center) => { + let (wr_gradient, radial_stops) = gradient::radial( + style, + border_image_area, + &gradient.items[..], + shape, + center, + gradient.repeating, + ); + stops = radial_stops; + NinePatchBorderSource::RadialGradient(wr_gradient) + }, + }, + _ => return None, + }; + + let details = BorderDetails::NinePatch(NinePatchBorder { + source, + width: width as i32, + height: height as i32, + slice: border::image_slice(border_image_slice, width as i32, height as i32), + fill: border_image_fill, + repeat_horizontal: border_image_repeat.0.to_layout(), + repeat_vertical: border_image_repeat.1.to_layout(), + outset: SideOffsets2D::new( + border_image_outset.top.to_f32_px(), + border_image_outset.right.to_f32_px(), + border_image_outset.bottom.to_f32_px(), + border_image_outset.left.to_f32_px(), + ), + }); + state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( + base, + webrender_api::BorderDisplayItem { + bounds: bounds.to_layout(), + common: items::empty_common_item_properties(), + widths: border_image_width, + details, + }, + stops, + ))); + Some(()) + } + + /// Adds the display items necessary to paint the outline of this fragment to the display list + /// if necessary. + fn build_display_list_for_outline_if_applicable( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + mut bounds: Rect<Au>, + clip: Rect<Au>, + ) { + use style::values::specified::outline::OutlineStyle; + + let width = Au::from(style.get_outline().outline_width); + if width == Au(0) { + return; + } + + let outline_style = match style.get_outline().outline_style { + OutlineStyle::Auto => BorderStyle::Solid, + // FIXME(emilio): I don't think this border-style check is + // necessary, since border-style: none implies an outline-width of + // zero at computed value time. + OutlineStyle::BorderStyle(BorderStyle::None) => return, + OutlineStyle::BorderStyle(s) => s, + }; + + // Outlines are not accounted for in the dimensions of the border box, so adjust the + // absolute bounds. + let offset = width + Au::from(style.get_outline().outline_offset); + bounds = bounds.inflate(offset, offset); + + // Append the outline to the display list. + let color = style + .resolve_color(style.get_outline().outline_color) + .to_layout(); + let base = state.create_base_display_item( + clip, + self.node, + get_cursor(&style, Cursor::Default), + DisplayListSection::Outlines, + ); + state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( + base, + webrender_api::BorderDisplayItem { + bounds: bounds.to_layout(), + common: items::empty_common_item_properties(), + widths: SideOffsets2D::new_all_same(width).to_layout(), + details: BorderDetails::Normal(border::simple(color, outline_style.to_layout())), + }, + Vec::new(), + ))); + } + + /// Adds display items necessary to draw debug boxes around a scanned text fragment. + fn build_debug_borders_around_text_fragments( + &self, + state: &mut DisplayListBuildState, + style: &ComputedValues, + stacking_relative_border_box: Rect<Au>, + stacking_relative_content_box: Rect<Au>, + text_fragment: &ScannedTextFragmentInfo, + clip: Rect<Au>, + ) { + // FIXME(pcwalton, #2795): Get the real container size. + let container_size = Size2D::zero(); + + // Compute the text fragment bounds and draw a border surrounding them. + let base = state.create_base_display_item( + clip, + self.node, + get_cursor(&style, Cursor::Default), + DisplayListSection::Content, + ); + state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( + base, + webrender_api::BorderDisplayItem { + bounds: stacking_relative_border_box.to_layout(), + common: items::empty_common_item_properties(), + widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(), + details: BorderDetails::Normal(border::simple( + ColorU::new(0, 0, 200, 1).into(), + webrender_api::BorderStyle::Solid, + )), + }, + Vec::new(), + ))); + + // Draw a rectangle representing the baselines. + let mut baseline = LogicalRect::from_physical( + self.style.writing_mode, + stacking_relative_content_box, + container_size, + ); + baseline.start.b = baseline.start.b + text_fragment.run.ascent(); + baseline.size.block = Au(0); + let baseline = baseline.to_physical(self.style.writing_mode, container_size); + + let base = state.create_base_display_item( + clip, + self.node, + get_cursor(&style, Cursor::Default), + DisplayListSection::Content, + ); + // TODO(gw): Use a better estimate for wavy line thickness. + let area = baseline.to_layout(); + let wavy_line_thickness = (0.33 * area.size.height).ceil(); + state.add_display_item(DisplayItem::Line(CommonDisplayItem::new( + base, + webrender_api::LineDisplayItem { + common: items::empty_common_item_properties(), + area, + orientation: webrender_api::LineOrientation::Horizontal, + wavy_line_thickness, + color: ColorU::new(0, 200, 0, 1).into(), + style: LineStyle::Dashed, + }, + ))); + } + + /// Adds display items necessary to draw debug boxes around this fragment. + fn build_debug_borders_around_fragment( + &self, + state: &mut DisplayListBuildState, + stacking_relative_border_box: Rect<Au>, + clip: Rect<Au>, + ) { + // This prints a debug border around the border of this fragment. + let base = state.create_base_display_item( + clip, + self.node, + get_cursor(&self.style, Cursor::Default), + DisplayListSection::Content, + ); + state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( + base, + webrender_api::BorderDisplayItem { + bounds: stacking_relative_border_box.to_layout(), + common: items::empty_common_item_properties(), + widths: SideOffsets2D::new_all_same(Au::from_px(1)).to_layout(), + details: BorderDetails::Normal(border::simple( + ColorU::new(0, 0, 200, 1).into(), + webrender_api::BorderStyle::Solid, + )), + }, + Vec::new(), + ))); + } + + /// Builds the display items necessary to paint the selection and/or caret for this fragment, + /// if any. + fn build_display_items_for_selection_if_necessary( + &self, + state: &mut DisplayListBuildState, + stacking_relative_border_box: Rect<Au>, + display_list_section: DisplayListSection, + ) { + let scanned_text_fragment_info = match self.specific { + SpecificFragmentInfo::ScannedText(ref scanned_text_fragment_info) => { + scanned_text_fragment_info + }, + _ => return, + }; + + // Draw a highlighted background if the text is selected. + // + // TODO: Allow non-text fragments to be selected too. + if scanned_text_fragment_info.selected() { + let style = self.selected_style(); + let background_color = style.resolve_color(style.get_background().background_color); + let base = state.create_base_display_item( + stacking_relative_border_box, + self.node, + get_cursor(&self.style, Cursor::Default), + display_list_section, + ); + state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( + base, + webrender_api::RectangleDisplayItem { + common: items::empty_common_item_properties(), + color: background_color.to_layout(), + }, + ))); + } + + // Draw a caret at the insertion point. + let insertion_point_index = match scanned_text_fragment_info.insertion_point { + Some(insertion_point_index) => insertion_point_index, + None => return, + }; + let range = Range::new( + scanned_text_fragment_info.range.begin(), + insertion_point_index - scanned_text_fragment_info.range.begin(), + ); + let advance = scanned_text_fragment_info.run.advance_for_range(&range); + + let insertion_point_bounds; + let cursor; + if !self.style.writing_mode.is_vertical() { + insertion_point_bounds = rect( + stacking_relative_border_box.origin.x + advance, + stacking_relative_border_box.origin.y, + INSERTION_POINT_LOGICAL_WIDTH, + stacking_relative_border_box.size.height, + ); + cursor = Cursor::Text; + } else { + insertion_point_bounds = rect( + stacking_relative_border_box.origin.x, + stacking_relative_border_box.origin.y + advance, + stacking_relative_border_box.size.width, + INSERTION_POINT_LOGICAL_WIDTH, + ); + cursor = Cursor::VerticalText; + }; + + let base = state.create_base_display_item( + insertion_point_bounds, + self.node, + get_cursor(&self.style, cursor), + display_list_section, + ); + state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( + base, + webrender_api::RectangleDisplayItem { + common: items::empty_common_item_properties(), + color: self.style().get_inherited_text().color.to_layout(), + }, + ))); + } + + /// Adds the display items for this fragment to the given display list. + /// + /// Arguments: + /// + /// * `state`: The display building state, including the display list currently + /// under construction and other metadata useful for constructing it. + /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. + /// * `clip`: The region to clip the display items to. + /// * `overflow_content_size`: The size of content associated with this fragment + /// that must have overflow handling applied to it. For a scrollable block + /// flow, it is expected that this is the size of the child boxes. + pub fn build_display_list( + &mut self, + state: &mut DisplayListBuildState, + stacking_relative_border_box: Rect<Au>, + border_painting_mode: BorderPaintingMode, + display_list_section: DisplayListSection, + clip: Rect<Au>, + overflow_content_size: Option<Size2D<Au>>, + ) { + let previous_clipping_and_scrolling = state.current_clipping_and_scrolling; + if let Some(index) = self.established_reference_frame { + state.current_clipping_and_scrolling = ClippingAndScrolling::simple(index); + } + + self.restyle_damage.remove(ServoRestyleDamage::REPAINT); + self.build_display_list_no_damage( + state, + stacking_relative_border_box, + border_painting_mode, + display_list_section, + clip, + overflow_content_size, + ); + + state.current_clipping_and_scrolling = previous_clipping_and_scrolling; + } + + /// build_display_list, but don't update the restyle damage + /// + /// Must be paired with a self.restyle_damage.remove(REPAINT) somewhere + fn build_display_list_no_damage( + &self, + state: &mut DisplayListBuildState, + stacking_relative_border_box: Rect<Au>, + border_painting_mode: BorderPaintingMode, + display_list_section: DisplayListSection, + clip: Rect<Au>, + overflow_content_size: Option<Size2D<Au>>, + ) { + if self.style().get_inherited_box().visibility != Visibility::Visible { + return; + } + + debug!( + "Fragment::build_display_list at rel={:?}, abs={:?}: {:?}", + self.border_box, stacking_relative_border_box, self + ); + + // Check the clip rect. If there's nothing to render at all, don't even construct display + // list items. + let empty_rect = !clip.intersects(&stacking_relative_border_box); + if self.is_primary_fragment() && !empty_rect { + // Add shadows, background, borders, and outlines, if applicable. + if let Some(ref inline_context) = self.inline_context { + for node in inline_context.nodes.iter().rev() { + self.build_display_list_for_background_if_applicable( + state, + &*node.style, + display_list_section, + stacking_relative_border_box, + ); + + self.build_display_list_for_box_shadow_if_applicable( + state, + &*node.style, + display_list_section, + stacking_relative_border_box, + clip, + ); + + self.build_display_list_for_borders_if_applicable( + state, + &*node.style, + Some(InlineNodeBorderInfo { + is_first_fragment_of_element: node + .flags + .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT), + is_last_fragment_of_element: node + .flags + .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT), + }), + border_painting_mode, + stacking_relative_border_box, + display_list_section, + clip, + ); + + // FIXME(emilio): Why does outline not do the same width + // fixup as border? + self.build_display_list_for_outline_if_applicable( + state, + &*node.style, + stacking_relative_border_box, + clip, + ); + } + } + + if !self.is_scanned_text_fragment() { + self.build_display_list_for_background_if_applicable( + state, + &*self.style, + display_list_section, + stacking_relative_border_box, + ); + + self.build_display_list_for_box_shadow_if_applicable( + state, + &*self.style, + display_list_section, + stacking_relative_border_box, + clip, + ); + + self.build_display_list_for_borders_if_applicable( + state, + &*self.style, + /* inline_node_info = */ None, + border_painting_mode, + stacking_relative_border_box, + display_list_section, + clip, + ); + + self.build_display_list_for_outline_if_applicable( + state, + &*self.style, + stacking_relative_border_box, + clip, + ); + } + } + + if self.is_primary_fragment() { + // Paint the selection point if necessary. Even an empty text fragment may have an + // insertion point, so we do this even if `empty_rect` is true. + self.build_display_items_for_selection_if_necessary( + state, + stacking_relative_border_box, + display_list_section, + ); + } + + if empty_rect { + return; + } + + debug!("Fragment::build_display_list: intersected. Adding display item..."); + + if let Some(content_size) = overflow_content_size { + // Create a transparent rectangle for hit-testing purposes that exists in front + // of this fragment's background but behind its content. This ensures that any + // hit tests inside the content box but not on actual content target the current + // scrollable ancestor. + let content_size = TypedRect::new(stacking_relative_border_box.origin, content_size); + let base = state.create_base_display_item_with_clipping_and_scrolling( + content_size, + self.node, + // FIXME(emilio): Why does this ignore pointer-events? + get_cursor(&self.style, Cursor::Default).or(Some(Cursor::Default)), + display_list_section, + state.current_clipping_and_scrolling, + ); + state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( + base, + webrender_api::RectangleDisplayItem { + common: items::empty_common_item_properties(), + color: ColorF::TRANSPARENT, + }, + ))); + } + + // Create special per-fragment-type display items. + state.clipping_and_scrolling_scope(|state| { + self.build_fragment_type_specific_display_items( + state, + stacking_relative_border_box, + clip, + ); + }); + + if opts::get().show_debug_fragment_borders { + self.build_debug_borders_around_fragment(state, stacking_relative_border_box, clip) + } + } + + /// A helper method that `build_display_list` calls to create per-fragment-type display items. + fn build_fragment_type_specific_display_items( + &self, + state: &mut DisplayListBuildState, + stacking_relative_border_box: Rect<Au>, + clip: Rect<Au>, + ) { + // Compute the context box position relative to the parent stacking context. + let stacking_relative_content_box = + self.stacking_relative_content_box(stacking_relative_border_box); + + let create_base_display_item = |state: &mut DisplayListBuildState| { + // Adjust the clipping region as necessary to account for `border-radius`. + let radii = + build_border_radius_for_inner_rect(stacking_relative_border_box, &self.style); + + if !radii.is_zero() { + let clip_id = + state.add_late_clip_node(stacking_relative_border_box.to_layout(), radii); + state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); + } + + state.create_base_display_item( + stacking_relative_border_box, + self.node, + get_cursor(&self.style, Cursor::Default), + DisplayListSection::Content, + ) + }; + + match self.specific { + SpecificFragmentInfo::TruncatedFragment(ref truncated_fragment) + if truncated_fragment.text_info.is_some() => + { + let text_fragment = truncated_fragment.text_info.as_ref().unwrap(); + // Create the main text display item. + self.build_display_list_for_text_fragment( + state, + &text_fragment, + stacking_relative_content_box, + &self.style.get_inherited_text().text_shadow.0, + clip, + ); + + if opts::get().show_debug_fragment_borders { + self.build_debug_borders_around_text_fragments( + state, + self.style(), + stacking_relative_border_box, + stacking_relative_content_box, + &text_fragment, + clip, + ); + } + } + SpecificFragmentInfo::ScannedText(ref text_fragment) => { + // Create the main text display item. + self.build_display_list_for_text_fragment( + state, + &text_fragment, + stacking_relative_content_box, + &self.style.get_inherited_text().text_shadow.0, + clip, + ); + + if opts::get().show_debug_fragment_borders { + self.build_debug_borders_around_text_fragments( + state, + self.style(), + stacking_relative_border_box, + stacking_relative_content_box, + &text_fragment, + clip, + ); + } + }, + SpecificFragmentInfo::Generic | + SpecificFragmentInfo::GeneratedContent(..) | + SpecificFragmentInfo::Table | + SpecificFragmentInfo::TableCell | + SpecificFragmentInfo::TableRow | + SpecificFragmentInfo::TableWrapper | + SpecificFragmentInfo::Multicol | + SpecificFragmentInfo::MulticolColumn | + SpecificFragmentInfo::InlineBlock(_) | + SpecificFragmentInfo::InlineAbsoluteHypothetical(_) | + SpecificFragmentInfo::InlineAbsolute(_) | + SpecificFragmentInfo::TruncatedFragment(_) | + SpecificFragmentInfo::Svg(_) => { + if opts::get().show_debug_fragment_borders { + self.build_debug_borders_around_fragment( + state, + stacking_relative_border_box, + clip, + ); + } + }, + SpecificFragmentInfo::Iframe(ref fragment_info) => { + if !stacking_relative_content_box.is_empty() { + let browsing_context_id = match fragment_info.browsing_context_id { + Some(browsing_context_id) => browsing_context_id, + None => return warn!("No browsing context id for iframe."), + }; + let pipeline_id = match fragment_info.pipeline_id { + Some(pipeline_id) => pipeline_id, + None => return warn!("No pipeline id for iframe {}.", browsing_context_id), + }; + + let base = create_base_display_item(state); + let bounds = stacking_relative_content_box.to_layout(); + let item = DisplayItem::Iframe(Box::new(IframeDisplayItem { + base, + bounds, + iframe: pipeline_id, + })); + + // XXXjdm: This sleight-of-hand to convert LayoutRect -> Size2D<CSSPixel> + // looks bogus. + let size = Size2D::new(bounds.size.width, bounds.size.height); + state.iframe_sizes.push(IFrameSize { + id: browsing_context_id, + size: TypedSize2D::from_untyped(&size), + }); + + state.add_display_item(item); + } + }, + SpecificFragmentInfo::Image(ref image_fragment) => { + // Place the image into the display list. + if let Some(ref image) = image_fragment.image { + if let Some(id) = image.id { + let base = create_base_display_item(state); + state.add_image_item( + base, + webrender_api::ImageDisplayItem { + bounds: stacking_relative_content_box.to_layout(), + common: items::empty_common_item_properties(), + image_key: id, + stretch_size: stacking_relative_content_box.size.to_layout(), + tile_spacing: LayoutSize::zero(), + image_rendering: self + .style + .get_inherited_box() + .image_rendering + .to_layout(), + alpha_type: webrender_api::AlphaType::PremultipliedAlpha, + color: webrender_api::ColorF::WHITE, + }, + ); + } + } + }, + SpecificFragmentInfo::Media(ref fragment_info) => { + if let Some((ref image_key, _, _)) = fragment_info.current_frame { + let base = create_base_display_item(state); + state.add_image_item( + base, + webrender_api::ImageDisplayItem { + bounds: stacking_relative_content_box.to_layout(), + common: items::empty_common_item_properties(), + image_key: *image_key, + stretch_size: stacking_relative_border_box.size.to_layout(), + tile_spacing: LayoutSize::zero(), + image_rendering: ImageRendering::Auto, + alpha_type: webrender_api::AlphaType::PremultipliedAlpha, + color: webrender_api::ColorF::WHITE, + }, + ); + } + }, + SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => { + let image_key = match canvas_fragment_info.source { + CanvasFragmentSource::WebGL(image_key) => image_key, + CanvasFragmentSource::Image(ref ipc_renderer) => match *ipc_renderer { + Some(ref ipc_renderer) => { + let ipc_renderer = ipc_renderer.lock().unwrap(); + let (sender, receiver) = ipc::channel().unwrap(); + ipc_renderer + .send(CanvasMsg::FromLayout( + FromLayoutMsg::SendData(sender), + canvas_fragment_info.canvas_id.clone(), + )) + .unwrap(); + receiver.recv().unwrap().image_key + }, + None => return, + }, + }; + + let base = create_base_display_item(state); + let display_item = webrender_api::ImageDisplayItem { + bounds: stacking_relative_border_box.to_layout(), + common: items::empty_common_item_properties(), + image_key, + stretch_size: stacking_relative_content_box.size.to_layout(), + tile_spacing: LayoutSize::zero(), + image_rendering: ImageRendering::Auto, + alpha_type: webrender_api::AlphaType::PremultipliedAlpha, + color: webrender_api::ColorF::WHITE, + }; + + state.add_image_item(base, display_item); + }, + SpecificFragmentInfo::UnscannedText(_) => { + panic!("Shouldn't see unscanned fragments here.") + }, + SpecificFragmentInfo::TableColumn(_) => { + panic!("Shouldn't see table column fragments here.") + }, + } + } + + /// Creates a stacking context for associated fragment. + fn create_stacking_context( + &self, + id: StackingContextId, + base_flow: &BaseFlow, + context_type: StackingContextType, + established_reference_frame: Option<ClipScrollNodeIndex>, + parent_clipping_and_scrolling: ClippingAndScrolling, + ) -> StackingContext { + let border_box = self.stacking_relative_border_box( + &base_flow.stacking_relative_position, + &base_flow + .early_absolute_position_info + .relative_containing_block_size, + base_flow + .early_absolute_position_info + .relative_containing_block_mode, + CoordinateSystem::Parent, + ); + // First, compute the offset of our border box (including relative positioning) + // from our flow origin, since that is what `BaseFlow::overflow` is relative to. + let border_box_offset = border_box + .translate(&-base_flow.stacking_relative_position) + .origin; + // Then, using that, compute our overflow region relative to our border box. + let overflow = base_flow + .overflow + .paint + .translate(&-border_box_offset.to_vector()); + + // Create the filter pipeline. + let effects = self.style().get_effects(); + let mut filters: Vec<FilterOp> = effects.filter.0.iter().map(ToLayout::to_layout).collect(); + if effects.opacity != 1.0 { + filters.push(FilterOp::Opacity(effects.opacity.into(), effects.opacity)); + } + + StackingContext::new( + id, + context_type, + border_box.to_layout(), + overflow.to_layout(), + self.effective_z_index(), + self.style().get_box()._servo_top_layer, + filters, + self.style().get_effects().mix_blend_mode.to_layout(), + self.transform_matrix(&border_box), + self.style().get_used_transform_style().to_layout(), + self.perspective_matrix(&border_box), + parent_clipping_and_scrolling, + established_reference_frame, + ) + } + + /// Creates the text display item for one text fragment. This can be called multiple times for + /// one fragment if there are text shadows. + /// + /// `text_shadow` will be `Some` if this is rendering a shadow. + fn build_display_list_for_text_fragment( + &self, + state: &mut DisplayListBuildState, + text_fragment: &ScannedTextFragmentInfo, + stacking_relative_content_box: Rect<Au>, + text_shadows: &[SimpleShadow], + clip: Rect<Au>, + ) { + // NB: The order for painting text components (CSS Text Decoration Module Level 3) is: + // shadows, underline, overline, text, text-emphasis, and then line-through. + + // TODO(emilio): Allow changing more properties by ::selection + // Paint the text with the color as described in its styling. + let text_color = if text_fragment.selected() { + self.selected_style().get_inherited_text().color + } else { + self.style().get_inherited_text().color + }; + + // Determine the orientation and cursor to use. + let (_orientation, cursor) = if self.style.writing_mode.is_vertical() { + // TODO: Distinguish between 'sideways-lr' and 'sideways-rl' writing modes in CSS + // Writing Modes Level 4. + (TextOrientation::SidewaysRight, Cursor::VerticalText) + } else { + (TextOrientation::Upright, Cursor::Text) + }; + + // Compute location of the baseline. + // + // FIXME(pcwalton): Get the real container size. + let container_size = Size2D::zero(); + let metrics = &text_fragment.run.font_metrics; + let baseline_origin = stacking_relative_content_box.origin + + LogicalPoint::new(self.style.writing_mode, Au(0), metrics.ascent) + .to_physical(self.style.writing_mode, container_size) + .to_vector(); + + // Base item for all text/shadows + let base = state.create_base_display_item( + clip, + self.node, + get_cursor(&self.style, cursor), + DisplayListSection::Content, + ); + + // NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front + // to back). + + // Shadows + for shadow in text_shadows.iter().rev() { + state.add_display_item(DisplayItem::PushTextShadow(Box::new( + PushTextShadowDisplayItem { + base: base.clone(), + shadow: webrender_api::Shadow { + offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()), + color: self.style.resolve_color(shadow.color).to_layout(), + blur_radius: shadow.blur.px(), + }, + }, + ))); + } + + // Create display items for text decorations. + let text_decorations = self.style().get_inherited_text().text_decorations_in_effect; + + let logical_stacking_relative_content_box = LogicalRect::from_physical( + self.style.writing_mode, + stacking_relative_content_box, + container_size, + ); + + // Underline + if text_decorations.underline { + let mut stacking_relative_box = logical_stacking_relative_content_box; + stacking_relative_box.start.b = logical_stacking_relative_content_box.start.b + + metrics.ascent - + metrics.underline_offset; + stacking_relative_box.size.block = metrics.underline_size; + self.build_display_list_for_text_decoration( + state, + &text_color, + &stacking_relative_box, + clip, + ); + } + + // Overline + if text_decorations.overline { + let mut stacking_relative_box = logical_stacking_relative_content_box; + stacking_relative_box.size.block = metrics.underline_size; + self.build_display_list_for_text_decoration( + state, + &text_color, + &stacking_relative_box, + clip, + ); + } + + // Text + let glyphs = convert_text_run_to_glyphs( + text_fragment.run.clone(), + text_fragment.range, + baseline_origin, + ); + if !glyphs.is_empty() { + let indexable_text = IndexableTextItem { + origin: stacking_relative_content_box.origin, + text_run: text_fragment.run.clone(), + range: text_fragment.range, + baseline_origin, + }; + state.indexable_text.insert(self.node, indexable_text); + + state.add_display_item(DisplayItem::Text(CommonDisplayItem::with_data( + base.clone(), + webrender_api::TextDisplayItem { + bounds: stacking_relative_content_box.to_layout(), + common: items::empty_common_item_properties(), + font_key: text_fragment.run.font_key, + color: text_color.to_layout(), + glyph_options: None, + }, + glyphs, + ))); + } + + // TODO(#17715): emit text-emphasis marks here. + // (just push another TextDisplayItem?) + + // Line-Through + if text_decorations.line_through { + let mut stacking_relative_box = logical_stacking_relative_content_box; + stacking_relative_box.start.b = + stacking_relative_box.start.b + metrics.ascent - metrics.strikeout_offset; + stacking_relative_box.size.block = metrics.strikeout_size; + self.build_display_list_for_text_decoration( + state, + &text_color, + &stacking_relative_box, + clip, + ); + } + + // Pop all the PushTextShadows + if !text_shadows.is_empty() { + state.add_display_item(DisplayItem::PopAllTextShadows(Box::new( + PopAllTextShadowsDisplayItem { base }, + ))); + } + } + + /// Creates the display item for a text decoration: underline, overline, or line-through. + fn build_display_list_for_text_decoration( + &self, + state: &mut DisplayListBuildState, + color: &RGBA, + stacking_relative_box: &LogicalRect<Au>, + clip: Rect<Au>, + ) { + // FIXME(pcwalton, #2795): Get the real container size. + let container_size = Size2D::zero(); + let stacking_relative_box = + stacking_relative_box.to_physical(self.style.writing_mode, container_size); + let base = state.create_base_display_item( + clip, + self.node, + get_cursor(&self.style, Cursor::Default), + DisplayListSection::Content, + ); + + // TODO(gw): Use a better estimate for wavy line thickness. + let area = stacking_relative_box.to_layout(); + let wavy_line_thickness = (0.33 * area.size.height).ceil(); + state.add_display_item(DisplayItem::Line(CommonDisplayItem::new( + base, + webrender_api::LineDisplayItem { + common: items::empty_common_item_properties(), + area, + orientation: webrender_api::LineOrientation::Horizontal, + wavy_line_thickness, + color: color.to_layout(), + style: LineStyle::Solid, + }, + ))); + } + + fn unique_id(&self) -> u64 { + let fragment_type = self.fragment_type(); + let id = self.node.id() as usize; + combine_id_with_fragment_type(id, fragment_type) as u64 + } + + fn fragment_type(&self) -> FragmentType { + self.pseudo.fragment_type() + } +} + +bitflags! { + pub struct StackingContextCollectionFlags: u8 { + /// This flow never establishes a containing block. + const POSITION_NEVER_CREATES_CONTAINING_BLOCK = 0b001; + /// This flow never creates a ClipScrollNode. + const NEVER_CREATES_CLIP_SCROLL_NODE = 0b010; + /// This flow never creates a stacking context. + const NEVER_CREATES_STACKING_CONTEXT = 0b100; + } +} + +/// This structure manages ensuring that modification to StackingContextCollectionState is +/// only temporary. It's useful for moving recursively down the flow tree and ensuring +/// that the state is restored for siblings. To use this structure, we must call +/// SavedStackingContextCollectionState::restore in order to restore the state. +/// TODO(mrobinson): It would be nice to use RAII here to avoid having to call restore. +pub struct SavedStackingContextCollectionState { + stacking_context_id: StackingContextId, + real_stacking_context_id: StackingContextId, + parent_reference_frame_id: ClipScrollNodeIndex, + clipping_and_scrolling: ClippingAndScrolling, + containing_block_clipping_and_scrolling: ClippingAndScrolling, + clips_pushed: usize, + containing_block_clips_pushed: usize, + stacking_relative_content_box: Rect<Au>, +} + +impl SavedStackingContextCollectionState { + fn new(state: &mut StackingContextCollectionState) -> SavedStackingContextCollectionState { + SavedStackingContextCollectionState { + stacking_context_id: state.current_stacking_context_id, + real_stacking_context_id: state.current_real_stacking_context_id, + parent_reference_frame_id: state.current_parent_reference_frame_id, + clipping_and_scrolling: state.current_clipping_and_scrolling, + containing_block_clipping_and_scrolling: state.containing_block_clipping_and_scrolling, + clips_pushed: 0, + containing_block_clips_pushed: 0, + stacking_relative_content_box: state.parent_stacking_relative_content_box, + } + } + + fn switch_to_containing_block_clip(&mut self, state: &mut StackingContextCollectionState) { + let clip = state + .containing_block_clip_stack + .last() + .cloned() + .unwrap_or_else(MaxRect::max_rect); + state.clip_stack.push(clip); + self.clips_pushed += 1; + } + + fn restore(self, state: &mut StackingContextCollectionState) { + state.current_stacking_context_id = self.stacking_context_id; + state.current_real_stacking_context_id = self.real_stacking_context_id; + state.current_parent_reference_frame_id = self.parent_reference_frame_id; + state.current_clipping_and_scrolling = self.clipping_and_scrolling; + state.containing_block_clipping_and_scrolling = + self.containing_block_clipping_and_scrolling; + state.parent_stacking_relative_content_box = self.stacking_relative_content_box; + + let truncate_length = state.clip_stack.len() - self.clips_pushed; + state.clip_stack.truncate(truncate_length); + + let truncate_length = + state.containing_block_clip_stack.len() - self.containing_block_clips_pushed; + state.containing_block_clip_stack.truncate(truncate_length); + } + + fn push_clip( + &mut self, + state: &mut StackingContextCollectionState, + mut clip: Rect<Au>, + positioning: StylePosition, + ) { + if positioning != StylePosition::Fixed { + if let Some(old_clip) = state.clip_stack.last() { + clip = old_clip.intersection(&clip).unwrap_or_else(Rect::zero); + } + } + + state.clip_stack.push(clip); + self.clips_pushed += 1; + + if StylePosition::Absolute == positioning { + state.containing_block_clip_stack.push(clip); + self.containing_block_clips_pushed += 1; + } + } +} + +impl BlockFlow { + fn transform_clip_to_coordinate_space( + &mut self, + state: &mut StackingContextCollectionState, + preserved_state: &mut SavedStackingContextCollectionState, + ) { + if state.clip_stack.is_empty() { + return; + } + let border_box = self.stacking_relative_border_box(CoordinateSystem::Parent); + let transform = match self.fragment.transform_matrix(&border_box) { + Some(transform) => transform, + None => return, + }; + + let perspective = self + .fragment + .perspective_matrix(&border_box) + .unwrap_or(LayoutTransform::identity()); + let transform = transform.pre_mul(&perspective).inverse(); + + let origin = border_box.origin; + let transform_clip = |clip: Rect<Au>| { + if clip == Rect::max_rect() { + return clip; + } + + match transform { + Some(transform) if transform.m13 != 0.0 || transform.m23 != 0.0 => { + // We cannot properly handle perspective transforms, because there may be a + // situation where an element is transformed from outside the clip into the + // clip region. Here we don't have enough information to detect when that is + // happening. For the moment we just punt on trying to optimize the display + // list for those cases. + Rect::max_rect() + }, + Some(transform) => { + let clip = rect( + (clip.origin.x - origin.x).to_f32_px(), + (clip.origin.y - origin.y).to_f32_px(), + clip.size.width.to_f32_px(), + clip.size.height.to_f32_px(), + ); + + let clip = transform.transform_rect(&clip).unwrap(); + + rect( + Au::from_f32_px(clip.origin.x), + Au::from_f32_px(clip.origin.y), + Au::from_f32_px(clip.size.width), + Au::from_f32_px(clip.size.height), + ) + }, + None => Rect::zero(), + } + }; + + if let Some(clip) = state.clip_stack.last().cloned() { + state.clip_stack.push(transform_clip(clip)); + preserved_state.clips_pushed += 1; + } + + if let Some(clip) = state.containing_block_clip_stack.last().cloned() { + state.containing_block_clip_stack.push(transform_clip(clip)); + preserved_state.containing_block_clips_pushed += 1; + } + } + + /// Returns true if this fragment may establish a reference frame and this block + /// creates a stacking context. Both are necessary in order to establish a reference + /// frame. + fn is_reference_frame(&self, context_type: Option<StackingContextType>) -> bool { + match context_type { + Some(StackingContextType::Real) => self.fragment.can_establish_reference_frame(), + _ => false, + } + } + + pub fn collect_stacking_contexts_for_block( + &mut self, + state: &mut StackingContextCollectionState, + flags: StackingContextCollectionFlags, + ) { + let mut preserved_state = SavedStackingContextCollectionState::new(state); + + let stacking_context_type = self.stacking_context_type(flags); + self.base.stacking_context_id = match stacking_context_type { + None => state.current_stacking_context_id, + Some(sc_type) => state.allocate_stacking_context_info(sc_type), + }; + state.current_stacking_context_id = self.base.stacking_context_id; + + if stacking_context_type == Some(StackingContextType::Real) { + state.current_real_stacking_context_id = self.base.stacking_context_id; + } + + let established_reference_frame = if self.is_reference_frame(stacking_context_type) { + // WebRender currently creates reference frames automatically, so just add + // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame. + Some(state.add_clip_scroll_node(ClipScrollNode::placeholder())) + } else { + None + }; + + // We are getting the id of the scroll root that contains us here, not the id of + // any scroll root that we create. If we create a scroll root, its index will be + // stored in state.current_clipping_and_scrolling. If we create a stacking context, + // we don't want it to be contained by its own scroll root. + let containing_clipping_and_scrolling = self.setup_clipping_for_block( + state, + &mut preserved_state, + stacking_context_type, + established_reference_frame, + flags, + ); + + let creates_containing_block = !flags + .contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK); + let abspos_containing_block = established_reference_frame.is_some() || + (creates_containing_block && self.positioning() != StylePosition::Static); + if abspos_containing_block { + state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling; + } + + match stacking_context_type { + None => self.base.collect_stacking_contexts_for_children(state), + Some(StackingContextType::Real) => { + self.create_real_stacking_context_for_block( + preserved_state.stacking_context_id, + containing_clipping_and_scrolling, + established_reference_frame, + state, + ); + }, + Some(stacking_context_type) => { + self.create_pseudo_stacking_context_for_block( + stacking_context_type, + preserved_state.stacking_context_id, + containing_clipping_and_scrolling, + state, + ); + }, + } + + preserved_state.restore(state); + } + + fn setup_clipping_for_block( + &mut self, + state: &mut StackingContextCollectionState, + preserved_state: &mut SavedStackingContextCollectionState, + stacking_context_type: Option<StackingContextType>, + established_reference_frame: Option<ClipScrollNodeIndex>, + flags: StackingContextCollectionFlags, + ) -> ClippingAndScrolling { + // If this block is absolutely positioned, we should be clipped and positioned by + // the scroll root of our nearest ancestor that establishes a containing block. + let containing_clipping_and_scrolling = match self.positioning() { + StylePosition::Absolute => { + preserved_state.switch_to_containing_block_clip(state); + state.current_clipping_and_scrolling = + state.containing_block_clipping_and_scrolling; + state.containing_block_clipping_and_scrolling + }, + StylePosition::Fixed => { + // If we are a fixed positioned stacking context, we want to be scrolled by + // our reference frame instead of the clip scroll node that we are inside. + preserved_state.push_clip(state, Rect::max_rect(), StylePosition::Fixed); + state.current_clipping_and_scrolling.scrolling = + state.current_parent_reference_frame_id; + state.current_clipping_and_scrolling + }, + _ => state.current_clipping_and_scrolling, + }; + self.base.clipping_and_scrolling = Some(containing_clipping_and_scrolling); + + if let Some(reference_frame_index) = established_reference_frame { + let clipping_and_scrolling = ClippingAndScrolling::simple(reference_frame_index); + state.current_clipping_and_scrolling = clipping_and_scrolling; + self.base.clipping_and_scrolling = Some(clipping_and_scrolling); + } + + let stacking_relative_border_box = if self.fragment.establishes_stacking_context() { + self.stacking_relative_border_box(CoordinateSystem::Own) + } else { + self.stacking_relative_border_box(CoordinateSystem::Parent) + }; + + if stacking_context_type == Some(StackingContextType::Real) { + self.transform_clip_to_coordinate_space(state, preserved_state); + } + + if !flags.contains(StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE) { + self.setup_clip_scroll_node_for_position(state, stacking_relative_border_box); + self.setup_clip_scroll_node_for_overflow(state, stacking_relative_border_box); + self.setup_clip_scroll_node_for_css_clip( + state, + preserved_state, + stacking_relative_border_box, + ); + } + self.base.clip = state + .clip_stack + .last() + .cloned() + .unwrap_or_else(Rect::max_rect); + + // We keep track of our position so that any stickily positioned elements can + // properly determine the extent of their movement relative to scrolling containers. + if !flags.contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK) + { + let border_box = if self.fragment.establishes_stacking_context() { + stacking_relative_border_box + } else { + self.stacking_relative_border_box(CoordinateSystem::Own) + }; + state.parent_stacking_relative_content_box = + self.fragment.stacking_relative_content_box(border_box) + } + + match self.positioning() { + StylePosition::Absolute | StylePosition::Relative | StylePosition::Fixed => { + state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling + }, + _ => {}, + } + + containing_clipping_and_scrolling + } + + fn setup_clip_scroll_node_for_position( + &mut self, + state: &mut StackingContextCollectionState, + border_box: Rect<Au>, + ) { + if self.positioning() != StylePosition::Sticky { + return; + } + + let sticky_position = self.sticky_position(); + if sticky_position.left == MaybeAuto::Auto && + sticky_position.right == MaybeAuto::Auto && + sticky_position.top == MaybeAuto::Auto && + sticky_position.bottom == MaybeAuto::Auto + { + return; + } + + // Since position: sticky elements always establish a stacking context, we will + // have previously calculated our border box in our own coordinate system. In + // order to properly calculate max offsets we need to compare our size and + // position in our parent's coordinate system. + let border_box_in_parent = self.stacking_relative_border_box(CoordinateSystem::Parent); + let margins = self.fragment.margin.to_physical( + self.base + .early_absolute_position_info + .relative_containing_block_mode, + ); + + // Position:sticky elements are always restricted based on the size and position of + // their containing block, which for sticky items is like relative and statically + // positioned items: just the parent block. + let constraint_rect = state.parent_stacking_relative_content_box; + + let to_offset_bound = |constraint_edge: Au, moving_edge: Au| -> f32 { + (constraint_edge - moving_edge).to_f32_px() + }; + + // This is the minimum negative offset and then the maximum positive offset. We just + // specify every edge, but if the corresponding margin is None, that offset has no effect. + let vertical_offset_bounds = StickyOffsetBounds::new( + to_offset_bound( + constraint_rect.min_y(), + border_box_in_parent.min_y() - margins.top, + ), + to_offset_bound(constraint_rect.max_y(), border_box_in_parent.max_y()), + ); + let horizontal_offset_bounds = StickyOffsetBounds::new( + to_offset_bound( + constraint_rect.min_x(), + border_box_in_parent.min_x() - margins.left, + ), + to_offset_bound(constraint_rect.max_x(), border_box_in_parent.max_x()), + ); + + // The margins control which edges have sticky behavior. + let sticky_frame_data = StickyFrameData { + margins: SideOffsets2D::new( + sticky_position.top.to_option().map(|v| v.to_f32_px()), + sticky_position.right.to_option().map(|v| v.to_f32_px()), + sticky_position.bottom.to_option().map(|v| v.to_f32_px()), + sticky_position.left.to_option().map(|v| v.to_f32_px()), + ), + vertical_offset_bounds, + horizontal_offset_bounds, + }; + + let new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode { + parent_index: self.clipping_and_scrolling().scrolling, + clip: ClippingRegion::from_rect(border_box.to_layout()), + content_rect: LayoutRect::zero(), + node_type: ClipScrollNodeType::StickyFrame(sticky_frame_data), + }); + + let new_clipping_and_scrolling = ClippingAndScrolling::simple(new_clip_scroll_index); + self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling); + state.current_clipping_and_scrolling = new_clipping_and_scrolling; + } + + fn setup_clip_scroll_node_for_overflow( + &mut self, + state: &mut StackingContextCollectionState, + border_box: Rect<Au>, + ) { + if !self.overflow_style_may_require_clip_scroll_node() { + return; + } + + let content_box = self.fragment.stacking_relative_content_box(border_box); + let has_scrolling_overflow = self.base.overflow.scroll.origin != Point2D::zero() || + self.base.overflow.scroll.size.width > content_box.size.width || + self.base.overflow.scroll.size.height > content_box.size.height || + StyleOverflow::Hidden == self.fragment.style.get_box().overflow_x || + StyleOverflow::Hidden == self.fragment.style.get_box().overflow_y; + + self.mark_scrolling_overflow(has_scrolling_overflow); + if !has_scrolling_overflow { + return; + } + + let sensitivity = if StyleOverflow::Hidden == self.fragment.style.get_box().overflow_x && + StyleOverflow::Hidden == self.fragment.style.get_box().overflow_y + { + ScrollSensitivity::Script + } else { + ScrollSensitivity::ScriptAndInputEvents + }; + + let border_widths = self + .fragment + .style + .logical_border_width() + .to_physical(self.fragment.style.writing_mode); + let clip_rect = border_box.inner_rect(border_widths); + + let mut clip = ClippingRegion::from_rect(clip_rect.to_layout()); + let radii = build_border_radius_for_inner_rect(border_box, &self.fragment.style); + if !radii.is_zero() { + clip.intersect_with_rounded_rect(clip_rect.to_layout(), radii) + } + + let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size; + let content_size = Size2D::new(content_size.x, content_size.y); + + let external_id = + ExternalScrollId(self.fragment.unique_id(), state.pipeline_id.to_webrender()); + let new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode { + parent_index: self.clipping_and_scrolling().scrolling, + clip: clip, + content_rect: Rect::new(content_box.origin, content_size).to_layout(), + node_type: ClipScrollNodeType::ScrollFrame(sensitivity, external_id), + }); + + let new_clipping_and_scrolling = ClippingAndScrolling::simple(new_clip_scroll_index); + self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling); + state.current_clipping_and_scrolling = new_clipping_and_scrolling; + } + + /// Adds a scroll root for a block to take the `clip` property into account + /// per CSS 2.1 § 11.1.2. + fn setup_clip_scroll_node_for_css_clip( + &mut self, + state: &mut StackingContextCollectionState, + preserved_state: &mut SavedStackingContextCollectionState, + stacking_relative_border_box: Rect<Au>, + ) { + // Account for `clip` per CSS 2.1 § 11.1.2. + let style_clip_rect = match self.fragment.style().get_effects().clip { + Either::First(style_clip_rect) => style_clip_rect, + _ => return, + }; + + // CSS `clip` should only apply to position:absolute or positione:fixed elements. + // CSS Masking Appendix A: "Applies to: Absolutely positioned elements." + match self.positioning() { + StylePosition::Absolute | StylePosition::Fixed => {}, + _ => return, + } + + fn extract_clip_component(p: &LengthOrAuto) -> Option<Au> { + match *p { + LengthOrAuto::Auto => None, + LengthOrAuto::LengthPercentage(ref length) => Some(Au::from(*length)), + } + } + + let clip_origin = Point2D::new( + stacking_relative_border_box.origin.x + + extract_clip_component(&style_clip_rect.left).unwrap_or_default(), + stacking_relative_border_box.origin.y + + extract_clip_component(&style_clip_rect.top).unwrap_or_default(), + ); + let right = extract_clip_component(&style_clip_rect.right) + .unwrap_or(stacking_relative_border_box.size.width); + let bottom = extract_clip_component(&style_clip_rect.bottom) + .unwrap_or(stacking_relative_border_box.size.height); + let clip_size = Size2D::new(right - clip_origin.x, bottom - clip_origin.y); + + let clip_rect = Rect::new(clip_origin, clip_size); + preserved_state.push_clip(state, clip_rect, self.positioning()); + + let new_index = state.add_clip_scroll_node(ClipScrollNode { + parent_index: self.clipping_and_scrolling().scrolling, + clip: ClippingRegion::from_rect(clip_rect.to_layout()), + content_rect: LayoutRect::zero(), // content_rect isn't important for clips. + node_type: ClipScrollNodeType::Clip, + }); + + let new_indices = ClippingAndScrolling::new(new_index, new_index); + self.base.clipping_and_scrolling = Some(new_indices); + state.current_clipping_and_scrolling = new_indices; + } + + fn create_pseudo_stacking_context_for_block( + &mut self, + stacking_context_type: StackingContextType, + parent_stacking_context_id: StackingContextId, + parent_clipping_and_scrolling: ClippingAndScrolling, + state: &mut StackingContextCollectionState, + ) { + let new_context = self.fragment.create_stacking_context( + self.base.stacking_context_id, + &self.base, + stacking_context_type, + None, + parent_clipping_and_scrolling, + ); + state.add_stacking_context(parent_stacking_context_id, new_context); + + self.base.collect_stacking_contexts_for_children(state); + + let children = state + .stacking_context_info + .get_mut(&self.base.stacking_context_id) + .map(|info| info.take_children()); + if let Some(children) = children { + for child in children { + if child.context_type == StackingContextType::PseudoFloat { + state.add_stacking_context(self.base.stacking_context_id, child); + } else { + state.add_stacking_context(parent_stacking_context_id, child); + } + } + } + } + + fn create_real_stacking_context_for_block( + &mut self, + parent_stacking_context_id: StackingContextId, + parent_clipping_and_scrolling: ClippingAndScrolling, + established_reference_frame: Option<ClipScrollNodeIndex>, + state: &mut StackingContextCollectionState, + ) { + let stacking_context = self.fragment.create_stacking_context( + self.base.stacking_context_id, + &self.base, + StackingContextType::Real, + established_reference_frame, + parent_clipping_and_scrolling, + ); + + state.add_stacking_context(parent_stacking_context_id, stacking_context); + self.base.collect_stacking_contexts_for_children(state); + } + + pub fn build_display_list_for_block_no_damage( + &self, + state: &mut DisplayListBuildState, + border_painting_mode: BorderPaintingMode, + ) { + let background_border_section = self.background_border_section(); + + state.processing_scrolling_overflow_element = self.has_scrolling_overflow(); + + let content_size = if state.processing_scrolling_overflow_element { + let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size; + Some(Size2D::new(content_size.x, content_size.y)) + } else { + None + }; + + let stacking_relative_border_box = self + .base + .stacking_relative_border_box_for_display_list(&self.fragment); + // Add the box that starts the block context. + self.fragment.build_display_list_no_damage( + state, + stacking_relative_border_box, + border_painting_mode, + background_border_section, + self.base.clip, + content_size, + ); + + self.base + .build_display_items_for_debugging_tint(state, self.fragment.node); + + state.processing_scrolling_overflow_element = false; + } + + pub fn build_display_list_for_block( + &mut self, + state: &mut DisplayListBuildState, + border_painting_mode: BorderPaintingMode, + ) { + self.fragment + .restyle_damage + .remove(ServoRestyleDamage::REPAINT); + self.build_display_list_for_block_no_damage(state, border_painting_mode); + } + + pub fn build_display_list_for_background_if_applicable_with_background( + &self, + state: &mut DisplayListBuildState, + background: &style_structs::Background, + background_color: RGBA, + ) { + let stacking_relative_border_box = self + .base + .stacking_relative_border_box_for_display_list(&self.fragment); + let background_border_section = self.background_border_section(); + + self.fragment + .build_display_list_for_background_if_applicable_with_background( + state, + self.fragment.style(), + background, + background_color, + background_border_section, + stacking_relative_border_box, + ) + } + + #[inline] + fn stacking_context_type( + &self, + flags: StackingContextCollectionFlags, + ) -> Option<StackingContextType> { + if flags.contains(StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT) { + return None; + } + + if self.fragment.establishes_stacking_context() { + return Some(StackingContextType::Real); + } + + if self + .base + .flags + .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) + { + return Some(StackingContextType::PseudoPositioned); + } + + if self.fragment.style.get_box().position != StylePosition::Static { + return Some(StackingContextType::PseudoPositioned); + } + + if self.base.flags.is_float() { + return Some(StackingContextType::PseudoFloat); + } + + None + } +} + +impl BaseFlow { + pub fn build_display_items_for_debugging_tint( + &self, + state: &mut DisplayListBuildState, + node: OpaqueNode, + ) { + if !opts::get().show_debug_parallel_layout { + return; + } + + let thread_id = self.thread_id; + let stacking_context_relative_bounds = Rect::new( + self.stacking_relative_position.to_point(), + self.position.size.to_physical(self.writing_mode), + ); + + let mut color = THREAD_TINT_COLORS[thread_id as usize % THREAD_TINT_COLORS.len()]; + color.a = 1.0; + let base = + state.create_base_display_item(self.clip, node, None, DisplayListSection::Content); + let bounds = stacking_context_relative_bounds.inflate(Au::from_px(2), Au::from_px(2)); + state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( + base, + webrender_api::BorderDisplayItem { + bounds: bounds.to_layout(), + common: items::empty_common_item_properties(), + widths: SideOffsets2D::new_all_same(Au::from_px(2)).to_layout(), + details: BorderDetails::Normal(border::simple( + color, + webrender_api::BorderStyle::Solid, + )), + }, + Vec::new(), + ))); + } +} + +/// Gets the cursor to use given the specific ComputedValues. `default_cursor` specifies +/// the cursor to use if `cursor` is `auto`. Typically, this will be `PointerCursor`, but for +/// text display items it may be `TextCursor` or `VerticalTextCursor`. +#[inline] +fn get_cursor(values: &ComputedValues, default_cursor: Cursor) -> Option<Cursor> { + let inherited_ui = values.get_inherited_ui(); + if inherited_ui.pointer_events == PointerEvents::None { + return None; + } + + Some(match inherited_ui.cursor.keyword { + CursorKind::Auto => default_cursor, + CursorKind::None => Cursor::None, + CursorKind::Default => Cursor::Default, + CursorKind::Pointer => Cursor::Pointer, + CursorKind::ContextMenu => Cursor::ContextMenu, + CursorKind::Help => Cursor::Help, + CursorKind::Progress => Cursor::Progress, + CursorKind::Wait => Cursor::Wait, + CursorKind::Cell => Cursor::Cell, + CursorKind::Crosshair => Cursor::Crosshair, + CursorKind::Text => Cursor::Text, + CursorKind::VerticalText => Cursor::VerticalText, + CursorKind::Alias => Cursor::Alias, + CursorKind::Copy => Cursor::Copy, + CursorKind::Move => Cursor::Move, + CursorKind::NoDrop => Cursor::NoDrop, + CursorKind::NotAllowed => Cursor::NotAllowed, + CursorKind::Grab => Cursor::Grab, + CursorKind::Grabbing => Cursor::Grabbing, + CursorKind::EResize => Cursor::EResize, + CursorKind::NResize => Cursor::NResize, + CursorKind::NeResize => Cursor::NeResize, + CursorKind::NwResize => Cursor::NwResize, + CursorKind::SResize => Cursor::SResize, + CursorKind::SeResize => Cursor::SeResize, + CursorKind::SwResize => Cursor::SwResize, + CursorKind::WResize => Cursor::WResize, + CursorKind::EwResize => Cursor::EwResize, + CursorKind::NsResize => Cursor::NsResize, + CursorKind::NeswResize => Cursor::NeswResize, + CursorKind::NwseResize => Cursor::NwseResize, + CursorKind::ColResize => Cursor::ColResize, + CursorKind::RowResize => Cursor::RowResize, + CursorKind::AllScroll => Cursor::AllScroll, + CursorKind::ZoomIn => Cursor::ZoomIn, + CursorKind::ZoomOut => Cursor::ZoomOut, + }) +} + +/// Adjusts borders as appropriate to account for a fragment's status as the +/// first or last fragment within the range of an element. +/// +/// Specifically, this function sets border widths to zero on the sides for +/// which the fragment is not outermost. +fn modify_border_width_for_inline_sides( + border_width: &mut LogicalMargin<Au>, + inline_border_info: InlineNodeBorderInfo, +) { + if !inline_border_info.is_first_fragment_of_element { + border_width.inline_start = Au(0); + } + + if !inline_border_info.is_last_fragment_of_element { + border_width.inline_end = Au(0); + } +} + +/// Describes how to paint the borders. +#[derive(Clone, Copy)] +pub enum BorderPaintingMode<'a> { + /// Paint borders separately (`border-collapse: separate`). + Separate, + /// Paint collapsed borders. + Collapse(&'a CollapsedBordersForCell), + /// Paint no borders. + Hidden, +} + +fn convert_text_run_to_glyphs( + text_run: Arc<TextRun>, + range: Range<ByteIndex>, + mut origin: Point2D<Au>, +) -> Vec<GlyphInstance> { + let mut glyphs = vec![]; + + for slice in text_run.natural_word_slices_in_visual_order(&range) { + for glyph in slice.glyphs.iter_glyphs_for_byte_range(&slice.range) { + let glyph_advance = if glyph.char_is_space() { + glyph.advance() + text_run.extra_word_spacing + } else { + glyph.advance() + }; + if !slice.glyphs.is_whitespace() { + let glyph_offset = glyph.offset().unwrap_or(Point2D::zero()); + let point = origin + glyph_offset.to_vector(); + let glyph = GlyphInstance { + index: glyph.id(), + point: point.to_layout(), + }; + glyphs.push(glyph); + } + origin.x += glyph_advance; + } + } + return glyphs; +} + +pub struct IndexableTextItem { + /// The placement of the text item on the plane. + pub origin: Point2D<Au>, + /// The text run. + pub text_run: Arc<TextRun>, + /// The range of text within the text run. + pub range: Range<ByteIndex>, + /// The position of the start of the baseline of this text. + pub baseline_origin: Point2D<Au>, +} + +#[derive(Default)] +pub struct IndexableText { + inner: FnvHashMap<OpaqueNode, Vec<IndexableTextItem>>, +} + +impl IndexableText { + fn insert(&mut self, node: OpaqueNode, item: IndexableTextItem) { + let entries = self.inner.entry(node).or_insert(Vec::new()); + entries.push(item); + } + + pub fn get(&self, node: OpaqueNode) -> Option<&[IndexableTextItem]> { + self.inner.get(&node).map(|x| x.as_slice()) + } + + // Returns the text index within a node for the point of interest. + pub fn text_index(&self, node: OpaqueNode, point_in_item: Point2D<Au>) -> Option<usize> { + let item = self.inner.get(&node)?; + // TODO(#20020): access all elements + let point = point_in_item + item[0].origin.to_vector(); + let offset = point - item[0].baseline_origin; + Some( + item[0] + .text_run + .range_index_of_advance(&item[0].range, offset.x), + ) + } +} + +trait ToF32Px { + type Output; + fn to_f32_px(&self) -> Self::Output; +} + +impl ToF32Px for TypedRect<Au> { + type Output = LayoutRect; + fn to_f32_px(&self) -> LayoutRect { + LayoutRect::from_untyped(&servo_geometry::au_rect_to_f32_rect(*self)) + } +} diff --git a/components/layout_2020/display_list/conversions.rs b/components/layout_2020/display_list/conversions.rs new file mode 100644 index 00000000000..4624b5f8dd0 --- /dev/null +++ b/components/layout_2020/display_list/conversions.rs @@ -0,0 +1,167 @@ +/* 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::{Point2D, Rect, SideOffsets2D, Size2D, Vector2D}; +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 style::values::RGBA; +use webrender_api as wr; + +pub trait ToLayout { + type Type; + fn to_layout(&self) -> 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 ToLayout for Filter { + type Type = wr::FilterOp; + fn to_layout(&self) -> Self::Type { + match *self { + Filter::Blur(radius) => wr::FilterOp::Blur(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), + // Statically check that DropShadow is impossible. + Filter::DropShadow(ref shadow) => match *shadow {}, + // 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, + } + } +} + +impl ToLayout for TransformStyle { + type Type = wr::TransformStyle; + fn to_layout(&self) -> Self::Type { + match *self { + TransformStyle::Auto | TransformStyle::Flat => wr::TransformStyle::Flat, + TransformStyle::Preserve3d => wr::TransformStyle::Preserve3D, + } + } +} + +impl ToLayout for RGBA { + type Type = wr::ColorF; + fn to_layout(&self) -> Self::Type { + wr::ColorF::new( + self.red_f32(), + self.green_f32(), + self.blue_f32(), + self.alpha_f32(), + ) + } +} + +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::new(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, + } + } +} diff --git a/components/layout_2020/display_list/gradient.rs b/components/layout_2020/display_list/gradient.rs new file mode 100644 index 00000000000..ab56b1d6c9c --- /dev/null +++ b/components/layout_2020/display_list/gradient.rs @@ -0,0 +1,323 @@ +/* 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 crate::display_list::ToLayout; +use app_units::Au; +use euclid::{Point2D, Size2D, Vector2D}; +use style::properties::ComputedValues; +use style::values::computed::image::{EndingShape, LineDirection}; +use style::values::computed::{Angle, GradientItem, LengthPercentage, Percentage, Position}; +use style::values::generics::image::{Circle, ColorStop, Ellipse, ShapeExtent}; +use webrender_api::{ExtendMode, Gradient, GradientBuilder, GradientStop, RadialGradient}; + +/// 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 explictly 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 explictly 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], + 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, position } => Some(ColorStop { + color, + position: Some(position), + }), + _ => None, + }) + .collect::<Vec<_>>(); + + assert!(stop_items.len() >= 2); + + // 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. + let mut last_stop_position = stop_items.first().unwrap().position.unwrap(); + for stop in stop_items.iter_mut().skip(1) { + if let Some(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.unwrap(); + } + } + + // 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.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(|&(_, ref stop)| stop.position.is_some()) + .unwrap(); + let end_offset = position_to_offset(end_stop.position.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(position) => { + stop_run = None; + position_to_offset(position, total_length) + }, + }; + assert!(offset.is_finite()); + stops.push(GradientStop { + offset: offset, + color: style.resolve_color(stop.color).to_layout(), + }) + } + 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], + direction: LineDirection, + repeating: bool, +) -> (Gradient, Vec<GradientStop>) { + use style::values::specified::position::HorizontalPositionKeyword::*; + use style::values::specified::position::VerticalPositionKeyword::*; + 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], + shape: EndingShape, + center: Position, + repeating: bool, +) -> (RadialGradient, Vec<GradientStop>) { + 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, ¢er), + 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, ¢er) + }, + }; + + 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(), + ) +} diff --git a/components/layout_2020/display_list/items.rs b/components/layout_2020/display_list/items.rs new file mode 100644 index 00000000000..b6674d972fe --- /dev/null +++ b/components/layout_2020/display_list/items.rs @@ -0,0 +1,795 @@ +/* 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 euclid::{SideOffsets2D, Vector2D}; +use gfx_traits::print_tree::PrintTree; +use gfx_traits::{self, StackingContextId}; +use msg::constellation_msg::PipelineId; +use net_traits::image::base::Image; +use servo_geometry::MaxRect; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::f32; +use std::fmt; +use style::computed_values::_servo_top_layer::T as InTopLayer; +use webrender_api as wr; +use webrender_api::units::{LayoutPoint, LayoutRect, LayoutSize, LayoutTransform}; +use webrender_api::{BorderRadius, ClipId, ClipMode, CommonItemProperties, ComplexClipRegion}; +use webrender_api::{ExternalScrollId, FilterOp, GlyphInstance, GradientStop, ImageKey}; +use webrender_api::{MixBlendMode, ScrollSensitivity, Shadow, SpatialId}; +use webrender_api::{StickyOffsetBounds, TransformStyle}; + +pub use style::dom::OpaqueNode; + +/// 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.get(0) { + Some(&DisplayItem::PushStackingContext(ref 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(); + } +} + +impl gfx_traits::DisplayList for DisplayList { + /// Analyze the display list to figure out if this may be the first + /// contentful paint (i.e. the display list contains items of type text, + /// image, non-white canvas or SVG). Used by metrics. + fn is_contentful(&self) -> bool { + for item in &self.list { + match item { + &DisplayItem::Text(_) | &DisplayItem::Image(_) => return true, + _ => (), + } + } + + false + } +} + +/// 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 estalishes. + pub established_reference_frame: Option<ClipScrollNodeIndex>, +} + +impl StackingContext { + /// Creates a new stacking context. + #[inline] + 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 { + 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, + })); + + 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>>, + pub vertical_offset_bounds: StickyOffsetBounds, + pub horizontal_offset_bounds: StickyOffsetBounds, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub enum ClipScrollNodeType { + Placeholder, + ScrollFrame(ScrollSensitivity, ExternalScrollId), + StickyFrame(StickyFrameData), + Clip, +} + +/// 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, +} + +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, + } + } + + pub fn is_placeholder(&self) -> bool { + self.node_type == ClipScrollNodeType::Placeholder + } +} + +/// 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>>), + 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), + pointing: 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_id: ClipId::root(wr::PipelineId::dummy()), + spatial_id: SpatialId::root_scroll_node(wr::PipelineId::dummy()), + hit_info: None, + is_backface_visible: false, + } +} + +/// 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, + /// Any complex regions. + /// + /// TODO(pcwalton): Atomically reference count these? Not sure if it's worth the trouble. + /// Measure and follow up. + pub complex: Vec<ComplexClipRegion>, +} + +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(), + complex: Vec::new(), + } + } + + /// Returns an all-encompassing clipping region that clips no pixels out. + #[inline] + pub fn max() -> ClippingRegion { + ClippingRegion { + main: LayoutRect::max_rect(), + complex: Vec::new(), + } + } + + /// Returns a clipping region that represents the given rectangle. + #[inline] + pub fn from_rect(rect: LayoutRect) -> ClippingRegion { + ClippingRegion { + main: rect, + complex: Vec::new(), + } + } + + /// Intersects this clipping region with the given rounded rectangle. + #[inline] + pub fn intersect_with_rounded_rect(&mut self, rect: LayoutRect, radii: BorderRadius) { + let new_complex_region = ComplexClipRegion { + rect, + radii, + mode: ClipMode::Clip, + }; + + // FIXME(pcwalton): This is O(n²) worst case for disjoint clipping regions. Is that OK? + // They're slow anyway… + // + // Possibly relevant if we want to do better: + // + // http://www.inrg.csie.ntu.edu.tw/algorithm2014/presentation/D&C%20Lee-84.pdf + for existing_complex_region in &mut self.complex { + if completely_encloses(&existing_complex_region, &new_complex_region) { + *existing_complex_region = new_complex_region; + return; + } + if completely_encloses(&new_complex_region, &existing_complex_region) { + return; + } + } + + self.complex.push(new_complex_region); + } +} + +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 if self.main == LayoutRect::max_rect() { + write!(f, "ClippingRegion(Complex={:?})", self.complex) + } else { + write!( + f, + "ClippingRegion(Rect={:?}, Complex={:?})", + self.main, self.complex + ) + } + } +} + +// TODO(pcwalton): This could be more aggressive by considering points that touch the inside of +// the border radius ellipse. +fn completely_encloses(this: &ComplexClipRegion, other: &ComplexClipRegion) -> bool { + let left = this.radii.top_left.width.max(this.radii.bottom_left.width); + let top = this.radii.top_left.height.max(this.radii.top_right.height); + let right = this + .radii + .top_right + .width + .max(this.radii.bottom_right.width); + let bottom = this + .radii + .bottom_left + .height + .max(this.radii.bottom_right.height); + let interior = LayoutRect::new( + LayoutPoint::new(this.rect.origin.x + left, this.rect.origin.y + top), + LayoutSize::new( + this.rect.size.width - left - right, + this.rect.size.height - top - bottom, + ), + ); + interior.origin.x <= other.rect.origin.x && + interior.origin.y <= other.rect.origin.y && + interior.max_x() >= other.rect.max_x() && + interior.max_y() >= other.rect.max_y() +} + +/// 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 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 pointing: Option<u16>, +} + +#[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, +} + +/// 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::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::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".to_owned(), + DisplayItem::Text(_) => "Text".to_owned(), + DisplayItem::Image(_) => "Image".to_owned(), + DisplayItem::Border(_) => "Border".to_owned(), + DisplayItem::Gradient(_) => "Gradient".to_owned(), + DisplayItem::RadialGradient(_) => "RadialGradient".to_owned(), + DisplayItem::Line(_) => "Line".to_owned(), + DisplayItem::BoxShadow(_) => "BoxShadow".to_owned(), + DisplayItem::PushTextShadow(_) => "PushTextShadow".to_owned(), + DisplayItem::PopAllTextShadows(_) => "PopTextShadow".to_owned(), + DisplayItem::Iframe(_) => "Iframe".to_owned(), + DisplayItem::PushStackingContext(_) | + DisplayItem::PopStackingContext(_) | + DisplayItem::DefineClipScrollNode(_) => "".to_owned(), + }, + 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>>; diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs new file mode 100644 index 00000000000..302728fbf22 --- /dev/null +++ b/components/layout_2020/display_list/mod.rs @@ -0,0 +1,19 @@ +/* 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/. */ + +pub use self::builder::BorderPaintingMode; +pub use self::builder::DisplayListBuildState; +pub use self::builder::IndexableText; +pub use self::builder::StackingContextCollectionFlags; +pub use self::builder::StackingContextCollectionState; +pub use self::conversions::ToLayout; +pub use self::webrender_helpers::WebRenderDisplayListConverter; + +mod background; +mod border; +mod builder; +mod conversions; +mod gradient; +pub mod items; +mod webrender_helpers; diff --git a/components/layout_2020/display_list/webrender_helpers.rs b/components/layout_2020/display_list/webrender_helpers.rs new file mode 100644 index 00000000000..6bdb1bfcfdc --- /dev/null +++ b/components/layout_2020/display_list/webrender_helpers.rs @@ -0,0 +1,321 @@ +/* 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 crate::display_list::items::{BaseDisplayItem, ClipScrollNode, ClipScrollNodeType}; +use crate::display_list::items::{DisplayItem, DisplayList, StackingContextType}; +use msg::constellation_msg::PipelineId; +use webrender_api::units::LayoutPoint; +use webrender_api::{self, ClipId, CommonItemProperties, DisplayItem as WrDisplayItem}; +use webrender_api::{DisplayListBuilder, PropertyBinding, PushStackingContextDisplayItem}; +use webrender_api::{ + RasterSpace, ReferenceFrameKind, SpaceAndClipInfo, SpatialId, StackingContext, +}; + +pub trait WebRenderDisplayListConverter { + fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder; +} + +struct ClipScrollState { + clip_ids: Vec<Option<ClipId>>, + spatial_ids: Vec<Option<SpatialId>>, + active_clip_id: ClipId, + active_spatial_id: SpatialId, +} + +trait WebRenderDisplayItemConverter { + fn convert_to_webrender( + &mut self, + clip_scroll_nodes: &[ClipScrollNode], + state: &mut ClipScrollState, + builder: &mut DisplayListBuilder, + ); +} + +impl WebRenderDisplayListConverter for DisplayList { + fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder { + let mut clip_ids = vec![None; self.clip_scroll_nodes.len()]; + let mut spatial_ids = vec![None; self.clip_scroll_nodes.len()]; + + // We need to add the WebRender root reference frame and root scroll node ids + // here manually, because WebRender creates these 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. + + let webrender_pipeline = pipeline_id.to_webrender(); + clip_ids[0] = Some(ClipId::root(webrender_pipeline)); + clip_ids[1] = Some(ClipId::root(webrender_pipeline)); + spatial_ids[0] = Some(SpatialId::root_reference_frame(webrender_pipeline)); + spatial_ids[1] = Some(SpatialId::root_scroll_node(webrender_pipeline)); + + let mut state = ClipScrollState { + clip_ids, + spatial_ids, + active_clip_id: ClipId::root(webrender_pipeline), + active_spatial_id: SpatialId::root_scroll_node(webrender_pipeline), + }; + + let mut builder = DisplayListBuilder::with_capacity( + webrender_pipeline, + self.bounds().size, + 1024 * 1024, // 1 MB of space + ); + + for item in &mut self.list { + item.convert_to_webrender(&self.clip_scroll_nodes, &mut state, &mut builder); + } + + builder + } +} + +impl WebRenderDisplayItemConverter for DisplayItem { + fn convert_to_webrender( + &mut self, + clip_scroll_nodes: &[ClipScrollNode], + state: &mut ClipScrollState, + builder: &mut DisplayListBuilder, + ) { + // 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 cur_spatial_id = state.spatial_ids[clip_and_scroll_indices.scrolling.to_index()] + .expect("Tried to use WebRender SpatialId before it was defined."); + if cur_spatial_id != state.active_spatial_id { + state.active_spatial_id = cur_spatial_id; + } + + let internal_clip_id = clip_and_scroll_indices + .clipping + .unwrap_or(clip_and_scroll_indices.scrolling); + let cur_clip_id = state.clip_ids[internal_clip_id.to_index()] + .expect("Tried to use WebRender ClipId before it was defined."); + if cur_clip_id != state.active_clip_id { + state.active_clip_id = cur_clip_id; + } + + match *self { + DisplayItem::Rectangle(ref mut item) => { + item.item.common = build_common_item_properties(&item.base, state); + builder.push_item(&WrDisplayItem::Rectangle(item.item)); + }, + DisplayItem::Text(ref mut item) => { + item.item.common = build_common_item_properties(&item.base, state); + builder.push_item(&WrDisplayItem::Text(item.item)); + builder.push_iter(item.data.iter()); + }, + DisplayItem::Image(ref mut item) => { + item.item.common = build_common_item_properties(&item.base, state); + builder.push_item(&WrDisplayItem::Image(item.item)); + }, + DisplayItem::Border(ref mut item) => { + item.item.common = build_common_item_properties(&item.base, state); + if !item.data.is_empty() { + builder.push_stops(item.data.as_ref()); + } + builder.push_item(&WrDisplayItem::Border(item.item)); + }, + DisplayItem::Gradient(ref mut item) => { + item.item.common = build_common_item_properties(&item.base, state); + builder.push_stops(item.data.as_ref()); + builder.push_item(&WrDisplayItem::Gradient(item.item)); + }, + DisplayItem::RadialGradient(ref mut item) => { + item.item.common = build_common_item_properties(&item.base, state); + builder.push_stops(item.data.as_ref()); + builder.push_item(&WrDisplayItem::RadialGradient(item.item)); + }, + DisplayItem::Line(ref mut item) => { + item.item.common = build_common_item_properties(&item.base, state); + builder.push_item(&WrDisplayItem::Line(item.item)); + }, + DisplayItem::BoxShadow(ref mut item) => { + item.item.common = build_common_item_properties(&item.base, state); + builder.push_item(&WrDisplayItem::BoxShadow(item.item)); + }, + DisplayItem::PushTextShadow(ref mut item) => { + let common = build_common_item_properties(&item.base, state); + builder.push_shadow( + &SpaceAndClipInfo { + spatial_id: common.spatial_id, + clip_id: common.clip_id, + }, + item.shadow, + true, + ); + }, + DisplayItem::PopAllTextShadows(_) => { + builder.push_item(&WrDisplayItem::PopAllShadows); + }, + DisplayItem::Iframe(ref mut item) => { + let common = build_common_item_properties(&item.base, state); + builder.push_iframe( + item.bounds, + common.clip_rect, + &SpaceAndClipInfo { + spatial_id: common.spatial_id, + clip_id: common.clip_id, + }, + item.iframe.to_webrender(), + true, + ); + }, + DisplayItem::PushStackingContext(ref mut item) => { + let stacking_context = &item.stacking_context; + debug_assert_eq!(stacking_context.context_type, StackingContextType::Real); + + //let mut info = webrender_api::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), + (Some(t), Some(p)) => ( + t.pre_mul(&p), + ReferenceFrameKind::Perspective { + scrolling_relative_to: None, + }, + ), + (None, None) => unreachable!(), + }; + + let spatial_id = builder.push_reference_frame( + stacking_context.bounds.origin, + state.active_spatial_id, + stacking_context.transform_style, + PropertyBinding::Value(transform), + ref_frame, + ); + + state.spatial_ids[frame_index.to_index()] = Some(spatial_id); + state.clip_ids[frame_index.to_index()] = Some(cur_clip_id); + + bounds.origin = LayoutPoint::zero(); + spatial_id + } else { + state.active_spatial_id + }; + + if !stacking_context.filters.is_empty() { + builder.push_item(&WrDisplayItem::SetFilterOps); + builder.push_iter(&stacking_context.filters); + } + + let wr_item = PushStackingContextDisplayItem { + origin: bounds.origin, + spatial_id, + is_backface_visible: true, + stacking_context: StackingContext { + transform_style: stacking_context.transform_style, + mix_blend_mode: stacking_context.mix_blend_mode, + clip_id: None, + raster_space: RasterSpace::Screen, + // TODO(pcwalton): Enable picture caching? + cache_tiles: false, + }, + }; + + builder.push_item(&WrDisplayItem::PushStackingContext(wr_item)); + }, + DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(), + DisplayItem::DefineClipScrollNode(ref mut item) => { + let node = &clip_scroll_nodes[item.node_index.to_index()]; + let item_rect = node.clip.main; + + let parent_spatial_id = state.spatial_ids[node.parent_index.to_index()] + .expect("Tried to use WebRender parent SpatialId before it was defined."); + let parent_clip_id = state.clip_ids[node.parent_index.to_index()] + .expect("Tried to use WebRender parent ClipId before it was defined."); + + match node.node_type { + ClipScrollNodeType::Clip => { + let id = builder.define_clip( + &SpaceAndClipInfo { + clip_id: parent_clip_id, + spatial_id: parent_spatial_id, + }, + item_rect, + node.clip.complex.clone(), + None, + ); + + state.spatial_ids[item.node_index.to_index()] = Some(parent_spatial_id); + state.clip_ids[item.node_index.to_index()] = Some(id); + }, + ClipScrollNodeType::ScrollFrame(scroll_sensitivity, external_id) => { + let space_clip_info = builder.define_scroll_frame( + &SpaceAndClipInfo { + clip_id: parent_clip_id, + spatial_id: parent_spatial_id, + }, + Some(external_id), + node.content_rect, + node.clip.main, + node.clip.complex.clone(), + None, + scroll_sensitivity, + webrender_api::units::LayoutVector2D::zero(), + ); + + state.clip_ids[item.node_index.to_index()] = Some(space_clip_info.clip_id); + state.spatial_ids[item.node_index.to_index()] = + Some(space_clip_info.spatial_id); + }, + 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, + webrender_api::units::LayoutVector2D::zero(), + ); + + state.spatial_ids[item.node_index.to_index()] = Some(id); + state.clip_ids[item.node_index.to_index()] = Some(parent_clip_id); + }, + ClipScrollNodeType::Placeholder => { + unreachable!("Found DefineClipScrollNode for Placeholder type node."); + }, + }; + }, + } + } +} + +fn build_common_item_properties( + base: &BaseDisplayItem, + state: &ClipScrollState, +) -> CommonItemProperties { + let tag = match base.metadata.pointing { + Some(cursor) => Some((base.metadata.node.0 as u64, cursor)), + None => None, + }; + CommonItemProperties { + clip_rect: base.clip_rect, + spatial_id: state.active_spatial_id, + clip_id: state.active_clip_id, + // TODO(gw): Make use of the WR backface visibility functionality. + is_backface_visible: true, + hit_info: tag, + } +} |