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