diff options
author | Oriol Brufau <obrufau@igalia.com> | 2025-03-13 08:26:57 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-13 07:26:57 +0000 |
commit | 7594dc69916f3c4d8ce771e1ef990b6d223eb158 (patch) | |
tree | e5e014f4b1168d13518b4c373c6aa8f5f9a37824 /components/layout/display_list/builder.rs | |
parent | f93006af95dd75a07de2571e6a2edabcc64a46ac (diff) | |
download | servo-7594dc69916f3c4d8ce771e1ef990b6d223eb158.tar.gz servo-7594dc69916f3c4d8ce771e1ef990b6d223eb158.zip |
Remove legacy layout (layout 2013) (#35943)
We were already not compiling it and not running tests on it by default.
So it's simpler to just completely remove it.
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Diffstat (limited to 'components/layout/display_list/builder.rs')
-rw-r--r-- | components/layout/display_list/builder.rs | 2932 |
1 files changed, 0 insertions, 2932 deletions
diff --git a/components/layout/display_list/builder.rs b/components/layout/display_list/builder.rs deleted file mode 100644 index 5b93fee53c7..00000000000 --- a/components/layout/display_list/builder.rs +++ /dev/null @@ -1,2932 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Builds display lists from flows and fragments. -//! -//! Other browser engines sometimes call this "painting", but it is more accurately called display -//! list building, as the actual painting does not happen here—only deciding *what* we're going to -//! paint. - -use std::default::Default; -use std::sync::Arc; -use std::{f32, mem}; - -use app_units::{AU_PER_PX, Au}; -use base::id::PipelineId; -use bitflags::bitflags; -use canvas_traits::canvas::{CanvasMsg, FromLayoutMsg}; -use embedder_traits::Cursor; -use euclid::default::{Point2D, Rect, SideOffsets2D as UntypedSideOffsets2D, Size2D}; -use euclid::{Scale, SideOffsets2D, rect}; -use fnv::FnvHashMap; -use fonts::ByteIndex; -use log::{debug, warn}; -use net_traits::image_cache::UsePlaceholder; -use range::Range; -use script_layout_interface::{ - FragmentType, IFrameSize, IFrameSizes, combine_id_with_fragment_type, -}; -use servo_geometry::{self, MaxRect}; -use style::color::AbsoluteColor; -use style::computed_values::border_style::T as BorderStyle; -use style::computed_values::overflow_x::T as StyleOverflow; -use style::computed_values::pointer_events::T as PointerEvents; -use style::computed_values::position::T as StylePosition; -use style::computed_values::visibility::T as Visibility; -use style::logical_geometry::{LogicalMargin, LogicalPoint, LogicalRect}; -use style::properties::{ComputedValues, style_structs}; -use style::servo::restyle_damage::ServoRestyleDamage; -use style::values::computed::effects::SimpleShadow; -use style::values::computed::image::Image; -use style::values::computed::{ClipRectOrAuto, Gradient}; -use style::values::generics::background::BackgroundSize; -use style::values::generics::image::PaintWorklet; -use style::values::specified::ui::CursorKind; -use style_traits::ToCss; -use webrender_api::units::{LayoutRect, LayoutTransform, LayoutVector2D}; -use webrender_api::{ - self, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, ColorF, ExternalScrollId, - FilterOp, GlyphInstance, ImageRendering, LineStyle, NinePatchBorder, NinePatchBorderSource, - NormalBorder, PropertyBinding, StickyOffsetBounds, -}; -use webrender_traits::display_list::AxesScrollSensitivity; - -use super::StackingContextId; -use crate::block::BlockFlow; -use crate::context::LayoutContext; -use crate::display_list::background::{self, get_cyclic}; -use crate::display_list::items::{ - self, BaseDisplayItem, ClipScrollNode, ClipScrollNodeIndex, ClipScrollNodeType, ClipType, - ClippingAndScrolling, ClippingRegion, CommonDisplayItem, DisplayItem, DisplayItemMetadata, - DisplayList, DisplayListSection, IframeDisplayItem, OpaqueNode, PopAllTextShadowsDisplayItem, - PushTextShadowDisplayItem, StackingContext, StackingContextType, StickyFrameData, - TextOrientation, WebRenderImageInfo, -}; -use crate::display_list::{FilterToLayout, ToLayout, border, gradient}; -use crate::flow::{BaseFlow, Flow, FlowFlags}; -use crate::flow_ref::FlowRef; -use crate::fragment::{ - CanvasFragmentSource, CoordinateSystem, Fragment, ScannedTextFragmentInfo, SpecificFragmentInfo, -}; -use crate::inline::InlineFragmentNodeFlags; -use crate::model::MaybeAuto; -use crate::table_cell::CollapsedBordersForCell; -use crate::text_run::TextRun; - -// An internal WebRender limit. -// -// See: https://github.com/servo/servo/issues/17230#issuecomment-564307277 -const MAX_GLYPHS_PER_TEXT_RUN: usize = 2000; - -pub struct InlineNodeBorderInfo { - is_first_fragment_of_element: bool, - is_last_fragment_of_element: bool, -} - -#[derive(Debug)] -struct StackingContextInfo { - children: Vec<StackingContext>, - clip_scroll_nodes: Vec<ClipScrollNodeIndex>, - real_stacking_context_id: StackingContextId, -} - -impl StackingContextInfo { - fn new(real_stacking_context_id: StackingContextId) -> StackingContextInfo { - StackingContextInfo { - children: Vec::new(), - clip_scroll_nodes: Vec::new(), - real_stacking_context_id, - } - } - - fn take_children(&mut self) -> Vec<StackingContext> { - mem::take(&mut self.children) - } -} - -pub struct StackingContextCollectionState { - /// The PipelineId of this stacking context collection. - pub pipeline_id: PipelineId, - - /// The root of the StackingContext tree. - pub root_stacking_context: StackingContext, - - /// StackingContext and ClipScrollNode children for each StackingContext. - stacking_context_info: FnvHashMap<StackingContextId, StackingContextInfo>, - - pub clip_scroll_nodes: Vec<ClipScrollNode>, - - /// The current stacking context id, used to keep track of state when building. - /// recursively building and processing the display list. - pub current_stacking_context_id: StackingContextId, - - /// The current reference frame ClipScrollNodeIndex. - pub current_real_stacking_context_id: StackingContextId, - - /// The next stacking context id that we will assign to a stacking context. - pub next_stacking_context_id: StackingContextId, - - /// The current reference frame id. This is used to assign items to the parent - /// reference frame when we encounter a fixed position stacking context. - pub current_parent_reference_frame_id: ClipScrollNodeIndex, - - /// The current clip and scroll info, used to keep track of state when - /// recursively building and processing the display list. - pub current_clipping_and_scrolling: ClippingAndScrolling, - - /// The clip and scroll info of the first ancestor which defines a containing block. - /// This is necessary because absolutely positioned items should be clipped - /// by their containing block's scroll root. - pub containing_block_clipping_and_scrolling: ClippingAndScrolling, - - /// A stack of clips used to cull display list entries that are outside the - /// rendered region. - pub clip_stack: Vec<Rect<Au>>, - - /// A stack of clips used to cull display list entries that are outside the - /// rendered region, but only collected at containing block boundaries. - pub containing_block_clip_stack: Vec<Rect<Au>>, - - /// The flow parent's content box, used to calculate sticky constraints. - parent_stacking_relative_content_box: Rect<Au>, -} - -impl StackingContextCollectionState { - pub fn new(pipeline_id: PipelineId) -> StackingContextCollectionState { - let root_clip_indices = - ClippingAndScrolling::simple(ClipScrollNodeIndex::root_scroll_node()); - - let mut stacking_context_info = FnvHashMap::default(); - stacking_context_info.insert( - StackingContextId::root(), - StackingContextInfo::new(StackingContextId::root()), - ); - - // We add two empty nodes to represent the WebRender root reference frame and - // root scroll nodes. WebRender adds these automatically and we add them here - // so that the ids in the array match up with the ones we assign during display - // list building. We ignore these two nodes during conversion to WebRender - // display lists. - let clip_scroll_nodes = vec![ClipScrollNode::placeholder(), ClipScrollNode::placeholder()]; - - StackingContextCollectionState { - pipeline_id, - root_stacking_context: StackingContext::root(), - stacking_context_info, - clip_scroll_nodes, - current_stacking_context_id: StackingContextId::root(), - current_real_stacking_context_id: StackingContextId::root(), - next_stacking_context_id: StackingContextId::root().next(), - current_parent_reference_frame_id: ClipScrollNodeIndex::root_reference_frame(), - current_clipping_and_scrolling: root_clip_indices, - containing_block_clipping_and_scrolling: root_clip_indices, - clip_stack: Vec::new(), - containing_block_clip_stack: Vec::new(), - parent_stacking_relative_content_box: Rect::zero(), - } - } - - fn allocate_stacking_context_info( - &mut self, - stacking_context_type: StackingContextType, - ) -> StackingContextId { - let next_stacking_context_id = self.next_stacking_context_id.next(); - let allocated_id = - mem::replace(&mut self.next_stacking_context_id, next_stacking_context_id); - - let real_stacking_context_id = match stacking_context_type { - StackingContextType::Real => allocated_id, - _ => self.current_real_stacking_context_id, - }; - - self.stacking_context_info.insert( - allocated_id, - StackingContextInfo::new(real_stacking_context_id), - ); - - allocated_id - } - - fn add_stacking_context( - &mut self, - parent_id: StackingContextId, - stacking_context: StackingContext, - ) { - self.stacking_context_info - .get_mut(&parent_id) - .unwrap() - .children - .push(stacking_context); - } - - fn add_clip_scroll_node(&mut self, clip_scroll_node: ClipScrollNode) -> ClipScrollNodeIndex { - let is_placeholder = clip_scroll_node.is_placeholder(); - - self.clip_scroll_nodes.push(clip_scroll_node); - let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1); - - // If this node is a placeholder node (currently just reference frames), then don't add - // it to the stacking context list. Placeholder nodes are created automatically by - // WebRender and we don't want to explicitly create them in the display list. The node - // is just there to take up a spot in the global list of ClipScrollNodes. - if !is_placeholder { - // We want the scroll root to be defined before any possible item that could use it, - // so we make sure that it is added to the beginning of the parent "real" (non-pseudo) - // stacking context. This ensures that item reordering will not result in an item using - // the scroll root before it is defined. - self.stacking_context_info - .get_mut(&self.current_real_stacking_context_id) - .unwrap() - .clip_scroll_nodes - .push(index); - } - - index - } -} - -pub struct DisplayListBuildState<'a> { - /// A LayoutContext reference important for creating WebRender images. - pub layout_context: &'a LayoutContext<'a>, - - /// The root of the StackingContext tree. - pub root_stacking_context: StackingContext, - - /// StackingContext and ClipScrollNode children for each StackingContext. - stacking_context_info: FnvHashMap<StackingContextId, StackingContextInfo>, - - /// A vector of ClipScrollNodes which will be given ids during WebRender DL conversion. - pub clip_scroll_nodes: Vec<ClipScrollNode>, - - /// The items in this display list. - pub items: FnvHashMap<StackingContextId, Vec<DisplayItem>>, - - /// Whether or not we are processing an element that establishes scrolling overflow. Used - /// to determine what ClipScrollNode to place backgrounds and borders into. - pub processing_scrolling_overflow_element: bool, - - /// The current stacking context id, used to keep track of state when building. - /// recursively building and processing the display list. - pub current_stacking_context_id: StackingContextId, - - /// The current clip and scroll info, used to keep track of state when - /// recursively building and processing the display list. - pub current_clipping_and_scrolling: ClippingAndScrolling, - - /// Vector containing iframe sizes, used to inform the constellation about - /// new iframe sizes - pub iframe_sizes: IFrameSizes, - - /// Stores text runs to answer text queries used to place a cursor inside text. - pub indexable_text: IndexableText, -} - -impl<'a> DisplayListBuildState<'a> { - pub fn new( - layout_context: &'a LayoutContext, - state: StackingContextCollectionState, - ) -> DisplayListBuildState<'a> { - DisplayListBuildState { - layout_context, - root_stacking_context: state.root_stacking_context, - items: FnvHashMap::default(), - stacking_context_info: state.stacking_context_info, - clip_scroll_nodes: state.clip_scroll_nodes, - processing_scrolling_overflow_element: false, - current_stacking_context_id: StackingContextId::root(), - current_clipping_and_scrolling: ClippingAndScrolling::simple( - ClipScrollNodeIndex::root_scroll_node(), - ), - iframe_sizes: FnvHashMap::default(), - indexable_text: IndexableText::default(), - } - } - - pub fn add_display_item(&mut self, display_item: DisplayItem) { - let items = self - .items - .entry(display_item.stacking_context_id()) - .or_default(); - items.push(display_item); - } - - fn add_image_item(&mut self, base: BaseDisplayItem, item: webrender_api::ImageDisplayItem) { - self.add_display_item(DisplayItem::Image(CommonDisplayItem::new(base, item))) - } - - fn parent_clip_scroll_node_index(&self, index: ClipScrollNodeIndex) -> ClipScrollNodeIndex { - if index.is_root_scroll_node() { - return index; - } - - self.clip_scroll_nodes[index.to_index()].parent_index - } - - fn is_background_or_border_of_clip_scroll_node(&self, section: DisplayListSection) -> bool { - (section == DisplayListSection::BackgroundAndBorders || - section == DisplayListSection::BlockBackgroundsAndBorders) && - self.processing_scrolling_overflow_element - } - - pub fn create_base_display_item( - &self, - clip_rect: Rect<Au>, - node: OpaqueNode, - unique_id: u64, - cursor: Option<Cursor>, - section: DisplayListSection, - ) -> BaseDisplayItem { - let clipping_and_scrolling = if self.is_background_or_border_of_clip_scroll_node(section) { - ClippingAndScrolling::simple( - self.parent_clip_scroll_node_index(self.current_clipping_and_scrolling.scrolling), - ) - } else { - self.current_clipping_and_scrolling - }; - self.create_base_display_item_with_clipping_and_scrolling( - clip_rect, - node, - unique_id, - cursor, - section, - clipping_and_scrolling, - ) - } - - fn create_base_display_item_with_clipping_and_scrolling( - &self, - clip_rect: Rect<Au>, - node: OpaqueNode, - unique_id: u64, - cursor: Option<Cursor>, - section: DisplayListSection, - clipping_and_scrolling: ClippingAndScrolling, - ) -> BaseDisplayItem { - BaseDisplayItem::new( - DisplayItemMetadata { - node, - unique_id, - cursor, - }, - clip_rect.to_layout(), - section, - self.current_stacking_context_id, - clipping_and_scrolling, - ) - } - - fn add_late_clip_node(&mut self, rect: LayoutRect, radii: BorderRadius) -> ClipScrollNodeIndex { - let node = - ClipScrollNode::rounded(rect, radii, self.current_clipping_and_scrolling.scrolling); - - // We want the scroll root to be defined before any possible item that could use it, - // so we make sure that it is added to the beginning of the parent "real" (non-pseudo) - // stacking context. This ensures that item reordering will not result in an item using - // the scroll root before it is defined. - self.clip_scroll_nodes.push(node); - let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1); - let real_stacking_context_id = - self.stacking_context_info[&self.current_stacking_context_id].real_stacking_context_id; - self.stacking_context_info - .get_mut(&real_stacking_context_id) - .unwrap() - .clip_scroll_nodes - .push(index); - - index - } - - pub fn to_display_list(mut self) -> DisplayList { - let mut list = Vec::new(); - let root_context = mem::replace(&mut self.root_stacking_context, StackingContext::root()); - - self.move_to_display_list_for_stacking_context(&mut list, root_context); - - DisplayList { - list, - clip_scroll_nodes: self.clip_scroll_nodes, - } - } - - fn move_to_display_list_for_stacking_context( - &mut self, - list: &mut Vec<DisplayItem>, - stacking_context: StackingContext, - ) { - let mut child_items = self.items.remove(&stacking_context.id).unwrap_or_default(); - child_items.sort_by(|a, b| a.base().section.cmp(&b.base().section)); - child_items.reverse(); - - let mut info = self - .stacking_context_info - .remove(&stacking_context.id) - .unwrap(); - - info.children.sort(); - - if stacking_context.context_type != StackingContextType::Real { - list.extend( - info.clip_scroll_nodes - .into_iter() - .map(|index| index.to_define_item()), - ); - self.move_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.move_to_display_list_for_items(list, child_items, info.children); - list.push(pop_item); - } - } - - fn move_to_display_list_for_items( - &mut self, - list: &mut Vec<DisplayItem>, - mut child_items: Vec<DisplayItem>, - child_stacking_contexts: Vec<StackingContext>, - ) { - // Properly order display items that make up a stacking context. "Steps" here - // refer to the steps in CSS 2.1 Appendix E. - // Steps 1 and 2: Borders and background for the root. - while child_items - .last() - .is_some_and(|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() - .is_some_and(|child| child.z_index < 0) - { - let context = child_stacking_contexts.next().unwrap(); - self.move_to_display_list_for_stacking_context(list, context); - } - - // Step 4: Block backgrounds and borders. - while child_items - .last() - .is_some_and(|child| child.section() == DisplayListSection::BlockBackgroundsAndBorders) - { - list.push(child_items.pop().unwrap()); - } - - // Step 5: Floats. - while child_stacking_contexts - .peek() - .is_some_and(|child| child.context_type == StackingContextType::PseudoFloat) - { - let context = child_stacking_contexts.next().unwrap(); - self.move_to_display_list_for_stacking_context(list, context); - } - - // Step 6 & 7: Content and inlines that generate stacking contexts. - while child_items - .last() - .is_some_and(|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.move_to_display_list_for_stacking_context(list, child); - } - - // Step 10: Outlines. - for item in child_items.drain(..) { - list.push(item); - } - } - - fn clipping_and_scrolling_scope<R, F: FnOnce(&mut Self) -> R>(&mut self, function: F) -> R { - let previous_clipping_and_scrolling = self.current_clipping_and_scrolling; - let ret = function(self); - self.current_clipping_and_scrolling = previous_clipping_and_scrolling; - ret - } -} - -/// The logical width of an insertion point: at the moment, a one-pixel-wide line. -const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX); - -/// Get the border radius for the rectangle inside of a rounded border. This is useful -/// for building the clip for the content inside the border. -fn build_border_radius_for_inner_rect( - outer_rect: Rect<Au>, - style: &ComputedValues, -) -> BorderRadius { - let radii = border::radii(outer_rect, style.get_border()); - if radii.is_zero() { - return radii; - } - - // Since we are going to using the inner rectangle (outer rectangle minus - // border width), we need to adjust to border radius so that we are smaller - // rectangle with the same border curve. - let border_widths = style.logical_border_width().to_physical(style.writing_mode); - border::inner_radii(radii, border_widths) -} - -impl Fragment { - pub fn collect_stacking_contexts_for_blocklike_fragment( - &mut self, - state: &mut StackingContextCollectionState, - ) -> bool { - match self.specific { - SpecificFragmentInfo::InlineBlock(ref mut block_flow) => { - let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); - block_flow.collect_stacking_contexts(state); - true - }, - SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut block_flow) => { - let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); - block_flow.collect_stacking_contexts(state); - true - }, - SpecificFragmentInfo::InlineAbsolute(ref mut block_flow) => { - let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref); - block_flow.collect_stacking_contexts(state); - true - }, - // FIXME: In the future, if #15144 is fixed we can remove this case. See #18510. - SpecificFragmentInfo::TruncatedFragment(ref mut info) => { - let _ = info - .full - .collect_stacking_contexts_for_blocklike_fragment(state); - // To ensure the caller updates this fragment's stacking context - // appropriately based on the un-truncated fragment's status, - // we don't pass on the result of collecting stacking contexts. - false - }, - _ => false, - } - } - - pub fn create_stacking_context_for_inline_block( - &mut self, - base: &BaseFlow, - state: &mut StackingContextCollectionState, - ) -> bool { - self.stacking_context_id = state.allocate_stacking_context_info(StackingContextType::Real); - - let established_reference_frame = if self.can_establish_reference_frame() { - // WebRender currently creates reference frames automatically, so just add - // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame. - self.established_reference_frame = - Some(state.add_clip_scroll_node(ClipScrollNode::placeholder())); - self.established_reference_frame - } else { - None - }; - - let current_stacking_context_id = state.current_stacking_context_id; - let stacking_context = self.create_stacking_context( - self.stacking_context_id, - base, - StackingContextType::Real, - established_reference_frame, - state.current_clipping_and_scrolling, - ); - state.add_stacking_context(current_stacking_context_id, stacking_context); - true - } - - /// Adds the display items necessary to paint the background of this fragment to the display - /// list if necessary. - fn build_display_list_for_background_if_applicable( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - ) { - let background = style.get_background(); - let background_color = style.resolve_color(&background.background_color); - // XXXManishearth the below method should ideally use an iterator over - // backgrounds - self.build_display_list_for_background_if_applicable_with_background( - state, - style, - background, - background_color, - display_list_section, - absolute_bounds, - ) - } - - /// Same as build_display_list_for_background_if_applicable, but lets you - /// override the actual background used - fn build_display_list_for_background_if_applicable_with_background( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - background: &style_structs::Background, - background_color: AbsoluteColor, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - ) { - // FIXME: This causes a lot of background colors to be displayed when they are clearly not - // needed. We could use display list optimization to clean this up, but it still seems - // inefficient. What we really want is something like "nearest ancestor element that - // doesn't have a fragment". - - // Quote from CSS Backgrounds and Borders Module Level 3: - // - // > The background color is clipped according to the background-clip value associated - // > with the bottom-most background image layer. - let last_background_image_index = background.background_image.0.len() - 1; - let color_clip = *get_cyclic(&background.background_clip.0, last_background_image_index); - let (bounds, border_radii) = background::clip( - color_clip, - absolute_bounds, - style.logical_border_width().to_physical(style.writing_mode), - self.border_padding.to_physical(self.style.writing_mode), - border::radii(absolute_bounds, style.get_border()), - ); - - state.clipping_and_scrolling_scope(|state| { - if !border_radii.is_zero() { - let clip_id = state.add_late_clip_node(bounds.to_layout(), border_radii); - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - } - - let base = state.create_base_display_item( - bounds, - self.node, - self.unique_id(), - get_cursor(style, Cursor::Default), - display_list_section, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - color: PropertyBinding::Value(background_color.to_layout()), - common: items::empty_common_item_properties(), - bounds: bounds.to_layout(), - }, - ))); - }); - - // 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 { - Image::None => {}, - Image::Gradient(ref gradient) => { - self.build_display_list_for_background_gradient( - state, - display_list_section, - absolute_bounds, - gradient, - style, - i, - ); - }, - Image::Url(ref image_url) => { - if let Some(url) = image_url.url() { - let webrender_image = state.layout_context.get_webrender_image_for_url( - self.node, - url.clone().into(), - UsePlaceholder::No, - ); - if let Some(webrender_image) = webrender_image { - self.build_display_list_for_webrender_image( - state, - style, - display_list_section, - absolute_bounds, - webrender_image, - i, - ); - } - } - }, - Image::PaintWorklet(ref paint_worklet) => { - let bounding_box = self.border_box - style.logical_border_width(); - let bounding_box_size = bounding_box.size.to_physical(style.writing_mode); - let background_size = - get_cyclic(&style.get_background().background_size.0, i).clone(); - let size = match background_size { - BackgroundSize::ExplicitSize { width, height } => Size2D::new( - width - .to_used_value(bounding_box_size.width) - .unwrap_or(bounding_box_size.width), - height - .to_used_value(bounding_box_size.height) - .unwrap_or(bounding_box_size.height), - ), - _ => bounding_box_size, - }; - let webrender_image = self.get_webrender_image_for_paint_worklet( - state, - style, - paint_worklet, - size, - ); - if let Some(webrender_image) = webrender_image { - self.build_display_list_for_webrender_image( - state, - style, - display_list_section, - absolute_bounds, - webrender_image, - i, - ); - } - }, - Image::CrossFade(..) | Image::ImageSet(..) => { - // TODO: Add support for ImageSet and CrossFade rendering. - }, - } - } - } - - /// Adds the display items necessary to paint a webrender image of this fragment to the - /// appropriate section of the display list. - fn build_display_list_for_webrender_image( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - webrender_image: WebRenderImageInfo, - index: usize, - ) { - debug!("(building display list) building background image"); - if webrender_image.key.is_none() { - return; - } - - let image = Size2D::new( - Au::from_px(webrender_image.width as i32), - Au::from_px(webrender_image.height as i32), - ); - let placement = background::placement( - style.get_background(), - state.layout_context.shared_context().viewport_size(), - absolute_bounds, - Some(image), - style.logical_border_width().to_physical(style.writing_mode), - self.border_padding.to_physical(self.style.writing_mode), - border::radii(absolute_bounds, style.get_border()), - index, - ); - - let placement = match placement { - Some(placement) => placement, - None => return, - }; - - state.clipping_and_scrolling_scope(|state| { - if !placement.clip_radii.is_zero() { - let clip_id = - state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii); - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - } - - // Create the image display item. - let base = state.create_base_display_item( - placement.clip_rect, - self.node, - self.unique_id(), - get_cursor(style, Cursor::Default), - display_list_section, - ); - - debug!("(building display list) adding background image."); - let item = CommonDisplayItem::new( - base, - webrender_api::RepeatingImageDisplayItem { - bounds: placement.bounds.to_f32_px(), - common: items::empty_common_item_properties(), - image_key: webrender_image.key.unwrap(), - stretch_size: placement.tile_size.to_layout(), - tile_spacing: placement.tile_spacing.to_layout(), - image_rendering: style.get_inherited_box().image_rendering.to_layout(), - alpha_type: webrender_api::AlphaType::PremultipliedAlpha, - color: webrender_api::ColorF::WHITE, - }, - ); - state.add_display_item(DisplayItem::RepeatingImage(item)) - }); - } - - /// Calculates the webrender image for a paint worklet. - /// Returns None if the worklet is not registered. - /// If the worklet has missing image URLs, it passes them to the image cache for loading. - fn get_webrender_image_for_paint_worklet( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - paint_worklet: &PaintWorklet, - size_in_au: Size2D<Au>, - ) -> Option<WebRenderImageInfo> { - let device_pixel_ratio = state.layout_context.style_context.device_pixel_ratio(); - let size_in_px = - euclid::Size2D::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, - Scale::new(device_pixel_ratio.get()), - properties, - arguments, - ) - }, - None => { - debug!("Worklet {} called before registration.", name); - return None; - }, - }; - - if let Ok(draw_result) = draw_result { - let webrender_image = WebRenderImageInfo { - width: draw_result.width, - height: draw_result.height, - key: draw_result.image_key, - }; - - for url in draw_result.missing_image_urls.into_iter() { - debug!("Requesting missing image URL {}.", url); - state.layout_context.get_webrender_image_for_url( - self.node, - url, - UsePlaceholder::No, - ); - } - Some(webrender_image) - } else { - None - } - } - - /// Adds the display items necessary to paint the background linear gradient of this fragment - /// to the appropriate section of the display list. - fn build_display_list_for_background_gradient( - &self, - state: &mut DisplayListBuildState, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - gradient: &Gradient, - style: &ComputedValues, - index: usize, - ) { - let placement = background::placement( - style.get_background(), - state.layout_context.shared_context().viewport_size(), - absolute_bounds, - None, - style.logical_border_width().to_physical(style.writing_mode), - self.border_padding.to_physical(self.style.writing_mode), - border::radii(absolute_bounds, style.get_border()), - index, - ); - - let placement = match placement { - Some(placement) => placement, - None => return, - }; - - state.clipping_and_scrolling_scope(|state| { - if !placement.clip_radii.is_zero() { - let clip_id = - state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii); - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - } - - let base = state.create_base_display_item( - placement.clip_rect, - self.node, - self.unique_id(), - get_cursor(style, Cursor::Default), - display_list_section, - ); - - let display_item = match gradient { - Gradient::Linear { - ref direction, - ref color_interpolation_method, - ref items, - ref flags, - compat_mode: _, - } => { - let (gradient, stops) = gradient::linear( - style, - placement.tile_size, - items, - *direction, - color_interpolation_method, - *flags, - ); - let item = webrender_api::GradientDisplayItem { - gradient, - bounds: placement.bounds.to_f32_px(), - common: items::empty_common_item_properties(), - tile_size: placement.tile_size.to_layout(), - tile_spacing: placement.tile_spacing.to_layout(), - }; - DisplayItem::Gradient(CommonDisplayItem::with_data(base, item, stops)) - }, - Gradient::Radial { - ref shape, - ref position, - ref color_interpolation_method, - ref items, - ref flags, - compat_mode: _, - } => { - let (gradient, stops) = gradient::radial( - style, - placement.tile_size, - items, - shape, - position, - color_interpolation_method, - *flags, - ); - let item = webrender_api::RadialGradientDisplayItem { - gradient, - bounds: placement.bounds.to_f32_px(), - common: items::empty_common_item_properties(), - tile_size: placement.tile_size.to_layout(), - tile_spacing: placement.tile_spacing.to_layout(), - }; - DisplayItem::RadialGradient(CommonDisplayItem::with_data(base, item, stops)) - }, - Gradient::Conic { .. } => return, - }; - state.add_display_item(display_item); - }); - } - - /// Adds the display items necessary to paint the box shadow of this fragment to the display - /// list if necessary. - fn build_display_list_for_box_shadow_if_applicable( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - display_list_section: DisplayListSection, - absolute_bounds: Rect<Au>, - clip: Rect<Au>, - ) { - // NB: According to CSS-BACKGROUNDS, box shadows render in *reverse* order (front to back). - for box_shadow in style.get_effects().box_shadow.0.iter().rev() { - let base = state.create_base_display_item( - clip, - self.node, - self.unique_id(), - get_cursor(style, Cursor::Default), - display_list_section, - ); - let border_radius = border::radii(absolute_bounds, style.get_border()); - state.add_display_item(DisplayItem::BoxShadow(CommonDisplayItem::new( - base, - webrender_api::BoxShadowDisplayItem { - common: items::empty_common_item_properties(), - box_bounds: absolute_bounds.to_layout(), - color: style.resolve_color(&box_shadow.base.color).to_layout(), - offset: LayoutVector2D::new( - box_shadow.base.horizontal.px(), - box_shadow.base.vertical.px(), - ), - blur_radius: box_shadow.base.blur.px(), - spread_radius: box_shadow.spread.px(), - border_radius, - clip_mode: if box_shadow.inset { - BoxShadowClipMode::Inset - } else { - BoxShadowClipMode::Outset - }, - }, - ))); - } - } - - /// Adds the display items necessary to paint the borders of this fragment to a display list if - /// necessary. - #[allow(clippy::too_many_arguments)] - fn build_display_list_for_borders_if_applicable( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - inline_info: Option<InlineNodeBorderInfo>, - border_painting_mode: BorderPaintingMode, - mut bounds: Rect<Au>, - display_list_section: DisplayListSection, - clip: Rect<Au>, - ) { - let mut border = style.logical_border_width(); - - if let Some(inline_info) = inline_info { - modify_border_width_for_inline_sides(&mut border, inline_info); - } - - match border_painting_mode { - BorderPaintingMode::Separate => {}, - BorderPaintingMode::Collapse(collapsed_borders) => { - collapsed_borders.adjust_border_widths_for_painting(&mut border) - }, - BorderPaintingMode::Hidden => return, - } - - let border_style_struct = style.get_border(); - let mut colors = SideOffsets2D::new( - border_style_struct.border_top_color.clone(), - border_style_struct.border_right_color.clone(), - border_style_struct.border_bottom_color.clone(), - border_style_struct.border_left_color.clone(), - ); - let mut border_style = SideOffsets2D::new( - border_style_struct.border_top_style, - border_style_struct.border_right_style, - border_style_struct.border_bottom_style, - border_style_struct.border_left_style, - ); - - if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode { - collapsed_borders.adjust_border_colors_and_styles_for_painting( - &mut colors, - &mut border_style, - style.writing_mode, - ); - } - - // If this border collapses, then we draw outside the boundaries we were given. - if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode { - collapsed_borders.adjust_border_bounds_for_painting(&mut bounds, style.writing_mode) - } - - // Append the border to the display list. - let base = state.create_base_display_item( - clip, - self.node, - self.unique_id(), - get_cursor(style, Cursor::Default), - display_list_section, - ); - - let border_radius = border::radii(bounds, border_style_struct); - let border_widths = border.to_physical(style.writing_mode); - - if self - .build_display_list_for_border_image( - state, - style, - base.clone(), - bounds, - &border_style_struct.border_image_source, - border_widths, - ) - .is_some() - { - return; - } - - if border_widths == SideOffsets2D::zero() { - return; - } - let details = BorderDetails::Normal(NormalBorder { - left: BorderSide { - color: style.resolve_color(&colors.left).to_layout(), - style: border_style.left.to_layout(), - }, - right: BorderSide { - color: style.resolve_color(&colors.right).to_layout(), - style: border_style.right.to_layout(), - }, - top: BorderSide { - color: style.resolve_color(&colors.top).to_layout(), - style: border_style.top.to_layout(), - }, - bottom: BorderSide { - color: style.resolve_color(&colors.bottom).to_layout(), - style: border_style.bottom.to_layout(), - }, - radius: border_radius, - do_aa: true, - }); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: bounds.to_layout(), - common: items::empty_common_item_properties(), - widths: border_widths.to_layout(), - details, - }, - Vec::new(), - ))); - } - - /// Add display item for image border. - /// - /// Returns `Some` if the addition was successful. - fn build_display_list_for_border_image( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - base: BaseDisplayItem, - bounds: Rect<Au>, - image: &Image, - border_width: UntypedSideOffsets2D<Au>, - ) -> Option<()> { - let border_style_struct = style.get_border(); - let border_image_outset = - border::image_outset(border_style_struct.border_image_outset, border_width); - let border_image_area = bounds.outer_rect(border_image_outset); - let border_image_size = border_image_area.size; - let border_image_width = border::image_width( - &border_style_struct.border_image_width, - border_width.to_layout(), - border_image_size, - ); - let border_image_repeat = &border_style_struct.border_image_repeat; - let border_image_fill = border_style_struct.border_image_slice.fill; - let border_image_slice = &border_style_struct.border_image_slice.offsets; - - let mut stops = Vec::new(); - let mut width = border_image_size.width.to_px() as u32; - let mut height = border_image_size.height.to_px() as u32; - let source = match image { - Image::Url(ref image_url) => { - let url = image_url.url()?; - let image = state.layout_context.get_webrender_image_for_url( - self.node, - url.clone().into(), - UsePlaceholder::No, - )?; - width = image.width; - height = image.height; - NinePatchBorderSource::Image(image.key?, ImageRendering::Auto) - }, - Image::PaintWorklet(ref paint_worklet) => { - let image = self.get_webrender_image_for_paint_worklet( - state, - style, - paint_worklet, - border_image_size, - )?; - width = image.width; - height = image.height; - NinePatchBorderSource::Image(image.key?, ImageRendering::Auto) - }, - Image::Gradient(ref gradient) => match **gradient { - Gradient::Linear { - ref direction, - ref color_interpolation_method, - ref items, - ref flags, - compat_mode: _, - } => { - let (wr_gradient, linear_stops) = gradient::linear( - style, - border_image_size, - items, - *direction, - color_interpolation_method, - *flags, - ); - stops = linear_stops; - NinePatchBorderSource::Gradient(wr_gradient) - }, - Gradient::Radial { - ref shape, - ref position, - ref color_interpolation_method, - ref items, - ref flags, - compat_mode: _, - } => { - let (wr_gradient, radial_stops) = gradient::radial( - style, - border_image_size, - items, - shape, - position, - color_interpolation_method, - *flags, - ); - stops = radial_stops; - NinePatchBorderSource::RadialGradient(wr_gradient) - }, - Gradient::Conic { .. } => return None, - }, - _ => return None, - }; - - // FIXME(emilio): WR expects device pixels here... somehow? - let size = euclid::Size2D::new(width as i32, height as i32); - let details = BorderDetails::NinePatch(NinePatchBorder { - source, - width: width as i32, - height: height as i32, - slice: border::image_slice(border_image_slice, size), - fill: border_image_fill, - repeat_horizontal: border_image_repeat.0.to_layout(), - repeat_vertical: border_image_repeat.1.to_layout(), - }); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: border_image_area.to_layout(), - common: items::empty_common_item_properties(), - widths: border_image_width, - details, - }, - stops, - ))); - Some(()) - } - - /// Adds the display items necessary to paint the outline of this fragment to the display list - /// if necessary. - fn build_display_list_for_outline_if_applicable( - &self, - state: &mut DisplayListBuildState, - style: &ComputedValues, - mut bounds: Rect<Au>, - clip: Rect<Au>, - ) { - use style::values::specified::outline::OutlineStyle; - - let width = style.get_outline().outline_width; - if width == Au(0) { - return; - } - - let outline_style = match style.get_outline().outline_style { - OutlineStyle::Auto => BorderStyle::Solid, - // FIXME(emilio): I don't think this border-style check is - // necessary, since border-style: none implies an outline-width of - // zero at computed value time. - OutlineStyle::BorderStyle(BorderStyle::None) => return, - OutlineStyle::BorderStyle(s) => s, - }; - - // Outlines are not accounted for in the dimensions of the border box, so adjust the - // absolute bounds. - let offset = width + Au::from(style.get_outline().outline_offset); - bounds = bounds.inflate(offset, offset); - - // Append the outline to the display list. - let color = style - .resolve_color(&style.get_outline().outline_color) - .to_layout(); - let base = state.create_base_display_item( - clip, - self.node, - self.unique_id(), - get_cursor(style, Cursor::Default), - DisplayListSection::Outlines, - ); - state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data( - base, - webrender_api::BorderDisplayItem { - bounds: bounds.to_layout(), - common: items::empty_common_item_properties(), - widths: SideOffsets2D::new_all_same(width).to_layout(), - details: BorderDetails::Normal(border::simple(color, outline_style.to_layout())), - }, - Vec::new(), - ))); - } - - /// Builds the display items necessary to paint the selection and/or caret for this fragment, - /// if any. - fn build_display_items_for_selection_if_necessary( - &self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - display_list_section: DisplayListSection, - ) { - let scanned_text_fragment_info = match self.specific { - SpecificFragmentInfo::ScannedText(ref scanned_text_fragment_info) => { - scanned_text_fragment_info - }, - _ => return, - }; - - // Draw a highlighted background if the text is selected. - // - // TODO: Allow non-text fragments to be selected too. - if scanned_text_fragment_info.selected() { - let style = self.selected_style(); - let background_color = style.resolve_color(&style.get_background().background_color); - let base = state.create_base_display_item( - stacking_relative_border_box, - self.node, - self.unique_id(), - get_cursor(&self.style, Cursor::Default), - display_list_section, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - common: items::empty_common_item_properties(), - color: PropertyBinding::Value(background_color.to_layout()), - bounds: stacking_relative_border_box.to_layout(), - }, - ))); - } - - // Draw a caret at the insertion point. - let insertion_point_index = match scanned_text_fragment_info.insertion_point { - Some(insertion_point_index) => insertion_point_index, - None => return, - }; - let range = Range::new( - scanned_text_fragment_info.range.begin(), - insertion_point_index - scanned_text_fragment_info.range.begin(), - ); - let advance = scanned_text_fragment_info.run.advance_for_range(&range); - - let insertion_point_bounds; - let cursor; - if !self.style.writing_mode.is_vertical() { - insertion_point_bounds = rect( - stacking_relative_border_box.origin.x + advance, - stacking_relative_border_box.origin.y, - INSERTION_POINT_LOGICAL_WIDTH, - stacking_relative_border_box.size.height, - ); - cursor = Cursor::Text; - } else { - insertion_point_bounds = rect( - stacking_relative_border_box.origin.x, - stacking_relative_border_box.origin.y + advance, - stacking_relative_border_box.size.width, - INSERTION_POINT_LOGICAL_WIDTH, - ); - cursor = Cursor::VerticalText; - }; - - let base = state.create_base_display_item( - insertion_point_bounds, - self.node, - self.unique_id(), - get_cursor(&self.style, cursor), - display_list_section, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - common: items::empty_common_item_properties(), - color: PropertyBinding::Value(self.style().get_inherited_text().color.to_layout()), - bounds: insertion_point_bounds.to_layout(), - }, - ))); - } - - /// Adds the display items for this fragment to the given display list. - /// - /// Arguments: - /// - /// * `state`: The display building state, including the display list currently - /// under construction and other metadata useful for constructing it. - /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. - /// * `clip`: The region to clip the display items to. - /// * `overflow_content_size`: The size of content associated with this fragment - /// that must have overflow handling applied to it. For a scrollable block - /// flow, it is expected that this is the size of the child boxes. - pub fn build_display_list( - &mut self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - border_painting_mode: BorderPaintingMode, - display_list_section: DisplayListSection, - clip: Rect<Au>, - overflow_content_size: Option<Size2D<Au>>, - ) { - let previous_clipping_and_scrolling = state.current_clipping_and_scrolling; - if let Some(index) = self.established_reference_frame { - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(index); - } - - self.restyle_damage.remove(ServoRestyleDamage::REPAINT); - self.build_display_list_no_damage( - state, - stacking_relative_border_box, - border_painting_mode, - display_list_section, - clip, - overflow_content_size, - ); - - state.current_clipping_and_scrolling = previous_clipping_and_scrolling; - } - - /// build_display_list, but don't update the restyle damage - /// - /// Must be paired with a self.restyle_damage.remove(REPAINT) somewhere - fn build_display_list_no_damage( - &self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - border_painting_mode: BorderPaintingMode, - display_list_section: DisplayListSection, - clip: Rect<Au>, - overflow_content_size: Option<Size2D<Au>>, - ) { - if self.style().get_inherited_box().visibility != Visibility::Visible { - return; - } - - // If this fragment takes up no space, we don't need to build any display items for it. - if self.has_non_invertible_transform_or_zero_scale() { - return; - } - - debug!( - "Fragment::build_display_list at rel={:?}, abs={:?}: {:?}", - self.border_box, stacking_relative_border_box, self - ); - - // Check the clip rect. If there's nothing to render at all, don't even construct display - // list items. - let empty_rect = !clip.intersects(&stacking_relative_border_box); - if self.is_primary_fragment() && !empty_rect { - // Add shadows, background, borders, and outlines, if applicable. - if let Some(ref inline_context) = self.inline_context { - for node in inline_context.nodes.iter().rev() { - self.build_display_list_for_background_if_applicable( - state, - &node.style, - display_list_section, - stacking_relative_border_box, - ); - - self.build_display_list_for_box_shadow_if_applicable( - state, - &node.style, - display_list_section, - stacking_relative_border_box, - clip, - ); - - self.build_display_list_for_borders_if_applicable( - state, - &node.style, - Some(InlineNodeBorderInfo { - is_first_fragment_of_element: node - .flags - .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT), - is_last_fragment_of_element: node - .flags - .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT), - }), - border_painting_mode, - stacking_relative_border_box, - display_list_section, - clip, - ); - - // FIXME(emilio): Why does outline not do the same width - // fixup as border? - self.build_display_list_for_outline_if_applicable( - state, - &node.style, - stacking_relative_border_box, - clip, - ); - } - } - - if !self.is_scanned_text_fragment() { - self.build_display_list_for_background_if_applicable( - state, - &self.style, - display_list_section, - stacking_relative_border_box, - ); - - self.build_display_list_for_box_shadow_if_applicable( - state, - &self.style, - display_list_section, - stacking_relative_border_box, - clip, - ); - - self.build_display_list_for_borders_if_applicable( - state, - &self.style, - /* inline_node_info = */ None, - border_painting_mode, - stacking_relative_border_box, - display_list_section, - clip, - ); - - self.build_display_list_for_outline_if_applicable( - state, - &self.style, - stacking_relative_border_box, - clip, - ); - } - } - - if self.is_primary_fragment() { - // Paint the selection point if necessary. Even an empty text fragment may have an - // insertion point, so we do this even if `empty_rect` is true. - self.build_display_items_for_selection_if_necessary( - state, - stacking_relative_border_box, - display_list_section, - ); - } - - if empty_rect { - return; - } - - debug!("Fragment::build_display_list: intersected. Adding display item..."); - - if let Some(content_size) = overflow_content_size { - // Create a transparent rectangle for hit-testing purposes that exists in front - // of this fragment's background but behind its content. This ensures that any - // hit tests inside the content box but not on actual content target the current - // scrollable ancestor. - let content_size = Rect::new(stacking_relative_border_box.origin, content_size); - let base = state.create_base_display_item_with_clipping_and_scrolling( - content_size, - self.node, - self.unique_id(), - // FIXME(emilio): Why does this ignore pointer-events? - get_cursor(&self.style, Cursor::Default).or(Some(Cursor::Default)), - display_list_section, - state.current_clipping_and_scrolling, - ); - state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new( - base, - webrender_api::RectangleDisplayItem { - common: items::empty_common_item_properties(), - color: PropertyBinding::Value(ColorF::TRANSPARENT), - bounds: content_size.to_layout(), - }, - ))); - } - - // Create special per-fragment-type display items. - state.clipping_and_scrolling_scope(|state| { - self.build_fragment_type_specific_display_items( - state, - stacking_relative_border_box, - clip, - ); - }); - } - - /// A helper method that `build_display_list` calls to create per-fragment-type display items. - fn build_fragment_type_specific_display_items( - &self, - state: &mut DisplayListBuildState, - stacking_relative_border_box: Rect<Au>, - clip: Rect<Au>, - ) { - // Compute the context box position relative to the parent stacking context. - let stacking_relative_content_box = - self.stacking_relative_content_box(stacking_relative_border_box); - - let create_base_display_item = |state: &mut DisplayListBuildState| { - // Adjust the clipping region as necessary to account for `border-radius`. - let radii = - build_border_radius_for_inner_rect(stacking_relative_border_box, &self.style); - - if !radii.is_zero() { - // This is already calculated inside of build_border_radius_for_inner_rect(), it would be - // nice if it were only calculated once. - let border_widths = self - .style - .logical_border_width() - .to_physical(self.style.writing_mode); - let clip_id = state.add_late_clip_node( - stacking_relative_border_box - .inner_rect(border_widths) - .to_layout(), - radii, - ); - state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - } - - state.create_base_display_item( - stacking_relative_border_box, - self.node, - self.unique_id(), - get_cursor(&self.style, Cursor::Default), - DisplayListSection::Content, - ) - }; - - match self.specific { - SpecificFragmentInfo::TruncatedFragment(ref truncated_fragment) - if truncated_fragment.text_info.is_some() => - { - let text_fragment = truncated_fragment.text_info.as_ref().unwrap(); - // Create the main text display item. - self.build_display_list_for_text_fragment( - state, - text_fragment, - stacking_relative_content_box, - &self.style.get_inherited_text().text_shadow.0, - clip, - ); - }, - SpecificFragmentInfo::ScannedText(ref text_fragment) => { - // Create the main text display item. - self.build_display_list_for_text_fragment( - state, - text_fragment, - stacking_relative_content_box, - &self.style.get_inherited_text().text_shadow.0, - clip, - ); - }, - 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(_) => {}, - SpecificFragmentInfo::Iframe(ref fragment_info) => { - if !stacking_relative_content_box.is_empty() { - let Some(browsing_context_id) = fragment_info.browsing_context_id else { - return warn!("No browsing context id for iframe."); - }; - let Some(pipeline_id) = fragment_info.pipeline_id else { - return warn!("No pipeline id for iframe."); - }; - - let base = create_base_display_item(state); - let bounds = stacking_relative_content_box.to_layout(); - - state.iframe_sizes.insert( - browsing_context_id, - IFrameSize { - browsing_context_id, - pipeline_id, - // XXXjdm: This sleight-of-hand to convert LayoutRect -> Size2D<CSSPixel> - // looks bogus. - size: euclid::Size2D::new(bounds.size().width, bounds.size().height), - }, - ); - - 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 item = DisplayItem::Iframe(Box::new(IframeDisplayItem { - base, - bounds, - iframe: pipeline_id, - })); - state.add_display_item(item); - } - }, - SpecificFragmentInfo::Image(ref image_fragment) => { - // Place the image into the display list. - if let Some(ref image) = image_fragment.image { - if let Some(id) = image.id { - let base = create_base_display_item(state); - state.add_image_item( - base, - webrender_api::ImageDisplayItem { - bounds: stacking_relative_content_box.to_layout(), - common: items::empty_common_item_properties(), - image_key: id, - image_rendering: self - .style - .get_inherited_box() - .image_rendering - .to_layout(), - alpha_type: webrender_api::AlphaType::PremultipliedAlpha, - color: webrender_api::ColorF::WHITE, - }, - ); - } - } - }, - SpecificFragmentInfo::Media(ref fragment_info) => { - if let Some(ref media_frame) = fragment_info.current_frame { - let base = create_base_display_item(state); - state.add_image_item( - base, - webrender_api::ImageDisplayItem { - bounds: stacking_relative_content_box.to_layout(), - common: items::empty_common_item_properties(), - image_key: media_frame.image_key, - image_rendering: ImageRendering::Auto, - alpha_type: webrender_api::AlphaType::PremultipliedAlpha, - color: webrender_api::ColorF::WHITE, - }, - ); - } - }, - SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => { - if canvas_fragment_info.dom_width == Au(0) || - canvas_fragment_info.dom_height == Au(0) - { - return; - } - - let image_key = match canvas_fragment_info.source { - CanvasFragmentSource::WebGL(image_key) => image_key, - CanvasFragmentSource::WebGPU(image_key) => image_key, - CanvasFragmentSource::Image((image_key, canvas_id, ref ipc_renderer)) => { - let ipc_renderer = ipc_renderer.lock().unwrap(); - let (sender, receiver) = ipc_channel::ipc::channel().unwrap(); - ipc_renderer - .send(CanvasMsg::FromLayout( - FromLayoutMsg::UpdateImage(sender), - canvas_id, - )) - .unwrap(); - receiver.recv().unwrap(); - image_key - }, - CanvasFragmentSource::Empty => return, - }; - - let base = create_base_display_item(state); - let display_item = webrender_api::ImageDisplayItem { - bounds: stacking_relative_border_box.to_layout(), - common: items::empty_common_item_properties(), - image_key, - image_rendering: ImageRendering::Auto, - alpha_type: webrender_api::AlphaType::PremultipliedAlpha, - color: webrender_api::ColorF::WHITE, - }; - - state.add_image_item(base, display_item); - }, - SpecificFragmentInfo::UnscannedText(_) => { - panic!("Shouldn't see unscanned fragments here.") - }, - SpecificFragmentInfo::TableColumn(_) => { - panic!("Shouldn't see table column fragments here.") - }, - } - } - - /// Creates a stacking context for associated fragment. - fn create_stacking_context( - &self, - id: StackingContextId, - base_flow: &BaseFlow, - context_type: StackingContextType, - established_reference_frame: Option<ClipScrollNodeIndex>, - parent_clipping_and_scrolling: ClippingAndScrolling, - ) -> StackingContext { - let border_box = self.stacking_relative_border_box( - &base_flow.stacking_relative_position, - &base_flow - .early_absolute_position_info - .relative_containing_block_size, - base_flow - .early_absolute_position_info - .relative_containing_block_mode, - CoordinateSystem::Parent, - ); - // First, compute the offset of our border box (including relative positioning) - // from our flow origin, since that is what `BaseFlow::overflow` is relative to. - let border_box_offset = border_box - .translate(-base_flow.stacking_relative_position) - .origin; - // Then, using that, compute our overflow region relative to our border box. - let overflow = base_flow - .overflow - .paint - .translate(-border_box_offset.to_vector()); - - // Create the filter pipeline. - let current_color = self.style().clone_color(); - let effects = self.style().get_effects(); - let mut filters: Vec<FilterOp> = effects - .filter - .0 - .iter() - .map(|filter| FilterToLayout::to_layout(filter, ¤t_color)) - .collect(); - if effects.opacity != 1.0 { - filters.push(FilterOp::Opacity(effects.opacity.into(), effects.opacity)); - } - - StackingContext::new( - id, - context_type, - border_box.to_layout(), - overflow.to_layout(), - self.effective_z_index(), - self.style().get_box()._servo_top_layer, - filters, - self.style().get_effects().mix_blend_mode.to_layout(), - self.transform_matrix(&border_box), - self.style().get_used_transform_style().to_layout(), - self.perspective_matrix(&border_box), - parent_clipping_and_scrolling, - established_reference_frame, - ) - } - - /// Creates the text display item for one text fragment. This can be called multiple times for - /// one fragment if there are text shadows. - /// - /// `text_shadow` will be `Some` if this is rendering a shadow. - fn build_display_list_for_text_fragment( - &self, - state: &mut DisplayListBuildState, - text_fragment: &ScannedTextFragmentInfo, - stacking_relative_content_box: Rect<Au>, - text_shadows: &[SimpleShadow], - clip: Rect<Au>, - ) { - // NB: The order for painting text components (CSS Text Decoration Module Level 3) is: - // shadows, underline, overline, text, text-emphasis, and then line-through. - - // TODO(emilio): Allow changing more properties by ::selection - // Paint the text with the color as described in its styling. - let text_color = if text_fragment.selected() { - self.selected_style().get_inherited_text().color - } else { - self.style().get_inherited_text().color - }; - - // Determine the orientation and cursor to use. - let (_orientation, cursor) = if self.style.writing_mode.is_vertical() { - // TODO: Distinguish between 'sideways-lr' and 'sideways-rl' writing modes in CSS - // Writing Modes Level 4. - (TextOrientation::SidewaysRight, Cursor::VerticalText) - } else { - (TextOrientation::Upright, Cursor::Text) - }; - - // Compute location of the baseline. - // - // FIXME(pcwalton): Get the real container size. - let container_size = Size2D::zero(); - let metrics = &text_fragment.run.font_metrics; - let baseline_origin = stacking_relative_content_box.origin + - LogicalPoint::new(self.style.writing_mode, Au(0), metrics.ascent) - .to_physical(self.style.writing_mode, container_size) - .to_vector(); - - // Base item for all text/shadows - let base = state.create_base_display_item( - clip, - self.node, - self.unique_id(), - get_cursor(&self.style, cursor), - DisplayListSection::Content, - ); - - // NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front - // to back). - - // Shadows - for shadow in text_shadows.iter().rev() { - state.add_display_item(DisplayItem::PushTextShadow(Box::new( - PushTextShadowDisplayItem { - base: base.clone(), - shadow: webrender_api::Shadow { - offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()), - color: self.style.resolve_color(&shadow.color).to_layout(), - blur_radius: shadow.blur.px(), - }, - }, - ))); - } - - // Create display items for text decorations. - let text_decorations = self.style().get_inherited_text().text_decorations_in_effect; - let dppx = state - .layout_context - .style_context - .device_pixel_ratio() - .get(); - let round_to_nearest_device_pixel = |value: Au| -> Au { - // Round to the nearest integer device pixel, ensuring at least one device pixel. - Au::from_f32_px((value.to_f32_px() * dppx).round().max(1.0) / dppx) - }; - - let logical_stacking_relative_content_box = LogicalRect::from_physical( - self.style.writing_mode, - stacking_relative_content_box, - container_size, - ); - - // Underline - if text_decorations.underline { - let mut stacking_relative_box = logical_stacking_relative_content_box; - stacking_relative_box.start.b = logical_stacking_relative_content_box.start.b + - metrics.ascent - - metrics.underline_offset; - stacking_relative_box.size.block = - round_to_nearest_device_pixel(metrics.underline_size); - self.build_display_list_for_text_decoration( - state, - &text_color, - &stacking_relative_box, - clip, - ); - } - - // Overline - if text_decorations.overline { - let mut stacking_relative_box = logical_stacking_relative_content_box; - stacking_relative_box.size.block = - round_to_nearest_device_pixel(metrics.underline_size); - self.build_display_list_for_text_decoration( - state, - &text_color, - &stacking_relative_box, - clip, - ); - } - - // Text - let (largest_advance, mut glyphs) = convert_text_run_to_glyphs( - text_fragment.run.clone(), - text_fragment.range, - baseline_origin, - ); - - let indexable_text = IndexableTextItem { - origin: stacking_relative_content_box.origin, - text_run: text_fragment.run.clone(), - range: text_fragment.range, - baseline_origin, - }; - state.indexable_text.insert(self.node, indexable_text); - - // FIXME(mrobinson, #30313): This is a serious hack to enable a WebRender upgrade. - // Servo is not calculating glyph boundaries and is instead relying on the - // measured size of the content box here -- which is based on the positioning - // of the text. The issue is that glyphs can extend beyond the boundaries - // established by their brush origin and advance. Servo should be measuring - // the ink boundary rectangle based on the brush origin and the glyph extents - // instead. - // - // We don't yet have that information here, so in the meantime simply expand - // the boundary rectangle of the text by the largest character advance of the - // painted text run in all directions. This is used as a heuristic for a - // reasonable amount of "fudge" space to include the entire text run. - let inflated_bounds = stacking_relative_content_box - .inflate(largest_advance, largest_advance) - .to_layout(); - - // Process glyphs in chunks to avoid overflowing WebRender's internal limits (#17230). - while !glyphs.is_empty() { - let mut rest_of_glyphs = vec![]; - if glyphs.len() > MAX_GLYPHS_PER_TEXT_RUN { - rest_of_glyphs = glyphs[MAX_GLYPHS_PER_TEXT_RUN..].to_vec(); - glyphs.truncate(MAX_GLYPHS_PER_TEXT_RUN); - }; - - state.add_display_item(DisplayItem::Text(CommonDisplayItem::with_data( - base.clone(), - webrender_api::TextDisplayItem { - bounds: inflated_bounds, - common: items::empty_common_item_properties(), - font_key: text_fragment.run.font_key, - color: text_color.to_layout(), - glyph_options: None, - ref_frame_offset: LayoutVector2D::zero(), - }, - glyphs, - ))); - - glyphs = rest_of_glyphs; - } - - // TODO(#17715): emit text-emphasis marks here. - // (just push another TextDisplayItem?) - - // Line-Through - if text_decorations.line_through { - let mut stacking_relative_box = logical_stacking_relative_content_box; - stacking_relative_box.start.b = - stacking_relative_box.start.b + metrics.ascent - metrics.strikeout_offset; - stacking_relative_box.size.block = - round_to_nearest_device_pixel(metrics.strikeout_size); - self.build_display_list_for_text_decoration( - state, - &text_color, - &stacking_relative_box, - clip, - ); - } - - // Pop all the PushTextShadows - if !text_shadows.is_empty() { - state.add_display_item(DisplayItem::PopAllTextShadows(Box::new( - PopAllTextShadowsDisplayItem { base }, - ))); - } - } - - /// Creates the display item for a text decoration: underline, overline, or line-through. - fn build_display_list_for_text_decoration( - &self, - state: &mut DisplayListBuildState, - color: &AbsoluteColor, - stacking_relative_box: &LogicalRect<Au>, - clip: Rect<Au>, - ) { - // FIXME(pcwalton, #2795): Get the real container size. - let container_size = Size2D::zero(); - let stacking_relative_box = - stacking_relative_box.to_physical(self.style.writing_mode, container_size); - let base = state.create_base_display_item( - clip, - self.node, - self.unique_id(), - get_cursor(&self.style, Cursor::Default), - DisplayListSection::Content, - ); - - // TODO(gw): Use a better estimate for wavy line thickness. - let area = stacking_relative_box.to_layout(); - let wavy_line_thickness = (0.33 * area.size().height).ceil(); - state.add_display_item(DisplayItem::Line(CommonDisplayItem::new( - base, - webrender_api::LineDisplayItem { - common: items::empty_common_item_properties(), - area, - orientation: webrender_api::LineOrientation::Horizontal, - wavy_line_thickness, - color: color.to_layout(), - style: LineStyle::Solid, - }, - ))); - } - - fn unique_id(&self) -> u64 { - let fragment_type = self.fragment_type(); - let id = self.node.id(); - combine_id_with_fragment_type(id, fragment_type) - } - - fn fragment_type(&self) -> FragmentType { - self.pseudo.fragment_type() - } -} - -bitflags! { - #[derive(Clone, Copy)] - pub struct StackingContextCollectionFlags: u8 { - /// This flow never establishes a containing block. - const POSITION_NEVER_CREATES_CONTAINING_BLOCK = 0b001; - /// This flow never creates a ClipScrollNode. - const NEVER_CREATES_CLIP_SCROLL_NODE = 0b010; - /// This flow never creates a stacking context. - const NEVER_CREATES_STACKING_CONTEXT = 0b100; - } -} - -/// This structure manages ensuring that modification to StackingContextCollectionState is -/// only temporary. It's useful for moving recursively down the flow tree and ensuring -/// that the state is restored for siblings. To use this structure, we must call -/// SavedStackingContextCollectionState::restore in order to restore the state. -/// TODO(mrobinson): It would be nice to use RAII here to avoid having to call restore. -pub struct SavedStackingContextCollectionState { - stacking_context_id: StackingContextId, - real_stacking_context_id: StackingContextId, - parent_reference_frame_id: ClipScrollNodeIndex, - clipping_and_scrolling: ClippingAndScrolling, - containing_block_clipping_and_scrolling: ClippingAndScrolling, - clips_pushed: usize, - containing_block_clips_pushed: usize, - stacking_relative_content_box: Rect<Au>, -} - -impl SavedStackingContextCollectionState { - fn new(state: &mut StackingContextCollectionState) -> SavedStackingContextCollectionState { - SavedStackingContextCollectionState { - stacking_context_id: state.current_stacking_context_id, - real_stacking_context_id: state.current_real_stacking_context_id, - parent_reference_frame_id: state.current_parent_reference_frame_id, - clipping_and_scrolling: state.current_clipping_and_scrolling, - containing_block_clipping_and_scrolling: state.containing_block_clipping_and_scrolling, - clips_pushed: 0, - containing_block_clips_pushed: 0, - stacking_relative_content_box: state.parent_stacking_relative_content_box, - } - } - - fn switch_to_containing_block_clip(&mut self, state: &mut StackingContextCollectionState) { - let clip = state - .containing_block_clip_stack - .last() - .cloned() - .unwrap_or_else(MaxRect::max_rect); - state.clip_stack.push(clip); - self.clips_pushed += 1; - } - - fn restore(self, state: &mut StackingContextCollectionState) { - state.current_stacking_context_id = self.stacking_context_id; - state.current_real_stacking_context_id = self.real_stacking_context_id; - state.current_parent_reference_frame_id = self.parent_reference_frame_id; - state.current_clipping_and_scrolling = self.clipping_and_scrolling; - state.containing_block_clipping_and_scrolling = - self.containing_block_clipping_and_scrolling; - state.parent_stacking_relative_content_box = self.stacking_relative_content_box; - - let truncate_length = state.clip_stack.len() - self.clips_pushed; - state.clip_stack.truncate(truncate_length); - - let truncate_length = - state.containing_block_clip_stack.len() - self.containing_block_clips_pushed; - state.containing_block_clip_stack.truncate(truncate_length); - } - - fn push_clip( - &mut self, - state: &mut StackingContextCollectionState, - mut clip: Rect<Au>, - positioning: StylePosition, - ) { - if positioning != StylePosition::Fixed { - if let Some(old_clip) = state.clip_stack.last() { - clip = old_clip.intersection(&clip).unwrap_or_else(Rect::zero); - } - } - - state.clip_stack.push(clip); - self.clips_pushed += 1; - - if StylePosition::Absolute == positioning { - state.containing_block_clip_stack.push(clip); - self.containing_block_clips_pushed += 1; - } - } -} - -impl BlockFlow { - fn transform_clip_to_coordinate_space( - &mut self, - state: &mut StackingContextCollectionState, - preserved_state: &mut SavedStackingContextCollectionState, - ) { - if state.clip_stack.is_empty() { - return; - } - let border_box = self.stacking_relative_border_box(CoordinateSystem::Parent); - let transform = match self.fragment.transform_matrix(&border_box) { - Some(transform) => transform, - None => return, - }; - - let perspective = self - .fragment - .perspective_matrix(&border_box) - .unwrap_or(LayoutTransform::identity()); - let transform = perspective.then(&transform).inverse(); - - let origin = border_box.origin; - let transform_clip = |clip: Rect<Au>| { - if clip == Rect::max_rect() { - return clip; - } - - match transform { - Some(transform) if transform.m13 != 0.0 || transform.m23 != 0.0 => { - // We cannot properly handle perspective transforms, because there may be a - // situation where an element is transformed from outside the clip into the - // clip region. Here we don't have enough information to detect when that is - // happening. For the moment we just punt on trying to optimize the display - // list for those cases. - Rect::max_rect() - }, - Some(transform) => { - let clip = rect( - (clip.origin.x - origin.x).to_f32_px(), - (clip.origin.y - origin.y).to_f32_px(), - clip.size.width.to_f32_px(), - clip.size.height.to_f32_px(), - ); - - let clip = transform.outer_transformed_rect(&clip).unwrap(); - - rect( - Au::from_f32_px(clip.origin.x), - Au::from_f32_px(clip.origin.y), - Au::from_f32_px(clip.size.width), - Au::from_f32_px(clip.size.height), - ) - }, - None => Rect::zero(), - } - }; - - if let Some(clip) = state.clip_stack.last().cloned() { - state.clip_stack.push(transform_clip(clip)); - preserved_state.clips_pushed += 1; - } - - if let Some(clip) = state.containing_block_clip_stack.last().cloned() { - state.containing_block_clip_stack.push(transform_clip(clip)); - preserved_state.containing_block_clips_pushed += 1; - } - } - - /// Returns true if this fragment may establish a reference frame and this block - /// creates a stacking context. Both are necessary in order to establish a reference - /// frame. - fn is_reference_frame(&self, context_type: Option<StackingContextType>) -> bool { - match context_type { - Some(StackingContextType::Real) => self.fragment.can_establish_reference_frame(), - _ => false, - } - } - - pub fn collect_stacking_contexts_for_block( - &mut self, - state: &mut StackingContextCollectionState, - flags: StackingContextCollectionFlags, - ) { - // This block flow produces no stacking contexts if it takes up no space. - if self.has_non_invertible_transform_or_zero_scale() { - return; - } - - let mut preserved_state = SavedStackingContextCollectionState::new(state); - - let stacking_context_type = self.stacking_context_type(flags); - self.base.stacking_context_id = match stacking_context_type { - None => state.current_stacking_context_id, - Some(sc_type) => state.allocate_stacking_context_info(sc_type), - }; - state.current_stacking_context_id = self.base.stacking_context_id; - - if stacking_context_type == Some(StackingContextType::Real) { - state.current_real_stacking_context_id = self.base.stacking_context_id; - } - - let established_reference_frame = if self.is_reference_frame(stacking_context_type) { - // WebRender currently creates reference frames automatically, so just add - // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame. - Some(state.add_clip_scroll_node(ClipScrollNode::placeholder())) - } else { - None - }; - - // We are getting the id of the scroll root that contains us here, not the id of - // any scroll root that we create. If we create a scroll root, its index will be - // stored in state.current_clipping_and_scrolling. If we create a stacking context, - // we don't want it to be contained by its own scroll root. - let containing_clipping_and_scrolling = self.setup_clipping_for_block( - state, - &mut preserved_state, - stacking_context_type, - established_reference_frame, - flags, - ); - - let creates_containing_block = !flags - .contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK); - let abspos_containing_block = established_reference_frame.is_some() || - (creates_containing_block && self.positioning() != StylePosition::Static); - if abspos_containing_block { - state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling; - } - - match stacking_context_type { - None => self.base.collect_stacking_contexts_for_children(state), - Some(StackingContextType::Real) => { - self.create_real_stacking_context_for_block( - preserved_state.stacking_context_id, - containing_clipping_and_scrolling, - established_reference_frame, - state, - ); - }, - Some(stacking_context_type) => { - self.create_pseudo_stacking_context_for_block( - stacking_context_type, - preserved_state.stacking_context_id, - containing_clipping_and_scrolling, - state, - ); - }, - } - - preserved_state.restore(state); - } - - fn setup_clipping_for_block( - &mut self, - state: &mut StackingContextCollectionState, - preserved_state: &mut SavedStackingContextCollectionState, - stacking_context_type: Option<StackingContextType>, - established_reference_frame: Option<ClipScrollNodeIndex>, - flags: StackingContextCollectionFlags, - ) -> ClippingAndScrolling { - // If this block is absolutely positioned, we should be clipped and positioned by - // the scroll root of our nearest ancestor that establishes a containing block. - let containing_clipping_and_scrolling = match self.positioning() { - StylePosition::Absolute => { - preserved_state.switch_to_containing_block_clip(state); - state.current_clipping_and_scrolling = - state.containing_block_clipping_and_scrolling; - state.containing_block_clipping_and_scrolling - }, - StylePosition::Fixed => { - // If we are a fixed positioned stacking context, we want to be scrolled by - // our reference frame instead of the clip scroll node that we are inside. - preserved_state.push_clip(state, Rect::max_rect(), StylePosition::Fixed); - state.current_clipping_and_scrolling.scrolling = - state.current_parent_reference_frame_id; - state.current_clipping_and_scrolling - }, - _ => state.current_clipping_and_scrolling, - }; - self.base.clipping_and_scrolling = Some(containing_clipping_and_scrolling); - - if let Some(reference_frame_index) = established_reference_frame { - let clipping_and_scrolling = ClippingAndScrolling::simple(reference_frame_index); - state.current_clipping_and_scrolling = clipping_and_scrolling; - self.base.clipping_and_scrolling = Some(clipping_and_scrolling); - } - - let stacking_relative_border_box = if self.fragment.establishes_stacking_context() { - self.stacking_relative_border_box(CoordinateSystem::Own) - } else { - self.stacking_relative_border_box(CoordinateSystem::Parent) - }; - - if stacking_context_type == Some(StackingContextType::Real) { - self.transform_clip_to_coordinate_space(state, preserved_state); - } - - if !flags.contains(StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE) { - self.setup_clip_scroll_node_for_position(state, stacking_relative_border_box); - self.setup_clip_scroll_node_for_overflow(state, stacking_relative_border_box); - self.setup_clip_scroll_node_for_css_clip( - state, - preserved_state, - stacking_relative_border_box, - ); - } - self.base.clip = state - .clip_stack - .last() - .cloned() - .unwrap_or_else(Rect::max_rect); - - // We keep track of our position so that any stickily positioned elements can - // properly determine the extent of their movement relative to scrolling containers. - if !flags.contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK) - { - let border_box = if self.fragment.establishes_stacking_context() { - stacking_relative_border_box - } else { - self.stacking_relative_border_box(CoordinateSystem::Own) - }; - state.parent_stacking_relative_content_box = - self.fragment.stacking_relative_content_box(border_box) - } - - match self.positioning() { - StylePosition::Absolute | StylePosition::Relative | StylePosition::Fixed => { - state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling - }, - _ => {}, - } - - containing_clipping_and_scrolling - } - - fn setup_clip_scroll_node_for_position( - &mut self, - state: &mut StackingContextCollectionState, - border_box: Rect<Au>, - ) { - if self.positioning() != StylePosition::Sticky { - return; - } - - let sticky_position = self.sticky_position(); - if sticky_position.left == MaybeAuto::Auto && - sticky_position.right == MaybeAuto::Auto && - sticky_position.top == MaybeAuto::Auto && - sticky_position.bottom == MaybeAuto::Auto - { - return; - } - - // Since position: sticky elements always establish a stacking context, we will - // have previously calculated our border box in our own coordinate system. In - // order to properly calculate max offsets we need to compare our size and - // position in our parent's coordinate system. - let border_box_in_parent = self.stacking_relative_border_box(CoordinateSystem::Parent); - let margins = self - .fragment - .margin - .to_physical(self.fragment.style.writing_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.as_option().map(|v| v.to_f32_px()), - sticky_position.right.as_option().map(|v| v.to_f32_px()), - sticky_position.bottom.as_option().map(|v| v.to_f32_px()), - sticky_position.left.as_option().map(|v| v.to_f32_px()), - ), - vertical_offset_bounds, - horizontal_offset_bounds, - }; - - let new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode { - parent_index: self.clipping_and_scrolling().scrolling, - clip: ClippingRegion::from_rect(border_box.to_layout()), - content_rect: LayoutRect::zero(), - node_type: ClipScrollNodeType::StickyFrame(sticky_frame_data), - scroll_node_id: None, - clip_chain_id: None, - }); - - let new_clipping_and_scrolling = ClippingAndScrolling::simple(new_clip_scroll_index); - self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling); - state.current_clipping_and_scrolling = new_clipping_and_scrolling; - } - - fn setup_clip_scroll_node_for_overflow( - &mut self, - state: &mut StackingContextCollectionState, - border_box: Rect<Au>, - ) { - if !self.overflow_style_may_require_clip_scroll_node() { - return; - } - - let content_box = self.fragment.stacking_relative_content_box(border_box); - let has_scrolling_overflow = self.base.overflow.scroll.origin != Point2D::zero() || - self.base.overflow.scroll.size.width > content_box.size.width || - self.base.overflow.scroll.size.height > content_box.size.height || - StyleOverflow::Hidden == self.fragment.style.get_box().overflow_x || - StyleOverflow::Hidden == self.fragment.style.get_box().overflow_y; - - self.mark_scrolling_overflow(has_scrolling_overflow); - if !has_scrolling_overflow { - return; - } - - let border_widths = self - .fragment - .style - .logical_border_width() - .to_physical(self.fragment.style.writing_mode); - let clip_rect = border_box.inner_rect(border_widths); - - let clip = ClippingRegion::from_rect(clip_rect.to_layout()); - let radii = build_border_radius_for_inner_rect(border_box, &self.fragment.style); - if !radii.is_zero() { - let node = ClipScrollNode::rounded( - clip_rect.to_layout(), - radii, - state.current_clipping_and_scrolling.scrolling, - ); - let clip_id = state.add_clip_scroll_node(node); - let new_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id); - self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling); - state.current_clipping_and_scrolling = new_clipping_and_scrolling; - } - - let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size; - let content_size = Size2D::new(content_size.x, content_size.y); - - let external_id = ExternalScrollId(self.fragment.unique_id(), state.pipeline_id.into()); - let new_clip_scroll_index = state.add_clip_scroll_node(ClipScrollNode { - parent_index: self.clipping_and_scrolling().scrolling, - clip, - content_rect: Rect::new(content_box.origin, content_size).to_layout(), - node_type: ClipScrollNodeType::ScrollFrame( - AxesScrollSensitivity { - x: self.fragment.style.get_box().overflow_x.into(), - y: self.fragment.style.get_box().overflow_y.into(), - }, - external_id, - ), - scroll_node_id: None, - clip_chain_id: None, - }); - - let new_clipping_and_scrolling = ClippingAndScrolling::simple(new_clip_scroll_index); - self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling); - state.current_clipping_and_scrolling = new_clipping_and_scrolling; - } - - /// Adds a scroll root for a block to take the `clip` property into account - /// per CSS 2.1 § 11.1.2. - fn setup_clip_scroll_node_for_css_clip( - &mut self, - state: &mut StackingContextCollectionState, - preserved_state: &mut SavedStackingContextCollectionState, - stacking_relative_border_box: Rect<Au>, - ) { - // Account for `clip` per CSS 2.1 § 11.1.2. - let style_clip_rect = match self.fragment.style().get_effects().clip { - ClipRectOrAuto::Rect(ref r) => r, - ClipRectOrAuto::Auto => 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_rect = style_clip_rect.for_border_rect(stacking_relative_border_box); - preserved_state.push_clip(state, clip_rect, self.positioning()); - - let new_index = state.add_clip_scroll_node(ClipScrollNode { - parent_index: self.clipping_and_scrolling().scrolling, - clip: ClippingRegion::from_rect(clip_rect.to_layout()), - content_rect: LayoutRect::zero(), // content_rect isn't important for clips. - node_type: ClipScrollNodeType::Clip(ClipType::Rect), - scroll_node_id: None, - clip_chain_id: None, - }); - - let new_indices = ClippingAndScrolling::new(new_index, new_index); - self.base.clipping_and_scrolling = Some(new_indices); - state.current_clipping_and_scrolling = new_indices; - } - - fn create_pseudo_stacking_context_for_block( - &mut self, - stacking_context_type: StackingContextType, - parent_stacking_context_id: StackingContextId, - parent_clipping_and_scrolling: ClippingAndScrolling, - state: &mut StackingContextCollectionState, - ) { - let new_context = self.fragment.create_stacking_context( - self.base.stacking_context_id, - &self.base, - stacking_context_type, - None, - parent_clipping_and_scrolling, - ); - state.add_stacking_context(parent_stacking_context_id, new_context); - - self.base.collect_stacking_contexts_for_children(state); - - let children = state - .stacking_context_info - .get_mut(&self.base.stacking_context_id) - .map(|info| info.take_children()); - if let Some(children) = children { - for child in children { - if child.context_type == StackingContextType::PseudoFloat { - state.add_stacking_context(self.base.stacking_context_id, child); - } else { - state.add_stacking_context(parent_stacking_context_id, child); - } - } - } - } - - fn create_real_stacking_context_for_block( - &mut self, - parent_stacking_context_id: StackingContextId, - parent_clipping_and_scrolling: ClippingAndScrolling, - established_reference_frame: Option<ClipScrollNodeIndex>, - state: &mut StackingContextCollectionState, - ) { - let stacking_context = self.fragment.create_stacking_context( - self.base.stacking_context_id, - &self.base, - StackingContextType::Real, - established_reference_frame, - parent_clipping_and_scrolling, - ); - - state.add_stacking_context(parent_stacking_context_id, stacking_context); - self.base.collect_stacking_contexts_for_children(state); - } - - pub fn build_display_list_for_block_no_damage( - &self, - state: &mut DisplayListBuildState, - border_painting_mode: BorderPaintingMode, - ) { - let background_border_section = self.background_border_section(); - - state.processing_scrolling_overflow_element = self.has_scrolling_overflow(); - - let content_size = if state.processing_scrolling_overflow_element { - let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size; - Some(Size2D::new(content_size.x, content_size.y)) - } else { - None - }; - - let stacking_relative_border_box = self - .base - .stacking_relative_border_box_for_display_list(&self.fragment); - // Add the box that starts the block context. - self.fragment.build_display_list_no_damage( - state, - stacking_relative_border_box, - border_painting_mode, - background_border_section, - self.base.clip, - content_size, - ); - - state.processing_scrolling_overflow_element = false; - } - - pub fn build_display_list_for_block( - &mut self, - state: &mut DisplayListBuildState, - border_painting_mode: BorderPaintingMode, - ) { - self.fragment - .restyle_damage - .remove(ServoRestyleDamage::REPAINT); - self.build_display_list_for_block_no_damage(state, border_painting_mode); - } - - pub fn build_display_list_for_background_if_applicable_with_background( - &self, - state: &mut DisplayListBuildState, - background: &style_structs::Background, - background_color: AbsoluteColor, - ) { - let stacking_relative_border_box = self - .base - .stacking_relative_border_box_for_display_list(&self.fragment); - let background_border_section = self.background_border_section(); - - self.fragment - .build_display_list_for_background_if_applicable_with_background( - state, - self.fragment.style(), - background, - background_color, - background_border_section, - stacking_relative_border_box, - ) - } - - #[inline] - fn stacking_context_type( - &self, - flags: StackingContextCollectionFlags, - ) -> Option<StackingContextType> { - if flags.contains(StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT) { - return None; - } - - if self.fragment.establishes_stacking_context() { - return Some(StackingContextType::Real); - } - - if self - .base - .flags - .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED) - { - return Some(StackingContextType::PseudoPositioned); - } - - if self.fragment.style.get_box().position != StylePosition::Static { - return Some(StackingContextType::PseudoPositioned); - } - - if self.base.flags.is_float() { - return Some(StackingContextType::PseudoFloat); - } - - None - } -} - -/// Gets the cursor to use given the specific ComputedValues. `default_cursor` specifies -/// the cursor to use if `cursor` is `auto`. Typically, this will be `PointerCursor`, but for -/// text display items it may be `TextCursor` or `VerticalTextCursor`. -#[inline] -fn get_cursor(values: &ComputedValues, default_cursor: Cursor) -> Option<Cursor> { - let inherited_ui = values.get_inherited_ui(); - if inherited_ui.pointer_events == PointerEvents::None { - return None; - } - - Some(match inherited_ui.cursor.keyword { - CursorKind::Auto => default_cursor, - CursorKind::None => Cursor::None, - CursorKind::Default => Cursor::Default, - CursorKind::Pointer => Cursor::Pointer, - CursorKind::ContextMenu => Cursor::ContextMenu, - CursorKind::Help => Cursor::Help, - CursorKind::Progress => Cursor::Progress, - CursorKind::Wait => Cursor::Wait, - CursorKind::Cell => Cursor::Cell, - CursorKind::Crosshair => Cursor::Crosshair, - CursorKind::Text => Cursor::Text, - CursorKind::VerticalText => Cursor::VerticalText, - CursorKind::Alias => Cursor::Alias, - CursorKind::Copy => Cursor::Copy, - CursorKind::Move => Cursor::Move, - CursorKind::NoDrop => Cursor::NoDrop, - CursorKind::NotAllowed => Cursor::NotAllowed, - CursorKind::Grab => Cursor::Grab, - CursorKind::Grabbing => Cursor::Grabbing, - CursorKind::EResize => Cursor::EResize, - CursorKind::NResize => Cursor::NResize, - CursorKind::NeResize => Cursor::NeResize, - CursorKind::NwResize => Cursor::NwResize, - CursorKind::SResize => Cursor::SResize, - CursorKind::SeResize => Cursor::SeResize, - CursorKind::SwResize => Cursor::SwResize, - CursorKind::WResize => Cursor::WResize, - CursorKind::EwResize => Cursor::EwResize, - CursorKind::NsResize => Cursor::NsResize, - CursorKind::NeswResize => Cursor::NeswResize, - CursorKind::NwseResize => Cursor::NwseResize, - CursorKind::ColResize => Cursor::ColResize, - CursorKind::RowResize => Cursor::RowResize, - CursorKind::AllScroll => Cursor::AllScroll, - CursorKind::ZoomIn => Cursor::ZoomIn, - CursorKind::ZoomOut => Cursor::ZoomOut, - }) -} - -/// Adjusts borders as appropriate to account for a fragment's status as the -/// first or last fragment within the range of an element. -/// -/// Specifically, this function sets border widths to zero on the sides for -/// which the fragment is not outermost. -fn modify_border_width_for_inline_sides( - border_width: &mut LogicalMargin<Au>, - inline_border_info: InlineNodeBorderInfo, -) { - if !inline_border_info.is_first_fragment_of_element { - border_width.inline_start = Au(0); - } - - if !inline_border_info.is_last_fragment_of_element { - border_width.inline_end = Au(0); - } -} - -/// Describes how to paint the borders. -#[derive(Clone, Copy)] -pub enum BorderPaintingMode<'a> { - /// Paint borders separately (`border-collapse: separate`). - Separate, - /// Paint collapsed borders. - Collapse(&'a CollapsedBordersForCell), - /// Paint no borders. - Hidden, -} - -fn convert_text_run_to_glyphs( - text_run: Arc<TextRun>, - range: Range<ByteIndex>, - mut origin: Point2D<Au>, -) -> (Au, Vec<GlyphInstance>) { - let mut largest_advance = Au(0); - let mut glyphs = vec![]; - - for slice in text_run.natural_word_slices_in_visual_order(&range) { - for glyph in slice.glyphs.iter_glyphs_for_byte_range(&slice.range) { - let glyph_advance = if glyph.char_is_word_separator() { - glyph.advance() + text_run.extra_word_spacing - } else { - glyph.advance() - }; - largest_advance = largest_advance.max(glyph.advance()); - - if !slice.glyphs.is_whitespace() { - let glyph_offset = glyph.offset().unwrap_or(Point2D::zero()); - let point = origin + glyph_offset.to_vector(); - let glyph = GlyphInstance { - index: glyph.id(), - point: point.to_layout(), - }; - glyphs.push(glyph); - } - origin.x += glyph_advance; - } - } - (largest_advance, glyphs) -} - -pub struct IndexableTextItem { - /// The placement of the text item on the plane. - pub origin: Point2D<Au>, - /// The text run. - pub text_run: Arc<TextRun>, - /// The range of text within the text run. - pub range: Range<ByteIndex>, - /// The position of the start of the baseline of this text. - pub baseline_origin: Point2D<Au>, -} - -#[derive(Default)] -pub struct IndexableText { - inner: FnvHashMap<OpaqueNode, Vec<IndexableTextItem>>, -} - -impl IndexableText { - fn insert(&mut self, node: OpaqueNode, item: IndexableTextItem) { - let entries = self.inner.entry(node).or_default(); - entries.push(item); - } - - pub fn get(&self, node: OpaqueNode) -> Option<&[IndexableTextItem]> { - self.inner.get(&node).map(|x| x.as_slice()) - } - - // Returns the text index within a node for the point of interest. - pub fn text_index(&self, node: OpaqueNode, point_in_item: Point2D<Au>) -> Option<usize> { - let item = self.inner.get(&node)?; - // TODO(#20020): access all elements - let point = point_in_item + item[0].origin.to_vector(); - let offset = point - item[0].baseline_origin; - Some( - item[0] - .text_run - .range_index_of_advance(&item[0].range, offset.x), - ) - } -} - -trait ToF32Px { - type Output; - fn to_f32_px(&self) -> Self::Output; -} - -impl ToF32Px for Rect<Au> { - type Output = LayoutRect; - fn to_f32_px(&self) -> LayoutRect { - LayoutRect::from_untyped(&servo_geometry::au_rect_to_f32_rect(*self).to_box2d()) - } -} |