/* 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, ClipType, 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::{
    default::{Point2D, Rect, SideOffsets2D as UntypedSideOffsets2D, Size2D},
    rect, SideOffsets2D,
};
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;
use style::values::computed::{ClipRectOrAuto, Gradient};
use style::values::generics::background::BackgroundSize;
use style::values::generics::image::PaintWorklet;
use style::values::specified::ui::CursorKind;
use style::values::RGBA;
use style_traits::ToCss;
use webrender_api::units::{LayoutRect, 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, PropertyBinding};
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,
    },
];

// An internal WebRender limit.
//
// See: https://github.com/servo/servo/issues/17230#issuecomment-564307277
const MAX_GLYPHS_PER_TEXT_RUN: usize = 2000;

pub struct InlineNodeBorderInfo {
    is_first_fragment_of_element: bool,
    is_last_fragment_of_element: bool,
}

#[derive(Debug)]
struct StackingContextInfo {
    children: Vec<StackingContext>,
    clip_scroll_nodes: Vec<ClipScrollNodeIndex>,
    real_stacking_context_id: StackingContextId,
}

impl StackingContextInfo {
    fn new(real_stacking_context_id: StackingContextId) -> StackingContextInfo {
        StackingContextInfo {
            children: Vec::new(),
            clip_scroll_nodes: Vec::new(),
            real_stacking_context_id,
        }
    }

    fn take_children(&mut self) -> Vec<StackingContext> {
        mem::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) {
        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 node =
            ClipScrollNode::rounded(rect, radii, self.current_clipping_and_scrolling.scrolling);

        // We want the scroll root to be defined before any possible item that could use it,
        // so we make sure that it is added to the beginning of the parent "real" (non-pseudo)
        // stacking context. This ensures that item reordering will not result in an item using
        // the scroll root before it is defined.
        self.clip_scroll_nodes.push(node);
        let index = ClipScrollNodeIndex::new(self.clip_scroll_nodes.len() - 1);
        let real_stacking_context_id =
            self.stacking_context_info[&self.current_stacking_context_id].real_stacking_context_id;
        self.stacking_context_info
            .get_mut(&real_stacking_context_id)
            .unwrap()
            .clip_scroll_nodes
            .push(index);

        index
    }

    pub fn to_display_list(mut self) -> DisplayList {
        let mut list = Vec::new();
        let root_context = mem::replace(&mut self.root_stacking_context, StackingContext::root());

        self.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) => {
                let _ = info
                    .full
                    .collect_stacking_contexts_for_blocklike_fragment(state);
                // To ensure the caller updates this fragment's stacking context
                // appropriately based on the un-truncated fragment's status,
                // we don't pass on the result of collecting stacking contexts.
                false
            },
            _ => false,
        }
    }

    pub fn create_stacking_context_for_inline_block(
        &mut self,
        base: &BaseFlow,
        state: &mut StackingContextCollectionState,
    ) -> bool {
        self.stacking_context_id = state.allocate_stacking_context_info(StackingContextType::Real);

        let established_reference_frame = if self.can_establish_reference_frame() {
            // WebRender currently creates reference frames automatically, so just add
            // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame.
            self.established_reference_frame =
                Some(state.add_clip_scroll_node(ClipScrollNode::placeholder()));
            self.established_reference_frame
        } else {
            None
        };

        let current_stacking_context_id = state.current_stacking_context_id;
        let stacking_context = self.create_stacking_context(
            self.stacking_context_id,
            &base,
            StackingContextType::Real,
            established_reference_frame,
            state.current_clipping_and_scrolling,
        );
        state.add_stacking_context(current_stacking_context_id, stacking_context);
        true
    }

    /// Adds the display items necessary to paint the background of this fragment to the display
    /// list if necessary.
    fn build_display_list_for_background_if_applicable(
        &self,
        state: &mut DisplayListBuildState,
        style: &ComputedValues,
        display_list_section: DisplayListSection,
        absolute_bounds: Rect<Au>,
    ) {
        let background = style.get_background();
        let background_color = style.resolve_color(background.background_color);
        // XXXManishearth the below method should ideally use an iterator over
        // backgrounds
        self.build_display_list_for_background_if_applicable_with_background(
            state,
            style,
            background,
            background_color,
            display_list_section,
            absolute_bounds,
        )
    }

    /// Same as build_display_list_for_background_if_applicable, but lets you
    /// override the actual background used
    fn build_display_list_for_background_if_applicable_with_background(
        &self,
        state: &mut DisplayListBuildState,
        style: &ComputedValues,
        background: &style_structs::Background,
        background_color: 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: PropertyBinding::Value(background_color.to_layout()),
                    common: items::empty_common_item_properties(),
                    bounds: bounds.to_layout(),
                },
            )));
        });

        // The background image is painted on top of the background color.
        // Implements background image, per spec:
        // http://www.w3.org/TR/CSS21/colors.html#background
        let background = style.get_background();
        for (i, background_image) in background.background_image.0.iter().enumerate().rev() {
            match *background_image {
                Image::None => {},
                Image::Gradient(ref gradient) => {
                    self.build_display_list_for_background_gradient(
                        state,
                        display_list_section,
                        absolute_bounds,
                        gradient,
                        style,
                        i,
                    );
                },
                Image::Url(ref image_url) => {
                    if let Some(url) = image_url.url() {
                        let webrender_image = state.layout_context.get_webrender_image_for_url(
                            self.node,
                            url.clone(),
                            UsePlaceholder::No,
                        );
                        if let Some(webrender_image) = webrender_image {
                            self.build_display_list_for_webrender_image(
                                state,
                                style,
                                display_list_section,
                                absolute_bounds,
                                webrender_image,
                                i,
                            );
                        }
                    }
                },
                Image::PaintWorklet(ref paint_worklet) => {
                    let bounding_box = self.border_box - style.logical_border_width();
                    let bounding_box_size = bounding_box.size.to_physical(style.writing_mode);
                    let background_size =
                        get_cyclic(&style.get_background().background_size.0, i).clone();
                    let size = match background_size {
                        BackgroundSize::ExplicitSize { width, height } => Size2D::new(
                            width
                                .to_used_value(bounding_box_size.width)
                                .unwrap_or(bounding_box_size.width),
                            height
                                .to_used_value(bounding_box_size.height)
                                .unwrap_or(bounding_box_size.height),
                        ),
                        _ => bounding_box_size,
                    };
                    let webrender_image = self.get_webrender_image_for_paint_worklet(
                        state,
                        style,
                        paint_worklet,
                        size,
                    );
                    if let Some(webrender_image) = webrender_image {
                        self.build_display_list_for_webrender_image(
                            state,
                            style,
                            display_list_section,
                            absolute_bounds,
                            webrender_image,
                            i,
                        );
                    }
                },
                Image::CrossFade(..) | Image::ImageSet(..) => {
                    unreachable!("Shouldn't be parsed by Servo for now")
                },
                Image::Rect(ref rect) => {
                    // This is a (boxed) empty enum on non-Gecko
                    match **rect {}
                },
            }
        }
    }

    /// 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,
        );

        if placement.tile_size.is_empty() {
            return;
        }

        state.clipping_and_scrolling_scope(|state| {
            if !placement.clip_radii.is_zero() {
                let clip_id =
                    state.add_late_clip_node(placement.clip_rect.to_layout(), placement.clip_radii);
                state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id);
            }

            // Create the image display item.
            let base = state.create_base_display_item(
                placement.clip_rect,
                self.node,
                get_cursor(&style, Cursor::Default),
                display_list_section,
            );

            debug!("(building display list) adding background image.");
            let item = CommonDisplayItem::new(
                base,
                webrender_api::RepeatingImageDisplayItem {
                    bounds: placement.bounds.to_f32_px(),
                    common: items::empty_common_item_properties(),
                    image_key: webrender_image.key.unwrap(),
                    stretch_size: placement.tile_size.to_layout(),
                    tile_spacing: placement.tile_spacing.to_layout(),
                    image_rendering: style.get_inherited_box().image_rendering.to_layout(),
                    alpha_type: webrender_api::AlphaType::PremultipliedAlpha,
                    color: webrender_api::ColorF::WHITE,
                },
            );
            state.add_display_item(DisplayItem::RepeatingImage(item))
        });
    }

    /// Calculates the webrender image for a paint worklet.
    /// Returns None if the worklet is not registered.
    /// If the worklet has missing image URLs, it passes them to the image cache for loading.
    fn get_webrender_image_for_paint_worklet(
        &self,
        state: &mut DisplayListBuildState,
        style: &ComputedValues,
        paint_worklet: &PaintWorklet,
        size_in_au: Size2D<Au>,
    ) -> Option<WebRenderImageInfo> {
        let device_pixel_ratio = state.layout_context.style_context.device_pixel_ratio();
        let size_in_px =
            euclid::Size2D::new(size_in_au.width.to_f32_px(), size_in_au.height.to_f32_px());

        // TODO: less copying.
        let name = paint_worklet.name.clone();
        let arguments = paint_worklet
            .arguments
            .iter()
            .map(|argument| argument.to_css_string())
            .collect();

        let draw_result = match state.layout_context.registered_painters.get(&name) {
            Some(painter) => {
                debug!(
                    "Drawing a paint image {}({},{}).",
                    name, size_in_px.width, size_in_px.height
                );
                let properties = painter
                    .properties()
                    .iter()
                    .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id)))
                    .map(|(name, id)| (name.clone(), style.computed_value_to_string(id)))
                    .collect();
                painter.draw_a_paint_image(size_in_px, 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 {
                Gradient::Linear {
                    ref direction,
                    ref items,
                    ref repeating,
                    compat_mode: _,
                } => {
                    let (gradient, stops) =
                        gradient::linear(style, placement.tile_size, items, *direction, *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))
                },
                Gradient::Radial {
                    ref shape,
                    ref position,
                    ref items,
                    ref repeating,
                    compat_mode: _,
                } => {
                    let (gradient, stops) = gradient::radial(
                        style,
                        placement.tile_size,
                        items,
                        shape,
                        position,
                        *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))
                },
                Gradient::Conic { .. } => unimplemented!(),
            };
            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 self
            .build_display_list_for_border_image(
                state,
                style,
                base.clone(),
                bounds,
                &border_style_struct.border_image_source,
                border_widths,
            )
            .is_some()
        {
            return;
        }

        if border_widths == SideOffsets2D::zero() {
            return;
        }
        let details = BorderDetails::Normal(NormalBorder {
            left: BorderSide {
                color: style.resolve_color(colors.left).to_layout(),
                style: border_style.left.to_layout(),
            },
            right: BorderSide {
                color: style.resolve_color(colors.right).to_layout(),
                style: border_style.right.to_layout(),
            },
            top: BorderSide {
                color: style.resolve_color(colors.top).to_layout(),
                style: border_style.top.to_layout(),
            },
            bottom: BorderSide {
                color: style.resolve_color(colors.bottom).to_layout(),
                style: border_style.bottom.to_layout(),
            },
            radius: border_radius,
            do_aa: true,
        });
        state.add_display_item(DisplayItem::Border(CommonDisplayItem::with_data(
            base,
            webrender_api::BorderDisplayItem {
                bounds: bounds.to_layout(),
                common: items::empty_common_item_properties(),
                widths: border_widths.to_layout(),
                details,
            },
            Vec::new(),
        )));
    }

    /// Add display item for image border.
    ///
    /// Returns `Some` if the addition was successful.
    fn build_display_list_for_border_image(
        &self,
        state: &mut DisplayListBuildState,
        style: &ComputedValues,
        base: BaseDisplayItem,
        bounds: Rect<Au>,
        image: &Image,
        border_width: UntypedSideOffsets2D<Au>,
    ) -> Option<()> {
        let border_style_struct = style.get_border();
        let border_image_outset =
            border::image_outset(border_style_struct.border_image_outset, border_width);
        let border_image_area = bounds.outer_rect(border_image_outset).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 {
                Gradient::Linear {
                    ref direction,
                    ref items,
                    ref repeating,
                    compat_mode: _,
                } => {
                    let (wr_gradient, linear_stops) =
                        gradient::linear(style, border_image_area, items, *direction, *repeating);
                    stops = linear_stops;
                    NinePatchBorderSource::Gradient(wr_gradient)
                },
                Gradient::Radial {
                    ref shape,
                    ref position,
                    ref items,
                    ref repeating,
                    compat_mode: _,
                } => {
                    let (wr_gradient, radial_stops) = gradient::radial(
                        style,
                        border_image_area,
                        items,
                        shape,
                        position,
                        *repeating,
                    );
                    stops = radial_stops;
                    NinePatchBorderSource::RadialGradient(wr_gradient)
                },
                Gradient::Conic { .. } => unimplemented!(),
            },
            _ => return None,
        };

        // FIXME(emilio): WR expects device pixels here... somehow?
        let size = euclid::Size2D::new(width as i32, height as i32);
        let details = BorderDetails::NinePatch(NinePatchBorder {
            source,
            width: width as i32,
            height: height as i32,
            slice: border::image_slice(border_image_slice, size),
            fill: border_image_fill,
            repeat_horizontal: border_image_repeat.0.to_layout(),
            repeat_vertical: border_image_repeat.1.to_layout(),
            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: PropertyBinding::Value(background_color.to_layout()),
                    bounds: stacking_relative_border_box.to_layout(),
                },
            )));
        }

        // Draw a caret at the insertion point.
        let insertion_point_index = match scanned_text_fragment_info.insertion_point {
            Some(insertion_point_index) => insertion_point_index,
            None => return,
        };
        let range = Range::new(
            scanned_text_fragment_info.range.begin(),
            insertion_point_index - scanned_text_fragment_info.range.begin(),
        );
        let advance = scanned_text_fragment_info.run.advance_for_range(&range);

        let insertion_point_bounds;
        let cursor;
        if !self.style.writing_mode.is_vertical() {
            insertion_point_bounds = rect(
                stacking_relative_border_box.origin.x + advance,
                stacking_relative_border_box.origin.y,
                INSERTION_POINT_LOGICAL_WIDTH,
                stacking_relative_border_box.size.height,
            );
            cursor = Cursor::Text;
        } else {
            insertion_point_bounds = rect(
                stacking_relative_border_box.origin.x,
                stacking_relative_border_box.origin.y + advance,
                stacking_relative_border_box.size.width,
                INSERTION_POINT_LOGICAL_WIDTH,
            );
            cursor = Cursor::VerticalText;
        };

        let base = state.create_base_display_item(
            insertion_point_bounds,
            self.node,
            get_cursor(&self.style, cursor),
            display_list_section,
        );
        state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
            base,
            webrender_api::RectangleDisplayItem {
                common: items::empty_common_item_properties(),
                color: PropertyBinding::Value(self.style().get_inherited_text().color.to_layout()),
                bounds: insertion_point_bounds.to_layout(),
            },
        )));
    }

    /// Adds the display items for this fragment to the given display list.
    ///
    /// Arguments:
    ///
    /// * `state`: The display building state, including the display list currently
    ///   under construction and other metadata useful for constructing it.
    /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
    /// * `clip`: The region to clip the display items to.
    /// * `overflow_content_size`: The size of content associated with this fragment
    ///      that must have overflow handling applied to it. For a scrollable block
    ///      flow, it is expected that this is the size of the child boxes.
    pub fn build_display_list(
        &mut self,
        state: &mut DisplayListBuildState,
        stacking_relative_border_box: Rect<Au>,
        border_painting_mode: BorderPaintingMode,
        display_list_section: DisplayListSection,
        clip: Rect<Au>,
        overflow_content_size: Option<Size2D<Au>>,
    ) {
        let previous_clipping_and_scrolling = state.current_clipping_and_scrolling;
        if let Some(index) = self.established_reference_frame {
            state.current_clipping_and_scrolling = ClippingAndScrolling::simple(index);
        }

        self.restyle_damage.remove(ServoRestyleDamage::REPAINT);
        self.build_display_list_no_damage(
            state,
            stacking_relative_border_box,
            border_painting_mode,
            display_list_section,
            clip,
            overflow_content_size,
        );

        state.current_clipping_and_scrolling = previous_clipping_and_scrolling;
    }

    /// build_display_list, but don't update the restyle damage
    ///
    /// Must be paired with a self.restyle_damage.remove(REPAINT) somewhere
    fn build_display_list_no_damage(
        &self,
        state: &mut DisplayListBuildState,
        stacking_relative_border_box: Rect<Au>,
        border_painting_mode: BorderPaintingMode,
        display_list_section: DisplayListSection,
        clip: Rect<Au>,
        overflow_content_size: Option<Size2D<Au>>,
    ) {
        if self.style().get_inherited_box().visibility != Visibility::Visible {
            return;
        }

        // If this fragment takes up no space, we don't need to build any display items for it.
        if self.has_non_invertible_transform() {
            return;
        }

        debug!(
            "Fragment::build_display_list at rel={:?}, abs={:?}: {:?}",
            self.border_box, stacking_relative_border_box, self
        );

        // Check the clip rect. If there's nothing to render at all, don't even construct display
        // list items.
        let empty_rect = !clip.intersects(&stacking_relative_border_box);
        if self.is_primary_fragment() && !empty_rect {
            // Add shadows, background, borders, and outlines, if applicable.
            if let Some(ref inline_context) = self.inline_context {
                for node in inline_context.nodes.iter().rev() {
                    self.build_display_list_for_background_if_applicable(
                        state,
                        &*node.style,
                        display_list_section,
                        stacking_relative_border_box,
                    );

                    self.build_display_list_for_box_shadow_if_applicable(
                        state,
                        &*node.style,
                        display_list_section,
                        stacking_relative_border_box,
                        clip,
                    );

                    self.build_display_list_for_borders_if_applicable(
                        state,
                        &*node.style,
                        Some(InlineNodeBorderInfo {
                            is_first_fragment_of_element: node
                                .flags
                                .contains(InlineFragmentNodeFlags::FIRST_FRAGMENT_OF_ELEMENT),
                            is_last_fragment_of_element: node
                                .flags
                                .contains(InlineFragmentNodeFlags::LAST_FRAGMENT_OF_ELEMENT),
                        }),
                        border_painting_mode,
                        stacking_relative_border_box,
                        display_list_section,
                        clip,
                    );

                    // FIXME(emilio): Why does outline not do the same width
                    // fixup as border?
                    self.build_display_list_for_outline_if_applicable(
                        state,
                        &*node.style,
                        stacking_relative_border_box,
                        clip,
                    );
                }
            }

            if !self.is_scanned_text_fragment() {
                self.build_display_list_for_background_if_applicable(
                    state,
                    &*self.style,
                    display_list_section,
                    stacking_relative_border_box,
                );

                self.build_display_list_for_box_shadow_if_applicable(
                    state,
                    &*self.style,
                    display_list_section,
                    stacking_relative_border_box,
                    clip,
                );

                self.build_display_list_for_borders_if_applicable(
                    state,
                    &*self.style,
                    /* inline_node_info = */ None,
                    border_painting_mode,
                    stacking_relative_border_box,
                    display_list_section,
                    clip,
                );

                self.build_display_list_for_outline_if_applicable(
                    state,
                    &*self.style,
                    stacking_relative_border_box,
                    clip,
                );
            }
        }

        if self.is_primary_fragment() {
            // Paint the selection point if necessary.  Even an empty text fragment may have an
            // insertion point, so we do this even if `empty_rect` is true.
            self.build_display_items_for_selection_if_necessary(
                state,
                stacking_relative_border_box,
                display_list_section,
            );
        }

        if empty_rect {
            return;
        }

        debug!("Fragment::build_display_list: intersected. Adding display item...");

        if let Some(content_size) = overflow_content_size {
            // Create a transparent rectangle for hit-testing purposes that exists in front
            // of this fragment's background but behind its content. This ensures that any
            // hit tests inside the content box but not on actual content target the current
            // scrollable ancestor.
            let content_size = Rect::new(stacking_relative_border_box.origin, content_size);
            let base = state.create_base_display_item_with_clipping_and_scrolling(
                content_size,
                self.node,
                // FIXME(emilio): Why does this ignore pointer-events?
                get_cursor(&self.style, Cursor::Default).or(Some(Cursor::Default)),
                display_list_section,
                state.current_clipping_and_scrolling,
            );
            state.add_display_item(DisplayItem::Rectangle(CommonDisplayItem::new(
                base,
                webrender_api::RectangleDisplayItem {
                    common: items::empty_common_item_properties(),
                    color: PropertyBinding::Value(ColorF::TRANSPARENT),
                    bounds: content_size.to_layout(),
                },
            )));
        }

        // Create special per-fragment-type display items.
        state.clipping_and_scrolling_scope(|state| {
            self.build_fragment_type_specific_display_items(
                state,
                stacking_relative_border_box,
                clip,
            );
        });

        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() {
                // This is already calculated inside of build_border_radius_for_inner_rect(), it would be
                // nice if it were only calculated once.
                let border_widths = self
                    .style
                    .logical_border_width()
                    .to_physical(self.style.writing_mode);
                let clip_id = state.add_late_clip_node(
                    stacking_relative_border_box
                        .inner_rect(border_widths)
                        .to_layout(),
                    radii,
                );
                state.current_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id);
            }

            state.create_base_display_item(
                stacking_relative_border_box,
                self.node,
                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 base = create_base_display_item(state);
                    let bounds = stacking_relative_content_box.to_layout();

                    // XXXjdm: This sleight-of-hand to convert LayoutRect -> Size2D<CSSPixel>
                    //         looks bogus.
                    state.iframe_sizes.push(IFrameSize {
                        id: browsing_context_id,
                        size: euclid::Size2D::new(bounds.size.width, bounds.size.height),
                    });

                    let pipeline_id = match fragment_info.pipeline_id {
                        Some(pipeline_id) => pipeline_id,
                        None => return warn!("No pipeline id for iframe {}.", browsing_context_id),
                    };

                    let item = DisplayItem::Iframe(Box::new(IframeDisplayItem {
                        base,
                        bounds,
                        iframe: pipeline_id,
                    }));
                    state.add_display_item(item);
                }
            },
            SpecificFragmentInfo::Image(ref image_fragment) => {
                // Place the image into the display list.
                if let Some(ref image) = image_fragment.image {
                    if let Some(id) = image.id {
                        let base = create_base_display_item(state);
                        state.add_image_item(
                            base,
                            webrender_api::ImageDisplayItem {
                                bounds: stacking_relative_content_box.to_layout(),
                                common: items::empty_common_item_properties(),
                                image_key: id,
                                image_rendering: self
                                    .style
                                    .get_inherited_box()
                                    .image_rendering
                                    .to_layout(),
                                alpha_type: webrender_api::AlphaType::PremultipliedAlpha,
                                color: webrender_api::ColorF::WHITE,
                            },
                        );
                    }
                }
            },
            SpecificFragmentInfo::Media(ref fragment_info) => {
                if let Some((ref 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,
                            image_rendering: ImageRendering::Auto,
                            alpha_type: webrender_api::AlphaType::PremultipliedAlpha,
                            color: webrender_api::ColorF::WHITE,
                        },
                    );
                }
            },
            SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => {
                if canvas_fragment_info.dom_width == Au(0) ||
                    canvas_fragment_info.dom_height == Au(0)
                {
                    return;
                }

                let image_key = match canvas_fragment_info.source {
                    CanvasFragmentSource::WebGL(image_key) => image_key,
                    CanvasFragmentSource::WebGPU(image_key) => image_key,
                    CanvasFragmentSource::Image(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,
                    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 mut glyphs = convert_text_run_to_glyphs(
            text_fragment.run.clone(),
            text_fragment.range,
            baseline_origin,
        );

        let indexable_text = IndexableTextItem {
            origin: stacking_relative_content_box.origin,
            text_run: text_fragment.run.clone(),
            range: text_fragment.range,
            baseline_origin,
        };
        state.indexable_text.insert(self.node, indexable_text);

        // Process glyphs in chunks to avoid overflowing WebRender's internal limits (#17230).
        while !glyphs.is_empty() {
            let mut rest_of_glyphs = vec![];
            if glyphs.len() > MAX_GLYPHS_PER_TEXT_RUN {
                rest_of_glyphs = glyphs[MAX_GLYPHS_PER_TEXT_RUN..].to_vec();
                glyphs.truncate(MAX_GLYPHS_PER_TEXT_RUN);
            };

            state.add_display_item(DisplayItem::Text(CommonDisplayItem::with_data(
                base.clone(),
                webrender_api::TextDisplayItem {
                    bounds: 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,
            )));

            glyphs = rest_of_glyphs;
        }

        // TODO(#17715): emit text-emphasis marks here.
        // (just push another TextDisplayItem?)

        // Line-Through
        if text_decorations.line_through {
            let mut stacking_relative_box = logical_stacking_relative_content_box;
            stacking_relative_box.start.b =
                stacking_relative_box.start.b + metrics.ascent - metrics.strikeout_offset;
            stacking_relative_box.size.block = 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 = perspective.then(&transform).inverse();

        let origin = border_box.origin;
        let transform_clip = |clip: Rect<Au>| {
            if clip == Rect::max_rect() {
                return clip;
            }

            match transform {
                Some(transform) if transform.m13 != 0.0 || transform.m23 != 0.0 => {
                    // We cannot properly handle perspective transforms, because there may be a
                    // situation where an element is transformed from outside the clip into the
                    // clip region. Here we don't have enough information to detect when that is
                    // happening. For the moment we just punt on trying to optimize the display
                    // list for those cases.
                    Rect::max_rect()
                },
                Some(transform) => {
                    let clip = rect(
                        (clip.origin.x - origin.x).to_f32_px(),
                        (clip.origin.y - origin.y).to_f32_px(),
                        clip.size.width.to_f32_px(),
                        clip.size.height.to_f32_px(),
                    );

                    let clip = transform.outer_transformed_rect(&clip).unwrap();

                    rect(
                        Au::from_f32_px(clip.origin.x),
                        Au::from_f32_px(clip.origin.y),
                        Au::from_f32_px(clip.size.width),
                        Au::from_f32_px(clip.size.height),
                    )
                },
                None => Rect::zero(),
            }
        };

        if let Some(clip) = state.clip_stack.last().cloned() {
            state.clip_stack.push(transform_clip(clip));
            preserved_state.clips_pushed += 1;
        }

        if let Some(clip) = state.containing_block_clip_stack.last().cloned() {
            state.containing_block_clip_stack.push(transform_clip(clip));
            preserved_state.containing_block_clips_pushed += 1;
        }
    }

    /// Returns true if this fragment may establish a reference frame and this block
    /// creates a stacking context. Both are necessary in order to establish a reference
    /// frame.
    fn is_reference_frame(&self, context_type: Option<StackingContextType>) -> bool {
        match context_type {
            Some(StackingContextType::Real) => self.fragment.can_establish_reference_frame(),
            _ => false,
        }
    }

    pub fn collect_stacking_contexts_for_block(
        &mut self,
        state: &mut StackingContextCollectionState,
        flags: StackingContextCollectionFlags,
    ) {
        // This block flow produces no stacking contexts if it takes up no space.
        if self.has_non_invertible_transform() {
            return;
        }

        let mut preserved_state = SavedStackingContextCollectionState::new(state);

        let stacking_context_type = self.stacking_context_type(flags);
        self.base.stacking_context_id = match stacking_context_type {
            None => state.current_stacking_context_id,
            Some(sc_type) => state.allocate_stacking_context_info(sc_type),
        };
        state.current_stacking_context_id = self.base.stacking_context_id;

        if stacking_context_type == Some(StackingContextType::Real) {
            state.current_real_stacking_context_id = self.base.stacking_context_id;
        }

        let established_reference_frame = if self.is_reference_frame(stacking_context_type) {
            // WebRender currently creates reference frames automatically, so just add
            // a placeholder node to allocate a ClipScrollNodeIndex for this reference frame.
            Some(state.add_clip_scroll_node(ClipScrollNode::placeholder()))
        } else {
            None
        };

        // We are getting the id of the scroll root that contains us here, not the id of
        // any scroll root that we create. If we create a scroll root, its index will be
        // stored in state.current_clipping_and_scrolling. If we create a stacking context,
        // we don't want it to be contained by its own scroll root.
        let containing_clipping_and_scrolling = self.setup_clipping_for_block(
            state,
            &mut preserved_state,
            stacking_context_type,
            established_reference_frame,
            flags,
        );

        let creates_containing_block = !flags
            .contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK);
        let abspos_containing_block = established_reference_frame.is_some() ||
            (creates_containing_block && self.positioning() != StylePosition::Static);
        if abspos_containing_block {
            state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling;
        }

        match stacking_context_type {
            None => self.base.collect_stacking_contexts_for_children(state),
            Some(StackingContextType::Real) => {
                self.create_real_stacking_context_for_block(
                    preserved_state.stacking_context_id,
                    containing_clipping_and_scrolling,
                    established_reference_frame,
                    state,
                );
            },
            Some(stacking_context_type) => {
                self.create_pseudo_stacking_context_for_block(
                    stacking_context_type,
                    preserved_state.stacking_context_id,
                    containing_clipping_and_scrolling,
                    state,
                );
            },
        }

        preserved_state.restore(state);
    }

    fn setup_clipping_for_block(
        &mut self,
        state: &mut StackingContextCollectionState,
        preserved_state: &mut SavedStackingContextCollectionState,
        stacking_context_type: Option<StackingContextType>,
        established_reference_frame: Option<ClipScrollNodeIndex>,
        flags: StackingContextCollectionFlags,
    ) -> ClippingAndScrolling {
        // If this block is absolutely positioned, we should be clipped and positioned by
        // the scroll root of our nearest ancestor that establishes a containing block.
        let containing_clipping_and_scrolling = match self.positioning() {
            StylePosition::Absolute => {
                preserved_state.switch_to_containing_block_clip(state);
                state.current_clipping_and_scrolling =
                    state.containing_block_clipping_and_scrolling;
                state.containing_block_clipping_and_scrolling
            },
            StylePosition::Fixed => {
                // If we are a fixed positioned stacking context, we want to be scrolled by
                // our reference frame instead of the clip scroll node that we are inside.
                preserved_state.push_clip(state, Rect::max_rect(), StylePosition::Fixed);
                state.current_clipping_and_scrolling.scrolling =
                    state.current_parent_reference_frame_id;
                state.current_clipping_and_scrolling
            },
            _ => state.current_clipping_and_scrolling,
        };
        self.base.clipping_and_scrolling = Some(containing_clipping_and_scrolling);

        if let Some(reference_frame_index) = established_reference_frame {
            let clipping_and_scrolling = ClippingAndScrolling::simple(reference_frame_index);
            state.current_clipping_and_scrolling = clipping_and_scrolling;
            self.base.clipping_and_scrolling = Some(clipping_and_scrolling);
        }

        let stacking_relative_border_box = if self.fragment.establishes_stacking_context() {
            self.stacking_relative_border_box(CoordinateSystem::Own)
        } else {
            self.stacking_relative_border_box(CoordinateSystem::Parent)
        };

        if stacking_context_type == Some(StackingContextType::Real) {
            self.transform_clip_to_coordinate_space(state, preserved_state);
        }

        if !flags.contains(StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE) {
            self.setup_clip_scroll_node_for_position(state, stacking_relative_border_box);
            self.setup_clip_scroll_node_for_overflow(state, stacking_relative_border_box);
            self.setup_clip_scroll_node_for_css_clip(
                state,
                preserved_state,
                stacking_relative_border_box,
            );
        }
        self.base.clip = state
            .clip_stack
            .last()
            .cloned()
            .unwrap_or_else(Rect::max_rect);

        // We keep track of our position so that any stickily positioned elements can
        // properly determine the extent of their movement relative to scrolling containers.
        if !flags.contains(StackingContextCollectionFlags::POSITION_NEVER_CREATES_CONTAINING_BLOCK)
        {
            let border_box = if self.fragment.establishes_stacking_context() {
                stacking_relative_border_box
            } else {
                self.stacking_relative_border_box(CoordinateSystem::Own)
            };
            state.parent_stacking_relative_content_box =
                self.fragment.stacking_relative_content_box(border_box)
        }

        match self.positioning() {
            StylePosition::Absolute | StylePosition::Relative | StylePosition::Fixed => {
                state.containing_block_clipping_and_scrolling = state.current_clipping_and_scrolling
            },
            _ => {},
        }

        containing_clipping_and_scrolling
    }

    fn setup_clip_scroll_node_for_position(
        &mut self,
        state: &mut StackingContextCollectionState,
        border_box: Rect<Au>,
    ) {
        if self.positioning() != StylePosition::Sticky {
            return;
        }

        let sticky_position = self.sticky_position();
        if sticky_position.left == MaybeAuto::Auto &&
            sticky_position.right == MaybeAuto::Auto &&
            sticky_position.top == MaybeAuto::Auto &&
            sticky_position.bottom == MaybeAuto::Auto
        {
            return;
        }

        // Since position: sticky elements always establish a stacking context, we will
        // have previously calculated our border box in our own coordinate system. In
        // order to properly calculate max offsets we need to compare our size and
        // position in our parent's coordinate system.
        let border_box_in_parent = self.stacking_relative_border_box(CoordinateSystem::Parent);
        let margins = self.fragment.margin.to_physical(
            self.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 clip = ClippingRegion::from_rect(clip_rect.to_layout());
        let radii = build_border_radius_for_inner_rect(border_box, &self.fragment.style);
        if !radii.is_zero() {
            let node = ClipScrollNode::rounded(
                clip_rect.to_layout(),
                radii,
                state.current_clipping_and_scrolling.scrolling,
            );
            let clip_id = state.add_clip_scroll_node(node);
            let new_clipping_and_scrolling = ClippingAndScrolling::simple(clip_id);
            self.base.clipping_and_scrolling = Some(new_clipping_and_scrolling);
            state.current_clipping_and_scrolling = new_clipping_and_scrolling;
        }

        let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size;
        let content_size = Size2D::new(content_size.x, content_size.y);

        let external_id =
            ExternalScrollId(self.fragment.unique_id(), state.pipeline_id.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 {
            ClipRectOrAuto::Rect(ref r) => r,
            ClipRectOrAuto::Auto => return,
        };

        // CSS `clip` should only apply to position:absolute or positione:fixed elements.
        // CSS Masking Appendix A: "Applies to: Absolutely positioned elements."
        match self.positioning() {
            StylePosition::Absolute | StylePosition::Fixed => {},
            _ => return,
        }

        let clip_rect = style_clip_rect.for_border_rect(stacking_relative_border_box);
        preserved_state.push_clip(state, clip_rect, self.positioning());

        let new_index = state.add_clip_scroll_node(ClipScrollNode {
            parent_index: self.clipping_and_scrolling().scrolling,
            clip: ClippingRegion::from_rect(clip_rect.to_layout()),
            content_rect: LayoutRect::zero(), // content_rect isn't important for clips.
            node_type: ClipScrollNodeType::Clip(ClipType::Rect),
        });

        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 Rect<Au> {
    type Output = LayoutRect;
    fn to_f32_px(&self) -> LayoutRect {
        LayoutRect::from_untyped(&servo_geometry::au_rect_to_f32_rect(*self))
    }
}