/* 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/. */ //! Servo heavily uses display lists, which are retained-mode lists of painting commands to //! perform. Using a list instead of painting elements in immediate mode allows transforms, hit //! testing, and invalidation to be performed using the same primitives as painting. It also allows //! Servo to aggressively cull invisible and out-of-bounds painting elements, to reduce overdraw. //! Finally, display lists allow tiles to be farmed out onto multiple CPUs and painted in parallel //! (although this benefit does not apply to GPU-based painting). //! //! Display items describe relatively high-level drawing operations (for example, entire borders //! and shadows instead of lines and blur operations), to reduce the amount of allocation required. //! They are therefore not exactly analogous to constructs like Skia pictures, which consist of //! low-level drawing primitives. use app_units::Au; use azure::azure::AzFloat; use azure::azure_hl::{Color, DrawTarget}; use display_list::optimizer::DisplayListOptimizer; use euclid::approxeq::ApproxEq; use euclid::num::Zero; use euclid::{Matrix2D, Matrix4, Point2D, Rect, SideOffsets2D, Size2D}; use gfx_traits::{color, LayerId, LayerKind, ScrollPolicy}; use msg::constellation_msg::PipelineId; use net_traits::image::base::Image; use paint_context::PaintContext; use paint_thread::{PaintLayerContents, PaintLayer}; use self::DisplayItem::*; use self::DisplayItemIterator::*; use smallvec::SmallVec; use std::cmp::Ordering; use std::collections::linked_list::{self, LinkedList}; use std::fmt; use std::mem; use std::slice::Iter; use std::sync::Arc; use style::computed_values::{border_style, cursor, filter, image_rendering, mix_blend_mode}; use style::computed_values::{pointer_events}; use style::properties::ComputedValues; use text::TextRun; use text::glyph::CharIndex; use util::cursor::Cursor; use util::geometry::MAX_RECT; use util::linked_list::prepend_from; use util::mem::HeapSizeOf; use util::opts; use util::print_tree::PrintTree; use util::range::Range; pub use style::dom::OpaqueNode; // It seems cleaner to have layout code not mention Azure directly, so let's just reexport this for // layout to use. pub use azure::azure_hl::GradientStop; pub mod optimizer; /// The factor that we multiply the blur radius by in order to inflate the boundaries of display /// items that involve a blur. This ensures that the display item boundaries include all the ink. pub static BLUR_INFLATION_FACTOR: i32 = 3; /// LayerInfo is used to store PaintLayer metadata during DisplayList construction. /// It is also used for tracking LayerIds when creating layers to preserve ordering when /// layered DisplayItems should render underneath unlayered DisplayItems. #[derive(Clone, Copy, Debug, HeapSizeOf, Deserialize, Serialize)] pub struct LayerInfo { /// The base LayerId of this layer. pub layer_id: LayerId, /// The scroll policy of this layer. pub scroll_policy: ScrollPolicy, /// The subpage that this layer represents, if there is one. pub subpage_pipeline_id: Option, /// The id for the next layer in the sequence. This is used for synthesizing /// layers for content that needs to be displayed on top of this layer. pub next_layer_id: LayerId, } impl LayerInfo { pub fn new(id: LayerId, scroll_policy: ScrollPolicy, subpage_pipeline_id: Option) -> LayerInfo { LayerInfo { layer_id: id, scroll_policy: scroll_policy, subpage_pipeline_id: subpage_pipeline_id, next_layer_id: id.companion_layer_id(), } } fn next(&mut self) -> LayerInfo { let new_layer_info = LayerInfo::new(self.next_layer_id, self.scroll_policy, None); self.next_layer_id = self.next_layer_id.companion_layer_id(); new_layer_info } fn next_with_scroll_policy(&mut self, scroll_policy: ScrollPolicy) -> LayerInfo { let mut new_layer_info = self.next(); new_layer_info.scroll_policy = scroll_policy; new_layer_info } } /// Display items that make up a stacking context. "Steps" here refer to the steps in CSS 2.1 /// Appendix E. /// /// TODO(pcwalton): We could reduce the size of this structure with a more "skip list"-like /// structure, omitting several pointers and lengths. #[derive(HeapSizeOf, Deserialize, Serialize)] pub struct DisplayList { /// The border and backgrounds for the root of this stacking context: steps 1 and 2. pub background_and_borders: LinkedList, /// Borders and backgrounds for block-level descendants: step 4. pub block_backgrounds_and_borders: LinkedList, /// Floats: step 5. These are treated as pseudo-stacking contexts. pub floats: LinkedList, /// All non-positioned content. pub content: LinkedList, /// All positioned content that does not get a stacking context. pub positioned_content: LinkedList, /// Outlines: step 10. pub outlines: LinkedList, /// Child PaintLayers that will be rendered on top of everything else. pub layered_children: LinkedList>, /// Information about child layers. pub layer_info: LinkedList, } impl DisplayList { /// Creates a new, empty display list. #[inline] pub fn new() -> DisplayList { DisplayList { background_and_borders: LinkedList::new(), block_backgrounds_and_borders: LinkedList::new(), floats: LinkedList::new(), content: LinkedList::new(), positioned_content: LinkedList::new(), outlines: LinkedList::new(), layered_children: LinkedList::new(), layer_info: LinkedList::new(), } } /// Adds the given display item at the specified section of this display list. pub fn add_to_section(&mut self, display_item: DisplayItem, section: DisplayListSection) { self.get_section_mut(section).push_back(display_item); } /// Creates a new display list which contains a single stacking context. #[inline] pub fn new_with_stacking_context(stacking_context: Arc) -> Box { let mut display_list = box DisplayList::new(); display_list.positioned_content.push_back( DisplayItem::StackingContextClass(stacking_context)); display_list } /// Appends all display items from `other` into `self`, preserving stacking order and emptying /// `other` in the process. #[inline] pub fn append_from(&mut self, other: &mut Option>) { if let Some(mut other) = other.take() { self.background_and_borders.append(&mut other.background_and_borders); self.block_backgrounds_and_borders.append(&mut other.block_backgrounds_and_borders); self.floats.append(&mut other.floats); self.content.append(&mut other.content); self.positioned_content.append(&mut other.positioned_content); self.outlines.append(&mut other.outlines); self.layered_children.append(&mut other.layered_children); self.layer_info.append(&mut other.layer_info); } } /// Merges all display items from all non-float stacking levels to the `float` stacking level. /// From E.2.5 at http://www.w3.org/TR/CSS21/zindex.html. We do not include positioned content /// and stacking contexts in the pseudo-stacking-context. #[inline] pub fn form_float_pseudo_stacking_context(&mut self) { prepend_from(&mut self.floats, &mut self.outlines); prepend_from(&mut self.floats, &mut self.content); prepend_from(&mut self.floats, &mut self.block_backgrounds_and_borders); prepend_from(&mut self.floats, &mut self.background_and_borders); } /// Merges all display items from all non-positioned-content stacking levels to the /// positioned-content stacking level. #[inline] pub fn form_pseudo_stacking_context_for_positioned_content(&mut self) { prepend_from(&mut self.positioned_content, &mut self.outlines); prepend_from(&mut self.positioned_content, &mut self.content); prepend_from(&mut self.positioned_content, &mut self.floats); prepend_from(&mut self.positioned_content, &mut self.block_backgrounds_and_borders); prepend_from(&mut self.positioned_content, &mut self.background_and_borders); } /// Returns a list of all items in this display list concatenated together. This is extremely /// inefficient and should only be used for debugging. pub fn flatten(&self) -> Vec { let mut result = Vec::new(); fn flatten_item(result: &mut Vec, item: &DisplayItem) { match item { &DisplayItem::StackingContextClass(ref stacking_context) => result.extend(stacking_context.display_list.flatten().into_iter()), _ => result.push((*item).clone()), } } for display_item in &self.background_and_borders { flatten_item(&mut result, display_item); } for display_item in &self.block_backgrounds_and_borders { flatten_item(&mut result, display_item); } for display_item in &self.floats { flatten_item(&mut result, display_item); } for display_item in &self.content { flatten_item(&mut result, display_item); } for display_item in &self.positioned_content { flatten_item(&mut result, display_item); } for display_item in &self.outlines { flatten_item(&mut result, display_item); } result } pub fn print(&self, title: String) { let mut print_tree = PrintTree::new(title); self.print_with_tree(&mut print_tree); } pub fn print_with_tree(&self, print_tree: &mut PrintTree) { fn print_display_list_section(print_tree: &mut PrintTree, items: &LinkedList, title: &str) { if items.is_empty() { return; } print_tree.new_level(title.to_owned()); for item in items { match item { &DisplayItem::StackingContextClass(ref stacking_context) => stacking_context.print_with_tree(print_tree), _ => print_tree.add_item(format!("{:?}", item)), } } print_tree.end_level(); } print_display_list_section(print_tree, &self.background_and_borders, "Backgrounds and Borders"); print_display_list_section(print_tree, &self.block_backgrounds_and_borders, "Block Backgrounds and Borders"); print_display_list_section(print_tree, &self.floats, "Floats"); print_display_list_section(print_tree, &self.content, "Content"); print_display_list_section(print_tree, &self.positioned_content, "Positioned Content"); print_display_list_section(print_tree, &self.outlines, "Outlines"); if !self.layered_children.is_empty() { print_tree.new_level("Layers".to_owned()); for paint_layer in &self.layered_children { match paint_layer.contents { PaintLayerContents::StackingContext(ref stacking_context) => stacking_context.print_with_tree(print_tree), PaintLayerContents::DisplayList(ref display_list) => { print_tree.new_level(format!("DisplayList Layer with bounds {:?}:", display_list.calculate_bounding_rect())); display_list.print_with_tree(print_tree); print_tree.end_level(); } } } print_tree.end_level(); } } /// Draws the DisplayList in stacking context order according to the steps in CSS 2.1 § E.2. pub fn draw_into_context(&self, draw_target: &DrawTarget, paint_context: &mut PaintContext, transform: &Matrix4, clip_rect: Option<&Rect>) { let mut paint_subcontext = PaintContext { draw_target: draw_target.clone(), font_context: &mut *paint_context.font_context, page_rect: paint_context.page_rect, screen_rect: paint_context.screen_rect, clip_rect: clip_rect.map(|clip_rect| *clip_rect), transient_clip: None, layer_kind: paint_context.layer_kind, }; if opts::get().dump_display_list_optimized { self.print(format!("Optimized display list. Tile bounds: {:?}", paint_context.page_rect)); } // Set up our clip rect and transform. let old_transform = paint_subcontext.draw_target.get_transform(); let xform_2d = Matrix2D::new(transform.m11, transform.m12, transform.m21, transform.m22, transform.m41, transform.m42); paint_subcontext.draw_target.set_transform(&xform_2d); paint_subcontext.push_clip_if_applicable(); // Steps 1 and 2: Borders and background for the root. for display_item in &self.background_and_borders { display_item.draw_into_context(transform, &mut paint_subcontext) } // Step 3: Positioned descendants with negative z-indices. for positioned_kid in &self.positioned_content { if let &DisplayItem::StackingContextClass(ref stacking_context) = positioned_kid { if stacking_context.z_index < 0 { positioned_kid.draw_into_context(transform, &mut paint_subcontext); } } } // Step 4: Block backgrounds and borders. for display_item in &self.block_backgrounds_and_borders { display_item.draw_into_context(transform, &mut paint_subcontext) } // Step 5: Floats. for display_item in &self.floats { display_item.draw_into_context(transform, &mut paint_subcontext) } // TODO(pcwalton): Step 6: Inlines that generate stacking contexts. // Step 7: Content. for display_item in &self.content { display_item.draw_into_context(transform, &mut paint_subcontext) } // Step 8 & 9: Positioned descendants with nonnegative, numeric z-indices. for positioned_kid in &self.positioned_content { if let &DisplayItem::StackingContextClass(ref stacking_context) = positioned_kid { if stacking_context.z_index < 0 { continue; } } positioned_kid.draw_into_context(transform, &mut paint_subcontext); } // Step 10: Outlines. for display_item in &self.outlines { display_item.draw_into_context(transform, &mut paint_subcontext) } // Undo our clipping and transform. paint_subcontext.remove_transient_clip_if_applicable(); paint_subcontext.pop_clip_if_applicable(); paint_subcontext.draw_target.set_transform(&old_transform) } pub fn hit_test(&self, point: Point2D, result: &mut Vec, topmost_only: bool) { fn hit_test_item(point: Point2D, result: &mut Vec, item: &DisplayItem) { let base_item = match item.base() { Some(base) => base, None => return, }; // TODO(pcwalton): Use a precise algorithm here. This will allow us to properly hit // test elements with `border-radius`, for example. if !base_item.clip.might_intersect_point(&point) { // Clipped out. return; } if !item.bounds().contains(&point) { // Can't possibly hit. return; } if base_item.metadata.pointing.is_none() { // `pointer-events` is `none`. Ignore this item. return; } if let DisplayItem::BorderClass(ref border) = *item { // If the point is inside the border, it didn't hit the border! let interior_rect = Rect::new( Point2D::new(border.base.bounds.origin.x + border.border_widths.left, border.base.bounds.origin.y + border.border_widths.top), Size2D::new(border.base.bounds.size.width - (border.border_widths.left + border.border_widths.right), border.base.bounds.size.height - (border.border_widths.top + border.border_widths.bottom))); if interior_rect.contains(&point) { return; } } // We found a hit! result.push(base_item.metadata); } fn hit_test_in_list<'a, I>(point: Point2D, result: &mut Vec, topmost_only: bool, iterator: I) where I: Iterator { for item in iterator { hit_test_item(point, result, item); if topmost_only && !result.is_empty() { return; } } } // Layers that are positioned on top of this layer should get a shot at the hit test first. for layer in self.layered_children.iter().rev() { match layer.contents { PaintLayerContents::StackingContext(ref stacking_context) => stacking_context.hit_test(point, result, topmost_only), PaintLayerContents::DisplayList(ref display_list) => display_list.hit_test(point, result, topmost_only), } if topmost_only && !result.is_empty() { return } } // Iterate through display items in reverse stacking order. Steps here refer to the // painting steps in CSS 2.1 Appendix E. // // Step 10: Outlines. hit_test_in_list(point, result, topmost_only, self.outlines.iter().rev()); if topmost_only && !result.is_empty() { return } // Steps 9 and 8: Positioned descendants with nonnegative z-indices. for kid in self.positioned_content.iter().rev() { if let &DisplayItem::StackingContextClass(ref stacking_context) = kid { if stacking_context.z_index < 0 { continue } stacking_context.hit_test(point, result, topmost_only); } else { hit_test_item(point, result, kid); } if topmost_only && !result.is_empty() { return } } // Steps 8, 7, 5, and 4: Positioned content, content, floats, and block backgrounds and // borders. // // TODO(pcwalton): Step 6: Inlines that generate stacking contexts. for display_list in &[ &self.content, &self.floats, &self.block_backgrounds_and_borders, ] { hit_test_in_list(point, result, topmost_only, display_list.iter().rev()); if topmost_only && !result.is_empty() { return } } for kid in self.positioned_content.iter().rev() { if let &DisplayItem::StackingContextClass(ref stacking_context) = kid { if stacking_context.z_index >= 0 { continue } stacking_context.hit_test(point, result, topmost_only); if topmost_only && !result.is_empty() { return } } } // Steps 2 and 1: Borders and background for the root. hit_test_in_list(point, result, topmost_only, self.background_and_borders.iter().rev()) } /// Returns the PaintLayer in the given DisplayList with a specific layer ID. pub fn find_layer_with_layer_id(&self, layer_id: LayerId) -> Option> { for kid in &self.layered_children { if let Some(paint_layer) = PaintLayer::find_layer_with_layer_id(&kid, layer_id) { return Some(paint_layer); } } for item in &self.positioned_content { if let &DisplayItem::StackingContextClass(ref stacking_context) = item { if let Some(paint_layer) = stacking_context.display_list.find_layer_with_layer_id(layer_id) { return Some(paint_layer); } } } None } /// Calculate the union of all the bounds of all of the items in this display list. /// This is an expensive operation, so it shouldn't be done unless absolutely necessary /// and, if possible, the result should be cached. pub fn calculate_bounding_rect(&self) -> Rect { fn union_all_items(list: &LinkedList, mut bounds: Rect) -> Rect { for item in list { bounds = bounds.union(&item.bounds()); } bounds }; let mut bounds = Rect::zero(); bounds = union_all_items(&self.background_and_borders, bounds); bounds = union_all_items(&self.block_backgrounds_and_borders, bounds); bounds = union_all_items(&self.floats, bounds); bounds = union_all_items(&self.content, bounds); bounds = union_all_items(&self.positioned_content, bounds); bounds = union_all_items(&self.outlines, bounds); bounds } #[inline] fn get_section_mut(&mut self, section: DisplayListSection) -> &mut LinkedList { match section { DisplayListSection::BackgroundAndBorders => &mut self.background_and_borders, DisplayListSection::BlockBackgroundsAndBorders => &mut self.block_backgrounds_and_borders, DisplayListSection::Floats => &mut self.floats, DisplayListSection::Content => &mut self.content, DisplayListSection::PositionedContent => &mut self.positioned_content, DisplayListSection::Outlines => &mut self.outlines, } } } #[derive(Clone, Copy, Debug)] pub enum DisplayListSection { BackgroundAndBorders, BlockBackgroundsAndBorders, Floats, Content, PositionedContent, Outlines, } #[derive(HeapSizeOf, Deserialize, Serialize)] /// Represents one CSS stacking context, which may or may not have a hardware layer. pub struct StackingContext { /// The display items that make up this stacking context. pub display_list: Box, /// The position and size of this stacking context. pub bounds: Rect, /// The overflow rect for this stacking context in its coordinate system. pub overflow: Rect, /// The `z-index` for this stacking context. pub z_index: i32, /// CSS filters to be applied to this stacking context (including opacity). pub filters: filter::T, /// The blend mode with which this stacking context blends with its backdrop. pub blend_mode: mix_blend_mode::T, /// A transform to be applied to this stacking context. pub transform: Matrix4, /// The perspective matrix to be applied to children. pub perspective: Matrix4, /// Whether this stacking context creates a new 3d rendering context. pub establishes_3d_context: bool, /// Whether this stacking context scrolls its overflow area. pub scrolls_overflow_area: bool, /// The layer info for this stacking context, if there is any. pub layer_info: Option, /// The LayerId of the last child layer of this stacking context. pub last_child_layer_info: Option, } impl StackingContext { /// Creates a new stacking context. #[inline] pub fn new(display_list: Box, bounds: &Rect, overflow: &Rect, z_index: i32, filters: filter::T, blend_mode: mix_blend_mode::T, transform: Matrix4, perspective: Matrix4, establishes_3d_context: bool, scrolls_overflow_area: bool, layer_info: Option) -> StackingContext { let mut stacking_context = StackingContext { display_list: display_list, bounds: *bounds, overflow: *overflow, z_index: z_index, filters: filters, blend_mode: blend_mode, transform: transform, perspective: perspective, establishes_3d_context: establishes_3d_context, scrolls_overflow_area: scrolls_overflow_area, layer_info: layer_info, last_child_layer_info: None, }; StackingContextLayerCreator::add_layers_to_preserve_drawing_order(&mut stacking_context); stacking_context } /// Draws the stacking context in the proper order according to the steps in CSS 2.1 § E.2. pub fn draw_into_context(&self, display_list: &DisplayList, paint_context: &mut PaintContext, transform: &Matrix4, clip_rect: Option<&Rect>) { let temporary_draw_target = paint_context.get_or_create_temporary_draw_target(&self.filters, self.blend_mode); display_list.draw_into_context(&temporary_draw_target, paint_context, transform, clip_rect); paint_context.draw_temporary_draw_target_if_necessary(&temporary_draw_target, &self.filters, self.blend_mode) } /// Optionally optimize and then draws the stacking context. pub fn optimize_and_draw_into_context(&self, paint_context: &mut PaintContext, transform: &Matrix4, clip_rect: Option<&Rect>) { // If a layer is being used, the transform for this layer // will be handled by the compositor. let transform = match self.layer_info { Some(..) => *transform, None => transform.mul(&self.transform), }; // TODO(gw): This is a hack to avoid running the DL optimizer // on 3d transformed tiles. We should have a better solution // than just disabling the opts here. if paint_context.layer_kind == LayerKind::HasTransform { self.draw_into_context(&self.display_list, paint_context, &transform, clip_rect); } else { // Invert the current transform, then use this to back transform // the tile rect (placed at the origin) into the space of this // stacking context. let inverse_transform = transform.invert(); let inverse_transform_2d = Matrix2D::new(inverse_transform.m11, inverse_transform.m12, inverse_transform.m21, inverse_transform.m22, inverse_transform.m41, inverse_transform.m42); let tile_size = Size2D::new(paint_context.screen_rect.as_f32().size.width, paint_context.screen_rect.as_f32().size.height); let tile_rect = Rect::new(Point2D::zero(), tile_size).to_untyped(); let tile_rect = inverse_transform_2d.transform_rect(&tile_rect); // Optimize the display list to throw out out-of-bounds display items and so forth. let display_list = DisplayListOptimizer::new(&tile_rect).optimize(&*self.display_list); self.draw_into_context(&display_list, paint_context, &transform, clip_rect); } } /// Places all nodes containing the point of interest into `result`, topmost first. Respects /// the `pointer-events` CSS property If `topmost_only` is true, stops after placing one node /// into the list. `result` must be empty upon entry to this function. pub fn hit_test(&self, point: Point2D, result: &mut Vec, topmost_only: bool) { // Convert the point into stacking context local space let point = point - self.bounds.origin; debug_assert!(!topmost_only || result.is_empty()); let inv_transform = self.transform.invert(); let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(), point.y.to_f32_px())); let point = Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y)); self.display_list.hit_test(point, result, topmost_only) } pub fn print(&self, title: String) { let mut print_tree = PrintTree::new(title); self.print_with_tree(&mut print_tree); } fn print_with_tree(&self, print_tree: &mut PrintTree) { if self.layer_info.is_some() { print_tree.new_level(format!("Layered StackingContext at {:?} with overflow {:?}:", self.bounds, self.overflow)); } else { print_tree.new_level(format!("StackingContext at {:?} with overflow {:?}:", self.bounds, self.overflow)); } self.display_list.print_with_tree(print_tree); print_tree.end_level(); } fn scroll_policy(&self) -> ScrollPolicy { match self.layer_info { Some(ref layer_info) => layer_info.scroll_policy, None => ScrollPolicy::Scrollable, } } fn get_layer_info(&mut self, layer_id: LayerId) -> &mut LayerInfo { for layer_info in self.display_list.layer_info.iter_mut() { if layer_info.layer_id == layer_id { return layer_info; } } panic!("Could not find LayerInfo with id: {:?}", layer_id); } } struct StackingContextLayerCreator { display_list_for_next_layer: Option, next_layer_info: Option, building_ordering_layer: bool, last_child_layer_info: Option, } impl StackingContextLayerCreator { fn new() -> StackingContextLayerCreator { StackingContextLayerCreator { display_list_for_next_layer: None, next_layer_info: None, building_ordering_layer: false, last_child_layer_info: None, } } #[inline] fn add_layers_to_preserve_drawing_order(stacking_context: &mut StackingContext) { let mut state = StackingContextLayerCreator::new(); // First we need to sort positioned content by z-index, so we can paint // it in order and also so that we can detect situations where unlayered // content should be on top of layered content. let positioned_content = mem::replace(&mut stacking_context.display_list.positioned_content, LinkedList::new()); let mut sorted_positioned_content: SmallVec<[DisplayItem; 8]> = SmallVec::new(); sorted_positioned_content.extend(positioned_content.into_iter()); sorted_positioned_content.sort_by(|this, other| this.compare_zindex(other)); // It's important here that we process all elements in paint order, so we can detect // situations where layers are needed to maintain paint order. state.layerize_display_list_section(DisplayListSection::BackgroundAndBorders, stacking_context); let mut remaining_positioned_content: SmallVec<[DisplayItem; 8]> = SmallVec::new(); for item in sorted_positioned_content.into_iter() { if !item.has_negative_z_index() { remaining_positioned_content.push(item); } else { state.add_display_item(item, DisplayListSection::PositionedContent, stacking_context); } } state.layerize_display_list_section(DisplayListSection::BlockBackgroundsAndBorders, stacking_context); state.layerize_display_list_section(DisplayListSection::Floats, stacking_context); state.layerize_display_list_section(DisplayListSection::Content, stacking_context); for item in remaining_positioned_content.into_iter() { assert!(!item.has_negative_z_index()); state.add_display_item(item, DisplayListSection::PositionedContent, stacking_context); } state.layerize_display_list_section(DisplayListSection::Outlines, stacking_context); state.finish_building_current_layer(stacking_context); stacking_context.last_child_layer_info = state.find_last_child_layer_info(stacking_context); } #[inline] fn layerize_display_list_section(&mut self, section: DisplayListSection, stacking_context: &mut StackingContext) { let section_list = stacking_context.display_list.get_section_mut(section).split_off(0); for item in section_list.into_iter() { self.add_display_item(item, section, stacking_context); } } #[inline] fn all_following_children_need_layers(&self) -> bool { self.next_layer_info.is_some() } #[inline] fn display_item_needs_layer(&mut self, item: &DisplayItem) -> bool { match *item { LayeredItemClass(_) => true, StackingContextClass(ref stacking_context) => stacking_context.layer_info.is_some() || self.all_following_children_need_layers(), _ => self.all_following_children_need_layers(), } } #[inline] fn prepare_ordering_layer(&mut self, stacking_context: &mut StackingContext) { if self.building_ordering_layer { assert!(self.next_layer_info.is_some()); return; } let next_layer_info = Some(stacking_context .get_layer_info(self.next_layer_info.unwrap().layer_id) .next_with_scroll_policy(ScrollPolicy::Scrollable)); self.finish_building_current_layer(stacking_context); self.next_layer_info = next_layer_info; self.building_ordering_layer = true; } fn add_display_item(&mut self, item: DisplayItem, section: DisplayListSection, parent_stacking_context: &mut StackingContext) { if !self.display_item_needs_layer(&item) { if let DisplayItem::StackingContextClass(ref stacking_context) = item { // This StackingContext has a layered child somewhere in its children. // We need to give all new StackingContexts their own layer, so that they // draw on top of this layered child. if let Some(layer_info) = stacking_context.last_child_layer_info { self.last_child_layer_info = stacking_context.last_child_layer_info; self.building_ordering_layer = true; self.next_layer_info = Some(layer_info.clone().next_with_scroll_policy(ScrollPolicy::Scrollable)); } } parent_stacking_context.display_list.add_to_section(item, section); return; } if let StackingContextClass(ref stacking_context) = item { // There is a bit of subtlety here. If this item is a stacking context, // yet doesn't have a layer assigned this code will fall through. This means that // stacking contexts that are promoted to layers will share layers with sibling // display items. let layer_info = stacking_context.layer_info.clone(); if let Some(mut layer_info) = layer_info { self.finish_building_current_layer(parent_stacking_context); // We have started processing layered stacking contexts, so any stacking context that // we process from now on needs its own layer to ensure proper rendering order. self.building_ordering_layer = true; self.next_layer_info = Some(layer_info.next_with_scroll_policy(parent_stacking_context.scroll_policy())); parent_stacking_context.display_list.layered_children.push_back( Arc::new(PaintLayer::new_with_stacking_context(layer_info, stacking_context.clone(), color::transparent()))); return; } } if let LayeredItemClass(item) = item { if let Some(ref next_layer_info) = self.next_layer_info { if item.layer_id == next_layer_info.layer_id && !self.building_ordering_layer { return; } } self.finish_building_current_layer(parent_stacking_context); self.building_ordering_layer = false; self.next_layer_info = Some(parent_stacking_context.get_layer_info(item.layer_id).clone()); self.add_display_item_to_display_list(item.item, section); return; } self.prepare_ordering_layer(parent_stacking_context); self.add_display_item_to_display_list(item, section); } fn add_display_item_to_display_list(&mut self, item: DisplayItem, section: DisplayListSection) { if self.display_list_for_next_layer.is_none() { self.display_list_for_next_layer = Some(DisplayList::new()); } if let Some(ref mut display_list) = self.display_list_for_next_layer { display_list.add_to_section(item, section); } } fn find_last_child_layer_info(self, stacking_context: &mut StackingContext) -> Option { if let Some(layer) = stacking_context.display_list.layered_children.back() { return Some(LayerInfo::new(layer.id, ScrollPolicy::Scrollable, None)); } self.last_child_layer_info } #[inline] fn finish_building_current_layer(&mut self, stacking_context: &mut StackingContext) { if let Some(display_list) = self.display_list_for_next_layer.take() { let layer_info = self.next_layer_info.take().unwrap(); stacking_context.display_list.layered_children.push_back( Arc::new(PaintLayer::new_with_display_list(layer_info, display_list))); } } } /// One drawing command in the list. #[derive(Clone, Deserialize, HeapSizeOf, Serialize)] pub enum DisplayItem { SolidColorClass(Box), TextClass(Box), ImageClass(Box), BorderClass(Box), GradientClass(Box), LineClass(Box), BoxShadowClass(Box), StackingContextClass(Arc), LayeredItemClass(Box), NoopClass(Box), } /// Information common to all display items. #[derive(Clone, Deserialize, HeapSizeOf, Serialize)] pub struct BaseDisplayItem { /// The boundaries of the display item, in layer coordinates. pub bounds: Rect, /// Metadata attached to this display item. pub metadata: DisplayItemMetadata, /// The region to clip to. pub clip: ClippingRegion, } impl BaseDisplayItem { #[inline(always)] pub fn new(bounds: &Rect, metadata: DisplayItemMetadata, clip: &ClippingRegion) -> BaseDisplayItem { // Detect useless clipping regions here and optimize them to `ClippingRegion::max()`. // The painting backend may want to optimize out clipping regions and this makes it easier // for it to do so. BaseDisplayItem { bounds: *bounds, metadata: metadata, clip: if clip.does_not_clip_rect(bounds) { ClippingRegion::max() } else { (*clip).clone() } } } } /// A clipping region for a display item. Currently, this can describe rectangles, rounded /// rectangles (for `border-radius`), or arbitrary intersections of the two. Arbitrary transforms /// are not supported because those are handled by the higher-level `StackingContext` abstraction. #[derive(Clone, PartialEq, Debug, HeapSizeOf, Deserialize, Serialize)] pub struct ClippingRegion { /// The main rectangular region. This does not include any corners. pub main: Rect, /// Any complex regions. /// /// TODO(pcwalton): Atomically reference count these? Not sure if it's worth the trouble. /// Measure and follow up. pub complex: Vec, } /// A complex clipping region. These don't as easily admit arbitrary intersection operations, so /// they're stored in a list over to the side. Currently a complex clipping region is just a /// rounded rectangle, but the CSS WGs will probably make us throw more stuff in here eventually. #[derive(Clone, PartialEq, Debug, HeapSizeOf, Deserialize, Serialize)] pub struct ComplexClippingRegion { /// The boundaries of the rectangle. pub rect: Rect, /// Border radii of this rectangle. pub radii: BorderRadii, } impl ClippingRegion { /// Returns an empty clipping region that, if set, will result in no pixels being visible. #[inline] pub fn empty() -> ClippingRegion { ClippingRegion { main: Rect::zero(), complex: Vec::new(), } } /// Returns an all-encompassing clipping region that clips no pixels out. #[inline] pub fn max() -> ClippingRegion { ClippingRegion { main: MAX_RECT, complex: Vec::new(), } } /// Returns a clipping region that represents the given rectangle. #[inline] pub fn from_rect(rect: &Rect) -> ClippingRegion { ClippingRegion { main: *rect, complex: Vec::new(), } } /// Returns the intersection of this clipping region and the given rectangle. /// /// TODO(pcwalton): This could more eagerly eliminate complex clipping regions, at the cost of /// complexity. #[inline] pub fn intersect_rect(self, rect: &Rect) -> ClippingRegion { ClippingRegion { main: self.main.intersection(rect).unwrap_or(Rect::zero()), complex: self.complex, } } /// Returns true if this clipping region might be nonempty. This can return false positives, /// but never false negatives. #[inline] pub fn might_be_nonempty(&self) -> bool { !self.main.is_empty() } /// Returns true if this clipping region might contain the given point and false otherwise. /// This is a quick, not a precise, test; it can yield false positives. #[inline] pub fn might_intersect_point(&self, point: &Point2D) -> bool { self.main.contains(point) && self.complex.iter().all(|complex| complex.rect.contains(point)) } /// Returns true if this clipping region might intersect the given rectangle and false /// otherwise. This is a quick, not a precise, test; it can yield false positives. #[inline] pub fn might_intersect_rect(&self, rect: &Rect) -> bool { self.main.intersects(rect) && self.complex.iter().all(|complex| complex.rect.intersects(rect)) } /// Returns true if this clipping region completely surrounds the given rect. #[inline] pub fn does_not_clip_rect(&self, rect: &Rect) -> bool { self.main.contains(&rect.origin) && self.main.contains(&rect.bottom_right()) && self.complex.iter().all(|complex| { complex.rect.contains(&rect.origin) && complex.rect.contains(&rect.bottom_right()) }) } /// Returns a bounding rect that surrounds this entire clipping region. #[inline] pub fn bounding_rect(&self) -> Rect { let mut rect = self.main; for complex in &*self.complex { rect = rect.union(&complex.rect) } rect } /// Intersects this clipping region with the given rounded rectangle. #[inline] pub fn intersect_with_rounded_rect(mut self, rect: &Rect, radii: &BorderRadii) -> ClippingRegion { self.complex.push(ComplexClippingRegion { rect: *rect, radii: *radii, }); self } /// Translates this clipping region by the given vector. #[inline] pub fn translate(&self, delta: &Point2D) -> ClippingRegion { ClippingRegion { main: self.main.translate(delta), complex: self.complex.iter().map(|complex| { ComplexClippingRegion { rect: complex.rect.translate(delta), radii: complex.radii, } }).collect(), } } } /// Metadata attached to each display item. This is useful for performing auxiliary threads with /// the display list involving hit testing: finding the originating DOM node and determining the /// cursor to use when the element is hovered over. #[derive(Clone, Copy, HeapSizeOf, Deserialize, Serialize)] pub struct DisplayItemMetadata { /// The DOM node from which this display item originated. pub node: OpaqueNode, /// The value of the `cursor` property when the mouse hovers over this display item. If `None`, /// this display item is ineligible for pointer events (`pointer-events: none`). pub pointing: Option, } impl DisplayItemMetadata { /// Creates a new set of display metadata for a display item constributed by a DOM node. /// `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] pub fn new(node: OpaqueNode, style: &ComputedValues, default_cursor: Cursor) -> DisplayItemMetadata { DisplayItemMetadata { node: node, pointing: match (style.get_pointing().pointer_events, style.get_pointing().cursor) { (pointer_events::T::none, _) => None, (pointer_events::T::auto, cursor::T::AutoCursor) => Some(default_cursor), (pointer_events::T::auto, cursor::T::SpecifiedCursor(cursor)) => Some(cursor), }, } } } /// Paints a solid color. #[derive(Clone, HeapSizeOf, Deserialize, Serialize)] pub struct SolidColorDisplayItem { /// Fields common to all display items. pub base: BaseDisplayItem, /// The color. pub color: Color, } /// Paints text. #[derive(Clone, HeapSizeOf, Deserialize, Serialize)] pub struct TextDisplayItem { /// Fields common to all display items. pub base: BaseDisplayItem, /// The text run. #[ignore_heap_size_of = "Because it is non-owning"] pub text_run: Arc, /// The range of text within the text run. pub range: Range, /// The color of the text. pub text_color: Color, /// The position of the start of the baseline of this text. pub baseline_origin: Point2D, /// The orientation of the text: upright or sideways left/right. pub orientation: TextOrientation, /// The blur radius for this text. If zero, this text is not blurred. pub blur_radius: Au, } #[derive(Clone, Eq, PartialEq, HeapSizeOf, Deserialize, Serialize)] pub enum TextOrientation { Upright, SidewaysLeft, SidewaysRight, } /// Paints an image. #[derive(Clone, HeapSizeOf, Deserialize, Serialize)] pub struct ImageDisplayItem { pub base: BaseDisplayItem, #[ignore_heap_size_of = "Because it is non-owning"] pub image: Arc, /// The dimensions to which the image display item should be stretched. If this is smaller than /// the bounds of this display item, then the image will be repeated in the appropriate /// direction to tile the entire bounds. pub stretch_size: Size2D, /// The algorithm we should use to stretch the image. See `image_rendering` in CSS-IMAGES-3 § /// 5.3. pub image_rendering: image_rendering::T, } /// Paints a gradient. #[derive(Clone, Deserialize, Serialize)] pub struct GradientDisplayItem { /// Fields common to all display items. pub base: BaseDisplayItem, /// The start point of the gradient (computed during display list construction). pub start_point: Point2D, /// The end point of the gradient (computed during display list construction). pub end_point: Point2D, /// A list of color stops. pub stops: Vec, } impl HeapSizeOf for GradientDisplayItem { fn heap_size_of_children(&self) -> usize { use libc::c_void; use util::mem::heap_size_of; // We can't measure `stops` via Vec's HeapSizeOf implementation because GradientStop isn't // defined in this module, and we don't want to import GradientStop into util::mem where // the HeapSizeOf trait is defined. So we measure the elements directly. self.base.heap_size_of_children() + heap_size_of(self.stops.as_ptr() as *const c_void) } } /// Paints a border. #[derive(Clone, HeapSizeOf, Deserialize, Serialize)] pub struct BorderDisplayItem { /// Fields common to all display items. pub base: BaseDisplayItem, /// Border widths. pub border_widths: SideOffsets2D, /// Border colors. pub color: SideOffsets2D, /// Border styles. pub style: SideOffsets2D, /// Border radii. /// /// TODO(pcwalton): Elliptical radii. pub radius: BorderRadii, } /// Information about the border radii. /// /// TODO(pcwalton): Elliptical radii. #[derive(Clone, PartialEq, Debug, Copy, HeapSizeOf, Deserialize, Serialize)] pub struct BorderRadii { pub top_left: Size2D, pub top_right: Size2D, pub bottom_right: Size2D, pub bottom_left: Size2D, } impl Default for BorderRadii where T: Default, T: Clone { fn default() -> Self { let top_left = Size2D::new(Default::default(), Default::default()); let top_right = Size2D::new(Default::default(), Default::default()); let bottom_left = Size2D::new(Default::default(), Default::default()); let bottom_right = Size2D::new(Default::default(), Default::default()); BorderRadii { top_left: top_left, top_right: top_right, bottom_left: bottom_left, bottom_right: bottom_right } } } impl BorderRadii { // Scale the border radii by the specified factor pub fn scale_by(&self, s: f32) -> BorderRadii { BorderRadii { top_left: BorderRadii::scale_corner_by(self.top_left, s), top_right: BorderRadii::scale_corner_by(self.top_right, s), bottom_left: BorderRadii::scale_corner_by(self.bottom_left, s), bottom_right: BorderRadii::scale_corner_by(self.bottom_right, s) } } // Scale the border corner radius by the specified factor pub fn scale_corner_by(corner: Size2D, s: f32) -> Size2D { Size2D { width: corner.width.scale_by(s), height: corner.height.scale_by(s) } } } impl BorderRadii where T: PartialEq + Zero { /// Returns true if all the radii are zero. pub fn is_square(&self) -> bool { let zero = Zero::zero(); self.top_left == zero && self.top_right == zero && self.bottom_right == zero && self.bottom_left == zero } } impl BorderRadii where T: PartialEq + Zero + Clone { /// Returns a set of border radii that all have the given value. pub fn all_same(value: T) -> BorderRadii { BorderRadii { top_left: Size2D { width: value.clone(), height: value.clone() }, top_right: Size2D { width: value.clone(), height: value.clone() }, bottom_right: Size2D { width: value.clone(), height: value.clone() }, bottom_left: Size2D { width: value.clone(), height: value.clone() }, } } } /// Paints a line segment. #[derive(Clone, HeapSizeOf, Deserialize, Serialize)] pub struct LineDisplayItem { pub base: BaseDisplayItem, /// The line segment color. pub color: Color, /// The line segment style. pub style: border_style::T } /// Paints a box shadow per CSS-BACKGROUNDS. #[derive(Clone, HeapSizeOf, Deserialize, Serialize)] pub struct BoxShadowDisplayItem { /// Fields common to all display items. pub base: BaseDisplayItem, /// The dimensions of the box that we're placing a shadow around. pub box_bounds: Rect, /// The offset of this shadow from the box. pub offset: Point2D, /// The color of this shadow. pub color: Color, /// The blur radius for this shadow. pub blur_radius: Au, /// The spread radius of this shadow. pub spread_radius: Au, /// The border radius of this shadow. /// /// TODO(pcwalton): Elliptical radii; different radii for each corner. pub border_radius: Au, /// How we should clip the result. pub clip_mode: BoxShadowClipMode, } /// Contains an item that should get its own layer during layer creation. #[derive(Clone, HeapSizeOf, Deserialize, Serialize)] pub struct LayeredItem { /// Fields common to all display items. pub item: DisplayItem, /// The id of the layer this item belongs to. pub layer_id: LayerId, } /// How a box shadow should be clipped. #[derive(Clone, Copy, Debug, PartialEq, HeapSizeOf, Deserialize, Serialize)] pub enum BoxShadowClipMode { /// No special clipping should occur. This is used for (shadowed) text decorations. None, /// The area inside `box_bounds` should be clipped out. Corresponds to the normal CSS /// `box-shadow`. Outset, /// The area outside `box_bounds` should be clipped out. Corresponds to the `inset` flag on CSS /// `box-shadow`. Inset, } pub enum DisplayItemIterator<'a> { Empty, Parent(linked_list::Iter<'a, DisplayItem>), } impl<'a> Iterator for DisplayItemIterator<'a> { type Item = &'a DisplayItem; #[inline] fn next(&mut self) -> Option<&'a DisplayItem> { match *self { DisplayItemIterator::Empty => None, DisplayItemIterator::Parent(ref mut subiterator) => subiterator.next(), } } } impl DisplayItem { /// Paints this display item into the given painting context. fn draw_into_context(&self, transform: &Matrix4, paint_context: &mut PaintContext) { if let Some(base) = self.base() { let this_clip = &base.clip; match paint_context.transient_clip { Some(ref transient_clip) if transient_clip == this_clip => {} Some(_) | None => paint_context.push_transient_clip((*this_clip).clone()), } } match *self { DisplayItem::SolidColorClass(ref solid_color) => { if !solid_color.color.a.approx_eq(&0.0) { paint_context.draw_solid_color(&solid_color.base.bounds, solid_color.color) } } DisplayItem::TextClass(ref text) => { debug!("Drawing text at {:?}.", text.base.bounds); paint_context.draw_text(&**text); } DisplayItem::ImageClass(ref image_item) => { debug!("Drawing image at {:?}.", image_item.base.bounds); paint_context.draw_image(&image_item.base.bounds, &image_item.stretch_size, image_item.image.clone(), image_item.image_rendering.clone()); } DisplayItem::BorderClass(ref border) => { paint_context.draw_border(&border.base.bounds, &border.border_widths, &border.radius, &border.color, &border.style) } DisplayItem::GradientClass(ref gradient) => { paint_context.draw_linear_gradient(&gradient.base.bounds, &gradient.start_point, &gradient.end_point, &gradient.stops); } DisplayItem::LineClass(ref line) => { paint_context.draw_line(&line.base.bounds, line.color, line.style) } DisplayItem::BoxShadowClass(ref box_shadow) => { paint_context.draw_box_shadow(&box_shadow.box_bounds, &box_shadow.offset, box_shadow.color, box_shadow.blur_radius, box_shadow.spread_radius, box_shadow.clip_mode); } DisplayItem::StackingContextClass(ref stacking_context) => { let pixels_per_px = paint_context.screen_pixels_per_px(); let new_transform = transform.translate(stacking_context.bounds .origin .x .to_nearest_pixel(pixels_per_px.get()) as AzFloat, stacking_context.bounds .origin .y .to_nearest_pixel(pixels_per_px.get()) as AzFloat, 0.0); stacking_context.optimize_and_draw_into_context(paint_context, &new_transform, Some(&stacking_context.overflow)) } DisplayItem::LayeredItemClass(_) => panic!("Found layered item during drawing."), DisplayItem::NoopClass(_) => { } } } pub fn base(&self) -> Option<&BaseDisplayItem> { match *self { DisplayItem::SolidColorClass(ref solid_color) => Some(&solid_color.base), DisplayItem::TextClass(ref text) => Some(&text.base), DisplayItem::ImageClass(ref image_item) => Some(&image_item.base), DisplayItem::BorderClass(ref border) => Some(&border.base), DisplayItem::GradientClass(ref gradient) => Some(&gradient.base), DisplayItem::LineClass(ref line) => Some(&line.base), DisplayItem::BoxShadowClass(ref box_shadow) => Some(&box_shadow.base), DisplayItem::LayeredItemClass(ref layered_item) => layered_item.item.base(), DisplayItem::NoopClass(ref base_item) => Some(base_item), DisplayItem::StackingContextClass(_) => None, } } pub fn bounds(&self) -> Rect { match *self { DisplayItem::StackingContextClass(ref stacking_context) => stacking_context.bounds, _ => self.base().unwrap().bounds, } } pub fn debug_with_level(&self, level: u32) { let mut indent = String::new(); for _ in 0..level { indent.push_str("| ") } println!("{}+ {:?}", indent, self); } fn compare_zindex(&self, other: &DisplayItem) -> Ordering { match (self, other) { (&DisplayItem::StackingContextClass(ref this), &DisplayItem::StackingContextClass(ref other)) => this.z_index.cmp(&other.z_index), (&DisplayItem::StackingContextClass(ref this), _) => this.z_index.cmp(&0), (_, &DisplayItem::StackingContextClass(ref other)) => 0.cmp(&other.z_index), (_, _) => Ordering::Equal, } } fn has_negative_z_index(&self) -> bool { if let &DisplayItem::StackingContextClass(ref stacking_context) = self { stacking_context.z_index < 0 } else { false } } } impl fmt::Debug for DisplayItem { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} @ {:?}", match *self { DisplayItem::SolidColorClass(ref solid_color) => format!("SolidColor rgba({}, {}, {}, {})", solid_color.color.r, solid_color.color.g, solid_color.color.b, solid_color.color.a), DisplayItem::TextClass(_) => "Text".to_owned(), DisplayItem::ImageClass(_) => "Image".to_owned(), DisplayItem::BorderClass(_) => "Border".to_owned(), DisplayItem::GradientClass(_) => "Gradient".to_owned(), DisplayItem::LineClass(_) => "Line".to_owned(), DisplayItem::BoxShadowClass(_) => "BoxShadow".to_owned(), DisplayItem::StackingContextClass(_) => "StackingContext".to_owned(), DisplayItem::LayeredItemClass(ref layered_item) => format!("LayeredItem({:?})", layered_item.item), DisplayItem::NoopClass(_) => "Noop".to_owned(), }, self.bounds(), ) } }