/* 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 http://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. #![deny(unsafe_code)] use app_units::{Au, AU_PER_PX}; use block::{BlockFlow, BlockStackingContextType}; use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg}; use context::LayoutContext; use euclid::{Point2D, Rect, SideOffsets2D, Size2D, Transform3D, TypedRect, TypedSize2D, Vector2D}; use flex::FlexFlow; use flow::{BaseFlow, Flow, FlowFlags}; use flow_ref::FlowRef; use fnv::FnvHashMap; use fragment::{CanvasFragmentSource, CoordinateSystem, Fragment, ScannedTextFragmentInfo}; use fragment::SpecificFragmentInfo; use gfx::display_list; use gfx::display_list::{BaseDisplayItem, BorderDetails, BorderDisplayItem, BLUR_INFLATION_FACTOR}; use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClipScrollNode}; use gfx::display_list::{ClipScrollNodeIndex, ClipScrollNodeType, ClippingAndScrolling}; use gfx::display_list::{ClippingRegion, DisplayItem, DisplayItemMetadata, DisplayList}; use gfx::display_list::{DisplayListSection, GradientDisplayItem, IframeDisplayItem, ImageBorder}; use gfx::display_list::{ImageDisplayItem, LineDisplayItem, NormalBorder, OpaqueNode}; use gfx::display_list::{PopAllTextShadowsDisplayItem, PushTextShadowDisplayItem}; use gfx::display_list::{RadialGradientDisplayItem, SolidColorDisplayItem, StackingContext}; use gfx::display_list::{StackingContextType, StickyFrameData, TextDisplayItem, TextOrientation}; use gfx::display_list::WebRenderImageInfo; use gfx_traits::{combine_id_with_fragment_type, FragmentType, StackingContextId}; use inline::{InlineFlow, InlineFragmentNodeFlags}; use ipc_channel::ipc; use list_item::ListItemFlow; use model::{self, MaybeAuto}; use msg::constellation_msg::{BrowsingContextId, PipelineId}; use net_traits::image::base::PixelFormat; use net_traits::image_cache::UsePlaceholder; use range::Range; use script_layout_interface::wrapper_traits::PseudoElementType; use servo_config::opts; use servo_geometry::max_rect; use std::{cmp, f32}; use std::default::Default; use std::mem; use std::sync::Arc; 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::computed_values::border_style::T as BorderStyle; use style::computed_values::cursor; use style::computed_values::image_rendering::T as ImageRendering; 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, LogicalSize, WritingMode}; use style::properties::ComputedValues; use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword; use style::properties::style_structs; use style::servo::restyle_damage::ServoRestyleDamage; use style::values::{Either, RGBA}; use style::values::computed::{Angle, Gradient, GradientItem, LengthOrPercentage, Percentage}; use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position}; use style::values::computed::effects::SimpleShadow; use style::values::computed::image::{EndingShape, LineDirection}; use style::values::generics::background::BackgroundSize; use style::values::generics::effects::Filter; use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape}; use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind}; use style::values::generics::image::{Image, ShapeExtent}; use style::values::generics::image::PaintWorklet; use style::values::specified::background::RepeatKeyword as BackgroundRepeatKeyword; use style::values::specified::position::{X, Y}; use style_traits::CSSPixel; use style_traits::ToCss; use style_traits::cursor::Cursor; use table_cell::CollapsedBordersForCell; use webrender_api::{ClipId, ClipMode, ColorF, ComplexClipRegion, GradientStop, LineStyle}; use webrender_api::{LocalClip, RepeatMode, ScrollPolicy, ScrollSensitivity, StickyOffsetBounds}; use webrender_helpers::{ToBorderRadius, ToMixBlendMode, ToRectF, ToTransformStyle}; trait ResolvePercentage { fn resolve(&self, length: u32) -> u32; } impl ResolvePercentage for NumberOrPercentage { fn resolve(&self, length: u32) -> u32 { match *self { NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as u32, NumberOrPercentage::Number(n) => n.round() as u32, } } } fn convert_repeat_mode(from: RepeatKeyword) -> RepeatMode { match from { RepeatKeyword::Stretch => RepeatMode::Stretch, RepeatKeyword::Repeat => RepeatMode::Repeat, RepeatKeyword::Round => RepeatMode::Round, RepeatKeyword::Space => RepeatMode::Space, } } fn establishes_containing_block_for_absolute( flags: StackingContextCollectionFlags, positioning: StylePosition, ) -> bool { !flags.contains(StackingContextCollectionFlags::NEVER_CREATES_CONTAINING_BLOCK) && StylePosition::Static != positioning } trait RgbColor { fn rgb(r: u8, g: u8, b: u8) -> Self; } impl RgbColor for ColorF { fn rgb(r: u8, g: u8, b: u8) -> Self { ColorF { r: (r as f32) / (255.0 as f32), g: (g as f32) / (255.0 as f32), b: (b as f32) / (255.0 as f32), a: 1.0 as f32, } } } 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, }, ]; fn get_cyclic(arr: &[T], index: usize) -> &T { &arr[index % arr.len()] } pub struct InlineNodeBorderInfo { is_first_fragment_of_element: bool, is_last_fragment_of_element: bool, } #[derive(Debug)] struct StackingContextInfo { children: Vec, clip_scroll_nodes: Vec, } impl StackingContextInfo { fn new() -> StackingContextInfo { StackingContextInfo { children: Vec::new(), clip_scroll_nodes: Vec::new(), } } fn take_children(&mut self) -> Vec { 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, pub clip_scroll_nodes: Vec, /// 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 stacking real context id, which doesn't include pseudo-stacking contexts. 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 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>, /// 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>, /// The flow parent's content box, used to calculate sticky constraints. parent_stacking_relative_content_box: Rect, } impl StackingContextCollectionState { pub fn new(pipeline_id: PipelineId) -> StackingContextCollectionState { let root_clip_indices = ClippingAndScrolling::simple(ClipScrollNodeIndex(0)); // This is just a dummy node to take up a slot in the array. WebRender // takes care of adding this root node and it can be ignored during DL conversion. let root_node = ClipScrollNode { id: Some(ClipId::root_scroll_node(pipeline_id.to_webrender())), parent_index: ClipScrollNodeIndex(0), clip: ClippingRegion::from_rect(&TypedRect::zero()), content_rect: Rect::zero(), node_type: ClipScrollNodeType::ScrollFrame(ScrollSensitivity::ScriptAndInputEvents), }; StackingContextCollectionState { pipeline_id: pipeline_id, root_stacking_context: StackingContext::root(), stacking_context_info: FnvHashMap::default(), clip_scroll_nodes: vec![root_node], current_stacking_context_id: StackingContextId::root(), current_real_stacking_context_id: StackingContextId::root(), next_stacking_context_id: StackingContextId::root().next(), 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 generate_stacking_context_id(&mut self) -> StackingContextId { let next_stacking_context_id = self.next_stacking_context_id.next(); mem::replace(&mut self.next_stacking_context_id, next_stacking_context_id) } fn add_stacking_context( &mut self, parent_id: StackingContextId, stacking_context: StackingContext, ) { let info = self.stacking_context_info .entry(parent_id) .or_insert(StackingContextInfo::new()); info.children.push(stacking_context); } fn add_clip_scroll_node(&mut self, clip_scroll_node: ClipScrollNode) -> ClipScrollNodeIndex { // 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(clip_scroll_node); let index = ClipScrollNodeIndex(self.clip_scroll_nodes.len() - 1); let info = self.stacking_context_info .entry(self.current_real_stacking_context_id) .or_insert(StackingContextInfo::new()); info.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, /// A vector of ClipScrollNodes which will be given ids during WebRender DL conversion. pub clip_scroll_nodes: Vec, /// The items in this display list. pub items: FnvHashMap>, /// 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<(BrowsingContextId, TypedSize2D)>, } impl<'a> DisplayListBuildState<'a> { pub fn new( layout_context: &'a LayoutContext, state: StackingContextCollectionState, ) -> DisplayListBuildState<'a> { let root_clip_indices = ClippingAndScrolling::simple(ClipScrollNodeIndex(0)); 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: root_clip_indices, iframe_sizes: Vec::new(), } } 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 parent_clip_scroll_node_index(&self, index: ClipScrollNodeIndex) -> ClipScrollNodeIndex { if index.is_root_scroll_node() { return index; } self.clip_scroll_nodes[index.0].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 } fn create_base_display_item( &self, bounds: &Rect, clip: LocalClip, node: OpaqueNode, cursor: Option, 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 }; BaseDisplayItem::new( &bounds, DisplayItemMetadata { node: node, pointing: cursor, }, clip, section, self.current_stacking_context_id, clipping_and_scrolling, ) } 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, 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_or_else(StackingContextInfo::new); 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, mut child_items: Vec, child_stacking_contexts: Vec, ) { // 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); } } } /// 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); pub enum IdType { StackingContext, OverflowClip, CSSClip, } pub trait FragmentDisplayListBuilding { fn collect_stacking_contexts_for_blocklike_fragment( &mut self, state: &mut StackingContextCollectionState, ) -> bool; /// 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, ); /// Determines where to place an element background image or gradient. /// /// Photos have their resolution as intrinsic size while gradients have /// no intrinsic size. fn compute_background_placement( &self, state: &mut DisplayListBuildState, style: &ComputedValues, absolute_bounds: Rect, intrinsic_size: Option>, index: usize, ) -> BackgroundPlacement; /// 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, webrender_image: WebRenderImageInfo, index: usize, ); /// 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: Size2D, ) -> Option; /// 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, gradient: &Gradient, style: &ComputedValues, index: usize, ); /// 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_node_info: Option, border_painting_mode: BorderPaintingMode, bounds: &Rect, display_list_section: DisplayListSection, clip: &Rect, ); /// 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, bounds: &Rect, clip: &Rect, ); /// 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, clip: &Rect, ); /// 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, stacking_relative_content_box: &Rect, text_fragment: &ScannedTextFragmentInfo, clip: &Rect, ); /// 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, clip: &Rect, ); /// 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. /// * `stacking_relative_flow_origin`: Position of the origin of the owning flow with respect /// to its nearest ancestor stacking context. /// * `relative_containing_block_size`: The size of the containing block that /// `position: relative` makes use of. /// * `clip`: The region to clip the display items to. fn build_display_list( &mut self, state: &mut DisplayListBuildState, stacking_relative_flow_origin: &Vector2D, relative_containing_block_size: &LogicalSize, relative_containing_block_mode: WritingMode, border_painting_mode: BorderPaintingMode, display_list_section: DisplayListSection, clip: &Rect, ); /// 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, display_list_section: DisplayListSection, clip: &Rect, ); /// 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, text_shadows: &[SimpleShadow], clip: &Rect, ); /// 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, clip: &Rect, ); /// A helper method that `build_display_list` calls to create per-fragment-type display items. fn build_fragment_type_specific_display_items( &mut self, state: &mut DisplayListBuildState, stacking_relative_border_box: &Rect, clip: &Rect, ); /// Creates a stacking context for associated fragment. fn create_stacking_context( &self, id: StackingContextId, base_flow: &BaseFlow, scroll_policy: ScrollPolicy, context_type: StackingContextType, parent_clipping_and_scrolling: ClippingAndScrolling, ) -> StackingContext; fn unique_id(&self, id_type: IdType) -> u64; fn fragment_type(&self) -> FragmentType; } fn handle_overlapping_radii(size: &Size2D, radii: &BorderRadii) -> BorderRadii { // 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: Au, radius_b: Au, edge_length: Au) -> f32 { let required = radius_a + radius_b; if required <= edge_length { 1.0 } else { edge_length.to_f32_px() / required.to_f32_px() } } 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 { radii.scale_by(min_factor) } else { *radii } } fn build_border_radius( abs_bounds: &Rect, border_style: &style_structs::Border, ) -> BorderRadii { // 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. handle_overlapping_radii( &abs_bounds.size, &BorderRadii { top_left: model::specified_border_radius( border_style.border_top_left_radius, abs_bounds.size, ), top_right: model::specified_border_radius( border_style.border_top_right_radius, abs_bounds.size, ), bottom_right: model::specified_border_radius( border_style.border_bottom_right_radius, abs_bounds.size, ), bottom_left: model::specified_border_radius( border_style.border_bottom_left_radius, abs_bounds.size, ), }, ) } /// 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, style: &ComputedValues, ) -> BorderRadii { let radii = build_border_radius(&outer_rect, style.get_border()); if radii.is_square() { 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); calculate_inner_border_radii(radii, border_widths) } fn build_inner_border_box_for_border_rect( border_box: &Rect, style: &ComputedValues, ) -> Rect { let border_widths = style.logical_border_width().to_physical(style.writing_mode); let mut inner_border_box = *border_box; inner_border_box.origin.x += border_widths.left; inner_border_box.origin.y += border_widths.top; inner_border_box.size.width -= border_widths.right + border_widths.left; inner_border_box.size.height -= border_widths.bottom + border_widths.top; inner_border_box } fn convert_gradient_stops(gradient_items: &[GradientItem], total_length: Au) -> Vec { // 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 { GenericGradientItem::ColorStop(ref stop) => Some(*stop), _ => None, }) .collect::>(); 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(LengthOrPercentage::Percentage(Percentage(0.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(LengthOrPercentage::Percentage(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. // Note: Remove the + 2 if fix_gradient_stops is changed. let mut stops = Vec::with_capacity(stop_items.len() + 2); 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: start_offset, end_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: stop.color.to_gfx_color(), }) } stops } fn convert_linear_gradient( size: Size2D, stops: &[GradientItem], direction: LineDirection, repeating: bool, ) -> display_list::Gradient { let angle = match direction { LineDirection::Angle(angle) => angle.radians(), LineDirection::Horizontal(x) => match x { X::Left => Angle::Deg(270.).radians(), X::Right => Angle::Deg(90.).radians(), }, LineDirection::Vertical(y) => match y { Y::Top => Angle::Deg(0.).radians(), Y::Bottom => Angle::Deg(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) { (X::Right, Y::Bottom) => f32::consts::PI - atan, (X::Left, Y::Bottom) => f32::consts::PI + atan, (X::Right, Y::Top) => atan, (X::Left, Y::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 stops = convert_gradient_stops(stops, length); // Only clamped gradients need to be fixed because in repeating gradients // there is no "first" or "last" stop because they repeat infinitly in // both directions, so the rendering is always correct. if !repeating { fix_gradient_stops(&mut stops); } let center = Point2D::new(size.width / 2, size.height / 2); display_list::Gradient { start_point: center - delta, end_point: center + delta, stops: stops, repeating: repeating, } } fn convert_radial_gradient( size: Size2D, stops: &[GradientItem], shape: EndingShape, center: Position, repeating: bool, ) -> display_list::RadialGradient { let center = Point2D::new( center.horizontal.to_used_value(size.width), center.vertical.to_used_value(size.height), ); let radius = match shape { GenericEndingShape::Circle(Circle::Radius(length)) => { let length = Au::from(length); Size2D::new(length, length) }, GenericEndingShape::Circle(Circle::Extent(extent)) => { convert_circle_size_keyword(extent, &size, ¢er) }, GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => { Size2D::new(x.to_used_value(size.width), y.to_used_value(size.height)) }, GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => { convert_ellipse_size_keyword(extent, &size, ¢er) }, }; let mut stops = convert_gradient_stops(stops, radius.width); // Repeating gradients have no last stops that can be ignored. So // fixup is not necessary but may actually break the gradient. if !repeating { fix_gradient_stops(&mut stops); } display_list::RadialGradient { center: center, radius: radius, stops: stops, repeating: repeating, } } #[inline] /// Duplicate the first and last stops if necessary. /// /// Explanation by pyfisch: /// If the last stop is at the same position as the previous stop the /// last color is ignored by webrender. This differs from the spec /// (I think so). The implementations of Chrome and Firefox seem /// to have the same problem but work fine if the position of the last /// stop is smaller than 100%. (Otherwise they ignore the last stop.) /// /// Similarly the first stop is duplicated if it is not placed /// at the start of the virtual gradient ray. fn fix_gradient_stops(stops: &mut Vec) { if stops.first().unwrap().offset > 0.0 { let color = stops.first().unwrap().color; stops.insert( 0, GradientStop { offset: 0.0, color: color, }, ) } if stops.last().unwrap().offset < 1.0 { let color = stops.last().unwrap().color; stops.push(GradientStop { offset: 1.0, color: color, }) } } /// Returns the the distance to the nearest or farthest corner depending on the comperator. fn get_distance_to_corner(size: &Size2D, center: &Point2D, cmp: F) -> Au where F: Fn(Au, Au) -> Au, { let dist = get_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 get_distance_to_sides(size: &Size2D, center: &Point2D, cmp: F) -> Size2D 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)) } /// Returns the radius for an ellipse with the same ratio as if it was matched to the sides. fn get_ellipse_radius(size: &Size2D, center: &Point2D, cmp: F) -> Size2D where F: Fn(Au, Au) -> Au, { let dist = get_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 a circle if it was not explictly provided. /// fn convert_circle_size_keyword( keyword: ShapeExtent, size: &Size2D, center: &Point2D, ) -> Size2D { let radius = match keyword { ShapeExtent::ClosestSide | ShapeExtent::Contain => { let dist = get_distance_to_sides(size, center, ::std::cmp::min); ::std::cmp::min(dist.width, dist.height) }, ShapeExtent::FarthestSide => { let dist = get_distance_to_sides(size, center, ::std::cmp::max); ::std::cmp::max(dist.width, dist.height) }, ShapeExtent::ClosestCorner => get_distance_to_corner(size, center, ::std::cmp::min), ShapeExtent::FarthestCorner | ShapeExtent::Cover => { get_distance_to_corner(size, center, ::std::cmp::max) }, }; Size2D::new(radius, radius) } /// Determines the radius of an ellipse if it was not explictly provided. /// fn convert_ellipse_size_keyword( keyword: ShapeExtent, size: &Size2D, center: &Point2D, ) -> Size2D { match keyword { ShapeExtent::ClosestSide | ShapeExtent::Contain => { get_distance_to_sides(size, center, ::std::cmp::min) }, ShapeExtent::FarthestSide => get_distance_to_sides(size, center, ::std::cmp::max), ShapeExtent::ClosestCorner => get_ellipse_radius(size, center, ::std::cmp::min), ShapeExtent::FarthestCorner | ShapeExtent::Cover => { get_ellipse_radius(size, center, ::std::cmp::max) }, } } /// Subtract offsets from a bounding box. /// /// As an example if the bounds are the border-box and the border /// is provided as offsets the result will be the padding-box. fn calculate_inner_bounds(mut bounds: Rect, offsets: SideOffsets2D) -> Rect { bounds.origin.x += offsets.left; bounds.origin.y += offsets.top; bounds.size.width -= offsets.horizontal(); bounds.size.height -= offsets.vertical(); bounds } fn calculate_inner_border_radii( mut radii: BorderRadii, offsets: SideOffsets2D, ) -> BorderRadii { radii.top_left.width = cmp::max(Au(0), radii.top_left.width - offsets.left); radii.bottom_left.width = cmp::max(Au(0), radii.bottom_left.width - offsets.left); radii.top_right.width = cmp::max(Au(0), radii.top_right.width - offsets.right); radii.bottom_right.width = cmp::max(Au(0), radii.bottom_right.width - offsets.right); radii.top_left.height = cmp::max(Au(0), radii.top_left.height - offsets.top); radii.top_right.height = cmp::max(Au(0), radii.top_right.height - offsets.top); radii.bottom_left.height = cmp::max(Au(0), radii.bottom_left.height - offsets.bottom); radii.bottom_right.height = cmp::max(Au(0), radii.bottom_right.height - offsets.bottom); radii } /// 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, intrinsic_size: Option>, ) -> Size2D { match intrinsic_size { None => match bg_size { BackgroundSize::Cover | BackgroundSize::Contain => bounds_size, BackgroundSize::Explicit { width, height } => Size2D::new( MaybeAuto::from_style(width, bounds_size.width) .specified_or_default(bounds_size.width), MaybeAuto::from_style(height, bounds_size.height) .specified_or_default(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::Explicit { width, height: LengthOrPercentageOrAuto::Auto, }, _, ) => { let width = MaybeAuto::from_style(width, bounds_size.width) .specified_or_default(own_size.width); Size2D::new(width, width.scale_by(image_aspect_ratio.recip())) }, ( BackgroundSize::Explicit { width: LengthOrPercentageOrAuto::Auto, height, }, _, ) => { let height = MaybeAuto::from_style(height, bounds_size.height) .specified_or_default(own_size.height); Size2D::new(height.scale_by(image_aspect_ratio), height) }, (BackgroundSize::Explicit { width, height }, _) => Size2D::new( MaybeAuto::from_style(width, bounds_size.width) .specified_or_default(own_size.width), MaybeAuto::from_style(height, bounds_size.height) .specified_or_default(own_size.height), ), } }, } } 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); }, } } impl FragmentDisplayListBuilding for Fragment { 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, } } fn build_display_list_for_background_if_applicable( &self, state: &mut DisplayListBuildState, style: &ComputedValues, display_list_section: DisplayListSection, absolute_bounds: &Rect, ) { // 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". let background = style.get_background(); let background_color = style.resolve_color(background.background_color); // 'background-clip' determines the area within which the background is painted. // http://dev.w3.org/csswg/css-backgrounds-3/#the-background-clip let mut bounds = *absolute_bounds; // This is the clip for the color (which is the last element in the bg array) let color_clip = get_cyclic( &background.background_clip.0, background.background_image.0.len() - 1, ); // Adjust the clipping region as necessary to account for `border-radius`. let mut border_radii = build_border_radius(absolute_bounds, style.get_border()); match *color_clip { BackgroundClip::BorderBox => {}, BackgroundClip::PaddingBox => { let border = style.logical_border_width().to_physical(style.writing_mode); bounds = calculate_inner_bounds(bounds, border); border_radii = calculate_inner_border_radii(border_radii, border); }, BackgroundClip::ContentBox => { let border_padding = self.border_padding.to_physical(style.writing_mode); bounds = calculate_inner_bounds(bounds, border_padding); border_radii = calculate_inner_border_radii(border_radii, border_padding); }, } let clip = if !border_radii.is_square() { LocalClip::RoundedRect( bounds.to_rectf(), ComplexClipRegion::new( bounds.to_rectf(), border_radii.to_border_radius(), ClipMode::Clip, ), ) } else { LocalClip::Rect(bounds.to_rectf()) }; let base = state.create_base_display_item( &bounds, clip, self.node, style.get_cursor(Cursor::Default), display_list_section, ); state.add_display_item(DisplayItem::SolidColor(Box::new(SolidColorDisplayItem { base: base, color: background_color.to_gfx_color(), }))); // 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() { match *background_image { Either::First(_) => {}, Either::Second(Image::Gradient(ref gradient)) => { self.build_display_list_for_background_gradient( state, display_list_section, *absolute_bounds, gradient, style, i, ); }, Either::Second(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, ); } } }, Either::Second(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::Explicit { width, height } => Size2D::new( MaybeAuto::from_style(width, bounding_box_size.width) .specified_or_default(bounding_box_size.width), MaybeAuto::from_style(height, bounding_box_size.height) .specified_or_default(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, ); } }, Either::Second(Image::Rect(_)) => { // TODO: Implement `-moz-image-rect` }, Either::Second(Image::Element(_)) => { // TODO: Implement `-moz-element` }, } } } fn compute_background_placement( &self, state: &mut DisplayListBuildState, style: &ComputedValues, absolute_bounds: Rect, intrinsic_size: Option>, index: usize, ) -> BackgroundPlacement { let bg = style.get_background(); 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 css_clip = match bg_clip { BackgroundClip::BorderBox => absolute_bounds, BackgroundClip::PaddingBox => calculate_inner_bounds( absolute_bounds, style.logical_border_width().to_physical(style.writing_mode), ), BackgroundClip::ContentBox => calculate_inner_bounds( absolute_bounds, self.border_padding.to_physical(style.writing_mode), ), }; let mut bounds = match bg_attachment { BackgroundAttachment::Scroll => match bg_origin { BackgroundOrigin::BorderBox => absolute_bounds, BackgroundOrigin::PaddingBox => calculate_inner_bounds( absolute_bounds, style.logical_border_width().to_physical(style.writing_mode), ), BackgroundOrigin::ContentBox => calculate_inner_bounds( absolute_bounds, self.border_padding.to_physical(style.writing_mode), ), }, BackgroundAttachment::Fixed => Rect::new( Point2D::origin(), // Get current viewport state.layout_context.shared_context().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 - intrinsic_size.unwrap_or(Size2D::zero()); 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, css_clip.origin.x, css_clip.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, css_clip.origin.y, css_clip.size.height, ); BackgroundPlacement { bounds, tile_size, tile_spacing, css_clip, } } fn build_display_list_for_webrender_image( &self, state: &mut DisplayListBuildState, style: &ComputedValues, display_list_section: DisplayListSection, absolute_bounds: Rect, webrender_image: WebRenderImageInfo, index: usize, ) { debug!("(building display list) building background image"); let image = Size2D::new( Au::from_px(webrender_image.width as i32), Au::from_px(webrender_image.height as i32), ); let placement = self.compute_background_placement(state, style, absolute_bounds, Some(image), index); // Create the image display item. let base = state.create_base_display_item( &placement.bounds, LocalClip::Rect(placement.css_clip.to_rectf()), self.node, style.get_cursor(Cursor::Default), display_list_section, ); debug!("(building display list) adding background image."); state.add_display_item(DisplayItem::Image(Box::new(ImageDisplayItem { base: base, webrender_image: webrender_image, image_data: None, stretch_size: placement.tile_size, tile_spacing: placement.tile_spacing, image_rendering: style.get_inheritedbox().image_rendering.clone(), }))); } fn get_webrender_image_for_paint_worklet( &self, state: &mut DisplayListBuildState, style: &ComputedValues, paint_worklet: &PaintWorklet, size_in_au: Size2D, ) -> Option { 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, format: draw_result.format, 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 } } fn build_display_list_for_background_gradient( &self, state: &mut DisplayListBuildState, display_list_section: DisplayListSection, absolute_bounds: Rect, gradient: &Gradient, style: &ComputedValues, index: usize, ) { let placement = self.compute_background_placement(state, style, absolute_bounds, None, index); let base = state.create_base_display_item( &placement.bounds, LocalClip::Rect(placement.css_clip.to_rectf()), self.node, style.get_cursor(Cursor::Default), display_list_section, ); let display_item = match gradient.kind { GradientKind::Linear(angle_or_corner) => { let gradient = convert_linear_gradient( placement.tile_size, &gradient.items[..], angle_or_corner, gradient.repeating, ); DisplayItem::Gradient(Box::new(GradientDisplayItem { base: base, gradient: gradient, tile: placement.tile_size, tile_spacing: placement.tile_spacing, })) }, GradientKind::Radial(shape, center, _angle) => { let gradient = convert_radial_gradient( placement.tile_size, &gradient.items[..], shape, center, gradient.repeating, ); DisplayItem::RadialGradient(Box::new(RadialGradientDisplayItem { base: base, gradient: gradient, tile: placement.tile_size, tile_spacing: placement.tile_spacing, })) }, }; state.add_display_item(display_item); } fn build_display_list_for_box_shadow_if_applicable( &self, state: &mut DisplayListBuildState, style: &ComputedValues, display_list_section: DisplayListSection, absolute_bounds: &Rect, clip: &Rect, ) { // 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 bounds = shadow_bounds( &absolute_bounds.translate(&Vector2D::new( Au::from(box_shadow.base.horizontal), Au::from(box_shadow.base.vertical), )), Au::from(box_shadow.base.blur), Au::from(box_shadow.spread), ); let base = state.create_base_display_item( &bounds, LocalClip::from(clip.to_rectf()), self.node, style.get_cursor(Cursor::Default), display_list_section, ); let border_radius = build_border_radius(absolute_bounds, style.get_border()); state.add_display_item(DisplayItem::BoxShadow(Box::new(BoxShadowDisplayItem { base: base, box_bounds: *absolute_bounds, color: box_shadow .base .color .unwrap_or(style.get_color().color) .to_gfx_color(), offset: Vector2D::new( Au::from(box_shadow.base.horizontal), Au::from(box_shadow.base.vertical), ), blur_radius: Au::from(box_shadow.base.blur), spread_radius: Au::from(box_shadow.spread), border_radius, clip_mode: if box_shadow.inset { BoxShadowClipMode::Inset } else { BoxShadowClipMode::Outset }, }))); } } fn build_display_list_for_borders_if_applicable( &self, state: &mut DisplayListBuildState, style: &ComputedValues, inline_info: Option, border_painting_mode: BorderPaintingMode, bounds: &Rect, display_list_section: DisplayListSection, clip: &Rect, ) { 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, } if border.is_zero() { // TODO: check if image-border-outset is zero 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, ); } let colors = SideOffsets2D::new( style.resolve_color(colors.top), style.resolve_color(colors.right), style.resolve_color(colors.bottom), style.resolve_color(colors.left), ); // If this border collapses, then we draw outside the boundaries we were given. let mut bounds = *bounds; 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( &bounds, LocalClip::from(clip.to_rectf()), self.node, style.get_cursor(Cursor::Default), display_list_section, ); match border_style_struct.border_image_source { Either::First(_) => { state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: border.to_physical(style.writing_mode), details: BorderDetails::Normal(NormalBorder { color: SideOffsets2D::new( colors.top.to_gfx_color(), colors.right.to_gfx_color(), colors.bottom.to_gfx_color(), colors.left.to_gfx_color(), ), style: border_style, radius: build_border_radius(&bounds, border_style_struct), }), }))); }, Either::Second(Image::Gradient(ref gradient)) => { match gradient.kind { GradientKind::Linear(angle_or_corner) => { let grad = convert_linear_gradient( bounds.size, &gradient.items[..], angle_or_corner, gradient.repeating, ); state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: border.to_physical(style.writing_mode), details: BorderDetails::Gradient(display_list::GradientBorder { gradient: grad, // TODO(gw): Support border-image-outset outset: SideOffsets2D::zero(), }), }))); }, GradientKind::Radial(shape, center, _angle) => { let grad = convert_radial_gradient( bounds.size, &gradient.items[..], shape, center, gradient.repeating, ); state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: border.to_physical(style.writing_mode), details: BorderDetails::RadialGradient( display_list::RadialGradientBorder { gradient: grad, // TODO(gw): Support border-image-outset outset: SideOffsets2D::zero(), }, ), }))); }, } }, Either::Second(Image::PaintWorklet(ref paint_worklet)) => { // TODO: this size should be increased by border-image-outset let size = self.border_box.size.to_physical(style.writing_mode); let webrender_image = self.get_webrender_image_for_paint_worklet(state, style, paint_worklet, size); if let Some(webrender_image) = webrender_image { let corners = &border_style_struct.border_image_slice.offsets; state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: border.to_physical(style.writing_mode), details: BorderDetails::Image(ImageBorder { image: webrender_image, fill: border_style_struct.border_image_slice.fill, slice: SideOffsets2D::new( corners.0.resolve(webrender_image.height), corners.1.resolve(webrender_image.width), corners.2.resolve(webrender_image.height), corners.3.resolve(webrender_image.width), ), // TODO(gw): Support border-image-outset outset: SideOffsets2D::zero(), repeat_horizontal: convert_repeat_mode( border_style_struct.border_image_repeat.0, ), repeat_vertical: convert_repeat_mode( border_style_struct.border_image_repeat.1, ), }), }))); } }, Either::Second(Image::Rect(..)) => { // TODO: Handle border-image with `-moz-image-rect`. }, Either::Second(Image::Element(..)) => { // TODO: Handle border-image with `-moz-element`. }, Either::Second(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 { let corners = &border_style_struct.border_image_slice.offsets; state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: border.to_physical(style.writing_mode), details: BorderDetails::Image(ImageBorder { image: webrender_image, fill: border_style_struct.border_image_slice.fill, slice: SideOffsets2D::new( corners.0.resolve(webrender_image.height), corners.1.resolve(webrender_image.width), corners.2.resolve(webrender_image.height), corners.3.resolve(webrender_image.width), ), // TODO(gw): Support border-image-outset outset: SideOffsets2D::zero(), repeat_horizontal: convert_repeat_mode( border_style_struct.border_image_repeat.0, ), repeat_vertical: convert_repeat_mode( border_style_struct.border_image_repeat.1, ), }), }))); } } }, } } fn build_display_list_for_outline_if_applicable( &self, state: &mut DisplayListBuildState, style: &ComputedValues, bounds: &Rect, clip: &Rect, ) { 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, OutlineStyle::Other(BorderStyle::None) => return, OutlineStyle::Other(border_style) => border_style, }; // Outlines are not accounted for in the dimensions of the border box, so adjust the // absolute bounds. let mut bounds = *bounds; let offset = width + Au::from(style.get_outline().outline_offset); bounds.origin.x = bounds.origin.x - offset; bounds.origin.y = bounds.origin.y - offset; bounds.size.width = bounds.size.width + offset + offset; bounds.size.height = bounds.size.height + offset + offset; // Append the outline to the display list. let color = style .resolve_color(style.get_outline().outline_color) .to_gfx_color(); let base = state.create_base_display_item( &bounds, LocalClip::from(clip.to_rectf()), self.node, style.get_cursor(Cursor::Default), DisplayListSection::Outlines, ); state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: SideOffsets2D::new_all_same(width), details: BorderDetails::Normal(NormalBorder { color: SideOffsets2D::new_all_same(color), style: SideOffsets2D::new_all_same(outline_style), radius: Default::default(), }), }))); } fn build_debug_borders_around_text_fragments( &self, state: &mut DisplayListBuildState, style: &ComputedValues, stacking_relative_border_box: &Rect, stacking_relative_content_box: &Rect, text_fragment: &ScannedTextFragmentInfo, clip: &Rect, ) { // 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( stacking_relative_border_box, LocalClip::from(clip.to_rectf()), self.node, style.get_cursor(Cursor::Default), DisplayListSection::Content, ); state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: SideOffsets2D::new_all_same(Au::from_px(1)), details: BorderDetails::Normal(NormalBorder { color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)), style: SideOffsets2D::new_all_same(BorderStyle::Solid), radius: Default::default(), }), }))); // 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( &baseline, LocalClip::from(clip.to_rectf()), self.node, style.get_cursor(Cursor::Default), DisplayListSection::Content, ); state.add_display_item(DisplayItem::Line(Box::new(LineDisplayItem { base: base, color: ColorF::rgb(0, 200, 0), style: LineStyle::Dashed, }))); } fn build_debug_borders_around_fragment( &self, state: &mut DisplayListBuildState, stacking_relative_border_box: &Rect, clip: &Rect, ) { // This prints a debug border around the border of this fragment. let base = state.create_base_display_item( stacking_relative_border_box, LocalClip::from(clip.to_rectf()), self.node, self.style.get_cursor(Cursor::Default), DisplayListSection::Content, ); state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: SideOffsets2D::new_all_same(Au::from_px(1)), details: BorderDetails::Normal(NormalBorder { color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)), style: SideOffsets2D::new_all_same(BorderStyle::Solid), radius: Default::default(), }), }))); } fn build_display_items_for_selection_if_necessary( &self, state: &mut DisplayListBuildState, stacking_relative_border_box: &Rect, display_list_section: DisplayListSection, clip: &Rect, ) { 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, LocalClip::from(clip.to_rectf()), self.node, self.style.get_cursor(Cursor::Default), display_list_section, ); state.add_display_item(DisplayItem::SolidColor(Box::new(SolidColorDisplayItem { base: base, color: background_color.to_gfx_color(), }))); } // 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::new( Point2D::new( stacking_relative_border_box.origin.x + advance, stacking_relative_border_box.origin.y, ), Size2D::new( INSERTION_POINT_LOGICAL_WIDTH, stacking_relative_border_box.size.height, ), ); cursor = Cursor::Text; } else { insertion_point_bounds = Rect::new( Point2D::new( stacking_relative_border_box.origin.x, stacking_relative_border_box.origin.y + advance, ), Size2D::new( stacking_relative_border_box.size.width, INSERTION_POINT_LOGICAL_WIDTH, ), ); cursor = Cursor::VerticalText; }; let base = state.create_base_display_item( &insertion_point_bounds, LocalClip::from(clip.to_rectf()), self.node, self.style.get_cursor(cursor), display_list_section, ); state.add_display_item(DisplayItem::SolidColor(Box::new(SolidColorDisplayItem { base: base, color: self.style().get_color().color.to_gfx_color(), }))); } fn build_display_list( &mut self, state: &mut DisplayListBuildState, stacking_relative_flow_origin: &Vector2D, relative_containing_block_size: &LogicalSize, relative_containing_block_mode: WritingMode, border_painting_mode: BorderPaintingMode, display_list_section: DisplayListSection, clip: &Rect, ) { self.restyle_damage.remove(ServoRestyleDamage::REPAINT); if self.style().get_inheritedbox().visibility != Visibility::Visible { return; } // Compute the fragment position relative to the parent stacking context. If the fragment // itself establishes a stacking context, then the origin of its position will be (0, 0) // for the purposes of this computation. let stacking_relative_border_box = self.stacking_relative_border_box( stacking_relative_flow_origin, relative_containing_block_size, relative_containing_block_mode, CoordinateSystem::Own, ); debug!( "Fragment::build_display_list at rel={:?}, abs={:?}, flow origin={:?}: {:?}", self.border_box, stacking_relative_border_box, stacking_relative_flow_origin, 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, clip, ); } if empty_rect { return; } debug!("Fragment::build_display_list: intersected. Adding display item..."); // Create special per-fragment-type display items. 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) } } fn build_fragment_type_specific_display_items( &mut self, state: &mut DisplayListBuildState, stacking_relative_border_box: &Rect, clip: &Rect, ) { // 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); // Adjust the clipping region as necessary to account for `border-radius`. let build_local_clip = |style: &ComputedValues| { let radii = build_border_radius_for_inner_rect(&stacking_relative_border_box, style); if !radii.is_square() { LocalClip::RoundedRect( stacking_relative_border_box.to_rectf(), ComplexClipRegion::new( stacking_relative_content_box.to_rectf(), radii.to_border_radius(), ClipMode::Clip, ), ) } else { LocalClip::Rect(stacking_relative_border_box.to_rectf()) } }; 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_inheritedtext().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_inheritedtext().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 = state.create_base_display_item( &stacking_relative_content_box, build_local_clip(&self.style), self.node, self.style.get_cursor(Cursor::Default), DisplayListSection::Content, ); let item = DisplayItem::Iframe(Box::new(IframeDisplayItem { base: base, iframe: pipeline_id, })); let size = Size2D::new( item.bounds().size.width.to_f32_px(), item.bounds().size.height.to_f32_px(), ); state .iframe_sizes .push((browsing_context_id, TypedSize2D::from_untyped(&size))); state.add_display_item(item); } }, SpecificFragmentInfo::Image(ref mut image_fragment) => { // Place the image into the display list. if let Some(ref image) = image_fragment.image { let base = state.create_base_display_item( &stacking_relative_content_box, build_local_clip(&self.style), self.node, self.style.get_cursor(Cursor::Default), DisplayListSection::Content, ); state.add_display_item(DisplayItem::Image(Box::new(ImageDisplayItem { base: base, webrender_image: WebRenderImageInfo::from_image(image), image_data: Some(Arc::new(image.bytes.clone())), stretch_size: stacking_relative_content_box.size, tile_spacing: Size2D::zero(), image_rendering: self.style.get_inheritedbox().image_rendering.clone(), }))); } }, SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => { let computed_width = canvas_fragment_info.dom_width.to_px(); let computed_height = canvas_fragment_info.dom_height.to_px(); let (image_key, format) = match canvas_fragment_info.source { CanvasFragmentSource::WebGL(image_key) => (image_key, PixelFormat::BGRA8), 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))) .unwrap(); (receiver.recv().unwrap().image_key, PixelFormat::BGRA8) }, None => return, }, }; let base = state.create_base_display_item( &stacking_relative_content_box, build_local_clip(&self.style), self.node, self.style.get_cursor(Cursor::Default), DisplayListSection::Content, ); let display_item = DisplayItem::Image(Box::new(ImageDisplayItem { base: base, webrender_image: WebRenderImageInfo { width: computed_width as u32, height: computed_height as u32, format: format, key: Some(image_key), }, image_data: None, stretch_size: stacking_relative_content_box.size, tile_spacing: Size2D::zero(), image_rendering: ImageRendering::Auto, })); state.add_display_item(display_item); }, SpecificFragmentInfo::UnscannedText(_) => { panic!("Shouldn't see unscanned fragments here.") }, SpecificFragmentInfo::TableColumn(_) => { panic!("Shouldn't see table column fragments here.") }, } } fn create_stacking_context( &self, id: StackingContextId, base_flow: &BaseFlow, scroll_policy: ScrollPolicy, context_type: StackingContextType, 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 = effects.filter.0.clone(); if effects.opacity != 1.0 { filters.push(Filter::Opacity(effects.opacity.into())) } StackingContext::new( id, context_type, &border_box, &overflow, self.effective_z_index(), filters.into(), self.style() .get_effects() .mix_blend_mode .to_mix_blend_mode(), self.transform_matrix(&border_box), self.style().get_used_transform_style().to_transform_style(), self.perspective_matrix(&border_box), scroll_policy, parent_clipping_and_scrolling, ) } fn build_display_list_for_text_fragment( &self, state: &mut DisplayListBuildState, text_fragment: &ScannedTextFragmentInfo, stacking_relative_content_box: &Rect, text_shadows: &[SimpleShadow], clip: &Rect, ) { // 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_color().color } else { self.style().get_color().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( &stacking_relative_content_box, LocalClip::from(clip.to_rectf()), self.node, self.style().get_cursor(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(), blur_radius: Au::from(shadow.blur), offset: Vector2D::new(Au::from(shadow.horizontal), Au::from(shadow.vertical)), color: shadow .color .unwrap_or(self.style().get_color().color) .to_gfx_color(), }, ))); } // Create display items for text decorations. let text_decorations = self.style() .get_inheritedtext() ._servo_text_decorations_in_effect; let 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 = stacking_relative_content_box; stacking_relative_box.start.b = 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 = 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 state.add_display_item(DisplayItem::Text(Box::new(TextDisplayItem { base: base.clone(), text_run: text_fragment.run.clone(), range: text_fragment.range, text_color: text_color.to_gfx_color(), orientation: orientation, baseline_origin: baseline_origin, }))); // TODO(#17715): emit text-emphasis marks here. // (just push another TextDisplayItem?) // Line-Through if text_decorations.line_through { let mut stacking_relative_box = 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: base.clone() }, ))); } } fn build_display_list_for_text_decoration( &self, state: &mut DisplayListBuildState, color: &RGBA, stacking_relative_box: &LogicalRect, clip: &Rect, ) { // 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( &stacking_relative_box, LocalClip::from(clip.to_rectf()), self.node, self.style.get_cursor(Cursor::Default), DisplayListSection::Content, ); state.add_display_item(DisplayItem::Line(Box::new(LineDisplayItem { base: base, color: color.to_gfx_color(), style: LineStyle::Solid, }))); } fn unique_id(&self, id_type: IdType) -> u64 { let fragment_type = self.fragment_type(); let id = match id_type { IdType::StackingContext | IdType::OverflowClip => self.node.id() as usize, IdType::CSSClip => self as *const _ as usize, }; combine_id_with_fragment_type(id, fragment_type) as u64 } fn fragment_type(&self) -> FragmentType { match self.pseudo { PseudoElementType::Normal => FragmentType::FragmentBody, PseudoElementType::Before(_) => FragmentType::BeforePseudoContent, PseudoElementType::After(_) => FragmentType::AfterPseudoContent, PseudoElementType::DetailsSummary(_) => FragmentType::FragmentBody, PseudoElementType::DetailsContent(_) => FragmentType::FragmentBody, } } } bitflags! { pub struct StackingContextCollectionFlags: u8 { /// This flow never establishes a containing block. const 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; } } pub trait BlockFlowDisplayListBuilding { fn collect_stacking_contexts_for_block( &mut self, state: &mut StackingContextCollectionState, flags: StackingContextCollectionFlags, ); fn transform_clip_to_coordinate_space( &mut self, state: &mut StackingContextCollectionState, preserved_state: &mut SavedStackingContextCollectionState, ); fn setup_clipping_for_block( &mut self, state: &mut StackingContextCollectionState, preserved_state: &mut SavedStackingContextCollectionState, stacking_context_type: BlockStackingContextType, flags: StackingContextCollectionFlags, ) -> ClippingAndScrolling; fn setup_clip_scroll_node_for_position( &mut self, state: &mut StackingContextCollectionState, border_box: &Rect, ); fn setup_clip_scroll_node_for_overflow( &mut self, state: &mut StackingContextCollectionState, border_box: &Rect, ); fn setup_clip_scroll_node_for_css_clip( &mut self, state: &mut StackingContextCollectionState, preserved_state: &mut SavedStackingContextCollectionState, stacking_relative_border_box: &Rect, ); fn create_pseudo_stacking_context_for_block( &mut self, parent_stacking_context_id: StackingContextId, parent_clip_and_scroll_info: ClippingAndScrolling, state: &mut StackingContextCollectionState, ); fn create_real_stacking_context_for_block( &mut self, parent_stacking_context_id: StackingContextId, parent_clipping_and_scrolling: ClippingAndScrolling, state: &mut StackingContextCollectionState, ); fn build_display_list_for_block( &mut self, state: &mut DisplayListBuildState, border_painting_mode: BorderPaintingMode, ); fn block_stacking_context_type( &self, flags: StackingContextCollectionFlags, ) -> BlockStackingContextType; } /// 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, clipping_and_scrolling: ClippingAndScrolling, containing_block_clipping_and_scrolling: ClippingAndScrolling, clips_pushed: usize, containing_block_clips_pushed: usize, stacking_relative_content_box: Rect, } 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, 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(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_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, clip: &Rect, positioning: StylePosition, ) { let mut clip = *clip; 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 BlockFlowDisplayListBuilding for 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_else(Transform3D::identity); let transform = transform.pre_mul(&perspective).inverse(); let origin = &border_box.origin; let transform_clip = |clip: &Rect| { if *clip == 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. max_rect() }, Some(transform) => { let clip = Rect::new( Point2D::new( (clip.origin.x - origin.x).to_f32_px(), (clip.origin.y - origin.y).to_f32_px(), ), Size2D::new(clip.size.width.to_f32_px(), clip.size.height.to_f32_px()), ); let clip = transform.transform_rect(&clip); Rect::new( Point2D::new( Au::from_f32_px(clip.origin.x), Au::from_f32_px(clip.origin.y), ), Size2D::new( 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; } } fn collect_stacking_contexts_for_block( &mut self, state: &mut StackingContextCollectionState, flags: StackingContextCollectionFlags, ) { let mut preserved_state = SavedStackingContextCollectionState::new(state); let block_stacking_context_type = self.block_stacking_context_type(flags); self.base.stacking_context_id = match block_stacking_context_type { BlockStackingContextType::NonstackingContext => state.current_stacking_context_id, BlockStackingContextType::PseudoStackingContext | BlockStackingContextType::StackingContext => state.generate_stacking_context_id(), }; state.current_stacking_context_id = self.base.stacking_context_id; if block_stacking_context_type == BlockStackingContextType::StackingContext { state.current_real_stacking_context_id = self.base.stacking_context_id; } // 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, block_stacking_context_type, flags, ); if establishes_containing_block_for_absolute(flags, self.positioning()) { state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling; } match block_stacking_context_type { BlockStackingContextType::NonstackingContext => { self.base.collect_stacking_contexts_for_children(state); }, BlockStackingContextType::PseudoStackingContext => { self.create_pseudo_stacking_context_for_block( preserved_state.stacking_context_id, containing_clipping_and_scrolling, state, ); }, BlockStackingContextType::StackingContext => { self.create_real_stacking_context_for_block( 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: BlockStackingContextType, 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 => { preserved_state.push_clip(state, &max_rect(), StylePosition::Fixed); state.current_clipping_and_scrolling }, _ => state.current_clipping_and_scrolling, }; self.base.clipping_and_scrolling = Some(containing_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 == BlockStackingContextType::StackingContext { 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(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::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, ) { 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 { id: None, parent_index: self.clipping_and_scrolling().scrolling, clip: ClippingRegion::from_rect(border_box), content_rect: Rect::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, ) { 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; } // If we already have a scroll root for this flow, just return. This can happen // when fragments map to more than one flow, such as in the case of table // wrappers. We just accept the first scroll root in that case. let new_clip_scroll_node_id = ClipId::new( self.fragment.unique_id(IdType::OverflowClip), state.pipeline_id.to_webrender(), ); 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 clip_rect = build_inner_border_box_for_border_rect(&border_box, &self.fragment.style); let mut clip = ClippingRegion::from_rect(&clip_rect); let radii = build_border_radius_for_inner_rect(&border_box, &self.fragment.style); if !radii.is_square() { clip.intersect_with_rounded_rect(&clip_rect, &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 new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode { id: Some(new_clip_scroll_node_id), parent_index: self.clipping_and_scrolling().scrolling, clip: clip, content_rect: Rect::new(content_box.origin, content_size), node_type: ClipScrollNodeType::ScrollFrame(sensitivity), }); 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, ) { // 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, } let clip_origin = Point2D::new( stacking_relative_border_box.origin.x + style_clip_rect.left.map(Au::from).unwrap_or(Au(0)), stacking_relative_border_box.origin.y + style_clip_rect.top.map(Au::from).unwrap_or(Au(0)), ); let right = style_clip_rect .right .map(Au::from) .unwrap_or(stacking_relative_border_box.size.width); let bottom = style_clip_rect .bottom .map(Au::from) .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 { id: None, parent_index: self.clipping_and_scrolling().scrolling, clip: ClippingRegion::from_rect(&clip_rect), content_rect: Rect::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, parent_stacking_context_id: StackingContextId, parent_clipping_and_scrolling: ClippingAndScrolling, state: &mut StackingContextCollectionState, ) { let creation_mode = if self.base .flags .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) || self.fragment.style.get_box().position != StylePosition::Static { StackingContextType::PseudoPositioned } else { assert!(self.base.flags.is_float()); StackingContextType::PseudoFloat }; let new_context = self.fragment.create_stacking_context( self.base.stacking_context_id, &self.base, ScrollPolicy::Scrollable, creation_mode, 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, state: &mut StackingContextCollectionState, ) { let scroll_policy = if self.is_fixed() { ScrollPolicy::Fixed } else { ScrollPolicy::Scrollable }; let stacking_context = self.fragment.create_stacking_context( self.base.stacking_context_id, &self.base, scroll_policy, StackingContextType::Real, parent_clipping_and_scrolling, ); state.add_stacking_context(parent_stacking_context_id, stacking_context); self.base.collect_stacking_contexts_for_children(state); } fn build_display_list_for_block( &mut self, state: &mut DisplayListBuildState, border_painting_mode: BorderPaintingMode, ) { let background_border_section = if self.base.flags.is_float() { DisplayListSection::BackgroundAndBorders } else if self.base .flags .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { if self.fragment.establishes_stacking_context() { DisplayListSection::BackgroundAndBorders } else { DisplayListSection::BlockBackgroundsAndBorders } } else { DisplayListSection::BlockBackgroundsAndBorders }; state.processing_scrolling_overflow_element = self.has_scrolling_overflow(); // Add the box that starts the block context. self.fragment.build_display_list( state, &self.base.stacking_relative_position, &self.base .early_absolute_position_info .relative_containing_block_size, self.base .early_absolute_position_info .relative_containing_block_mode, border_painting_mode, background_border_section, &self.base.clip, ); self.base .build_display_items_for_debugging_tint(state, self.fragment.node); state.processing_scrolling_overflow_element = false; } #[inline] fn block_stacking_context_type( &self, flags: StackingContextCollectionFlags, ) -> BlockStackingContextType { if flags.contains(StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT) { return BlockStackingContextType::NonstackingContext; } if self.fragment.establishes_stacking_context() { return BlockStackingContextType::StackingContext; } if self.base .flags .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) { return BlockStackingContextType::PseudoStackingContext; } if self.fragment.style.get_box().position != StylePosition::Static { return BlockStackingContextType::PseudoStackingContext; } if self.base.flags.is_float() { return BlockStackingContextType::PseudoStackingContext; } BlockStackingContextType::NonstackingContext } } pub trait InlineFlowDisplayListBuilding { fn collect_stacking_contexts_for_inline(&mut self, state: &mut StackingContextCollectionState); fn build_display_list_for_inline_fragment_at_index( &mut self, state: &mut DisplayListBuildState, index: usize, ); fn build_display_list_for_inline(&mut self, state: &mut DisplayListBuildState); } impl InlineFlowDisplayListBuilding for InlineFlow { fn collect_stacking_contexts_for_inline(&mut self, state: &mut StackingContextCollectionState) { self.base.stacking_context_id = state.current_stacking_context_id; self.base.clipping_and_scrolling = Some(state.current_clipping_and_scrolling); self.base.clip = state.clip_stack.last().cloned().unwrap_or_else(max_rect); for fragment in self.fragments.fragments.iter_mut() { let previous_cb_clipping_and_scrolling = state.containing_block_clipping_and_scrolling; if establishes_containing_block_for_absolute( StackingContextCollectionFlags::empty(), fragment.style.get_box().position, ) { state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling; } if !fragment.collect_stacking_contexts_for_blocklike_fragment(state) { if fragment.establishes_stacking_context() { fragment.stacking_context_id = state.generate_stacking_context_id(); let current_stacking_context_id = state.current_stacking_context_id; let stacking_context = fragment.create_stacking_context( fragment.stacking_context_id, &self.base, ScrollPolicy::Scrollable, StackingContextType::Real, state.current_clipping_and_scrolling, ); state.add_stacking_context(current_stacking_context_id, stacking_context); } else { fragment.stacking_context_id = state.current_stacking_context_id; } } state.containing_block_clipping_and_scrolling = previous_cb_clipping_and_scrolling; } } fn build_display_list_for_inline_fragment_at_index( &mut self, state: &mut DisplayListBuildState, index: usize, ) { let fragment = self.fragments.fragments.get_mut(index).unwrap(); fragment.build_display_list( state, &self.base.stacking_relative_position, &self.base .early_absolute_position_info .relative_containing_block_size, self.base .early_absolute_position_info .relative_containing_block_mode, BorderPaintingMode::Separate, DisplayListSection::Content, &self.base.clip, ); } fn build_display_list_for_inline(&mut self, state: &mut DisplayListBuildState) { debug!( "Flow: building display list for {} inline fragments", self.fragments.len() ); // We iterate using an index here, because we want to avoid doing a doing // a double-borrow of self (one mutable for the method call and one immutable // for the self.fragments.fragment iterator itself). for index in 0..self.fragments.fragments.len() { let (establishes_stacking_context, stacking_context_id) = { let fragment = self.fragments.fragments.get(index).unwrap(); ( self.base.stacking_context_id != fragment.stacking_context_id, fragment.stacking_context_id, ) }; let parent_stacking_context_id = state.current_stacking_context_id; if establishes_stacking_context { state.current_stacking_context_id = stacking_context_id; } self.build_display_list_for_inline_fragment_at_index(state, index); if establishes_stacking_context { state.current_stacking_context_id = parent_stacking_context_id } } if !self.fragments.fragments.is_empty() { self.base .build_display_items_for_debugging_tint(state, self.fragments.fragments[0].node); } } } pub trait ListItemFlowDisplayListBuilding { fn build_display_list_for_list_item(&mut self, state: &mut DisplayListBuildState); } impl ListItemFlowDisplayListBuilding for ListItemFlow { fn build_display_list_for_list_item(&mut self, state: &mut DisplayListBuildState) { // Draw the marker, if applicable. for marker in &mut self.marker_fragments { marker.build_display_list( state, &self.block_flow.base.stacking_relative_position, &self.block_flow .base .early_absolute_position_info .relative_containing_block_size, self.block_flow .base .early_absolute_position_info .relative_containing_block_mode, BorderPaintingMode::Separate, DisplayListSection::Content, &self.block_flow.base.clip, ); } // Draw the rest of the block. self.block_flow .build_display_list_for_block(state, BorderPaintingMode::Separate) } } pub trait FlexFlowDisplayListBuilding { fn build_display_list_for_flex(&mut self, state: &mut DisplayListBuildState); } impl FlexFlowDisplayListBuilding for FlexFlow { fn build_display_list_for_flex(&mut self, state: &mut DisplayListBuildState) { // Draw the rest of the block. self.as_mut_block() .build_display_list_for_block(state, BorderPaintingMode::Separate) } } trait BaseFlowDisplayListBuilding { fn build_display_items_for_debugging_tint( &self, state: &mut DisplayListBuildState, node: OpaqueNode, ); } impl BaseFlowDisplayListBuilding for BaseFlow { 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( &stacking_context_relative_bounds.inflate(Au::from_px(2), Au::from_px(2)), LocalClip::from(self.clip.to_rectf()), node, None, DisplayListSection::Content, ); state.add_display_item(DisplayItem::Border(Box::new(BorderDisplayItem { base: base, border_widths: SideOffsets2D::new_all_same(Au::from_px(2)), details: BorderDetails::Normal(NormalBorder { color: SideOffsets2D::new_all_same(color), style: SideOffsets2D::new_all_same(BorderStyle::Solid), radius: BorderRadii::all_same(Au(0)), }), }))); } } trait ComputedValuesCursorUtility { fn get_cursor(&self, default_cursor: Cursor) -> Option; } impl ComputedValuesCursorUtility for ComputedValues { /// 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(&self, default_cursor: Cursor) -> Option { match ( self.get_pointing().pointer_events, self.get_pointing().cursor, ) { (PointerEvents::None, _) => None, (PointerEvents::Auto, cursor::Keyword::Auto) => Some(default_cursor), (PointerEvents::Auto, cursor::Keyword::Cursor(cursor)) => Some(cursor), } } } // A helper data structure for gradients. #[derive(Clone, Copy)] struct StopRun { start_offset: f32, end_offset: f32, start_index: usize, stop_count: usize, } fn position_to_offset(position: LengthOrPercentage, total_length: Au) -> f32 { if total_length == Au(0) { return 0.0; } match position { LengthOrPercentage::Length(l) => l.to_i32_au() as f32 / total_length.0 as f32, LengthOrPercentage::Percentage(percentage) => percentage.0 as f32, LengthOrPercentage::Calc(calc) => { calc.to_used_value(Some(total_length)).unwrap().0 as f32 / total_length.0 as f32 }, } } /// Adjusts `content_rect` as necessary for the given spread, and blur so that the resulting /// bounding rect contains all of a shadow's ink. fn shadow_bounds(content_rect: &Rect, blur: Au, spread: Au) -> Rect { let inflation = spread + blur * BLUR_INFLATION_FACTOR; content_rect.inflate(inflation, inflation) } /// 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, 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); } } /// Allows a CSS color to be converted into a graphics color. pub trait ToGfxColor { /// Converts a CSS color to a graphics color. fn to_gfx_color(&self) -> ColorF; } impl ToGfxColor for RGBA { fn to_gfx_color(&self) -> ColorF { ColorF::new( self.red_f32(), self.green_f32(), self.blue_f32(), self.alpha_f32(), ) } } /// 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, } #[derive(Clone, Copy, Debug)] pub struct BackgroundPlacement { /// Rendering bounds. The background will start in the uppper-left corner /// and fill the whole area. bounds: Rect, /// Background tile size. Some backgrounds are repeated. These are the /// dimensions of a single image of the background. tile_size: Size2D, /// Spacing between tiles. Some backgrounds are not repeated seamless /// but have seams between them like tiles in real life. tile_spacing: Size2D, /// A clip area. While the background is rendered according to all the /// measures above it is only shown within these bounds. css_clip: Rect, }