servo/components/layout/display_list_builder.rs
author Glenn Watson <github@intuitionlibrary.com>
Tue, 18 Jul 2017 07:08:25 -0700
changeset 418155 5fece381cc9fdf19aeb0850ac47acf37dffcfbcc
parent 418011 2d5674f3b7422923f86e6df93fb7d8ef999a5f36
child 418550 9081664a8b86cd6bb3f188a78d3c70f076ef9ccd
permissions -rw-r--r--
servo: Merge #17771 - Update WR (text-shadow improvements, iframe + image clips, depth buffer sharing) (from mrobinson:wr-update-shadows); r=emilio <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [ ] These changes do not require tests because _____ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 988a516a5bfc9f2de1ced93b0681d714a09910fa

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Builds display lists from flows and fragments.
//!
//! Other browser engines sometimes call this "painting", but it is more accurately called display
//! list building, as the actual painting does not happen here—only deciding *what* we're going to
//! paint.

#![deny(unsafe_code)]

use app_units::{AU_PER_PX, Au};
use block::{BlockFlow, BlockStackingContextType};
use canvas_traits::{CanvasData, CanvasMsg, FromLayoutMsg};
use context::LayoutContext;
use euclid::{Transform3D, Point2D, Vector2D, Rect, SideOffsets2D, Size2D, TypedSize2D};
use flex::FlexFlow;
use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED};
use flow_ref::FlowRef;
use fragment::{CoordinateSystem, Fragment, ImageFragmentInfo, ScannedTextFragmentInfo};
use fragment::{SpecificFragmentInfo, TruncatedFragmentInfo};
use gfx::display_list;
use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDetails, BorderDisplayItem};
use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
use gfx::display_list::{DisplayItem, DisplayItemMetadata, DisplayList, DisplayListSection};
use gfx::display_list::{GradientDisplayItem, IframeDisplayItem, ImageBorder, ImageDisplayItem};
use gfx::display_list::{LineDisplayItem, NormalBorder, OpaqueNode, PushTextShadowDisplayItem};
use gfx::display_list::{PopTextShadowDisplayItem, RadialGradientDisplayItem, ScrollRoot};
use gfx::display_list::{ScrollRootType, SolidColorDisplayItem, StackingContext, StackingContextType};
use gfx::display_list::{TextDisplayItem, TextOrientation, WebGLDisplayItem, WebRenderImageInfo};
use gfx_traits::{combine_id_with_fragment_type, FragmentType, StackingContextId};
use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFlow, LAST_FRAGMENT_OF_ELEMENT};
use ipc_channel::ipc;
use list_item::ListItemFlow;
use model::{self, MaybeAuto};
use msg::constellation_msg::BrowsingContextId;
use net_traits::image::base::PixelFormat;
use net_traits::image_cache::UsePlaceholder;
use range::Range;
use script_layout_interface::wrapper_traits::PseudoElementType;
use servo_config::opts;
use servo_geometry::max_rect;
use servo_url::ServoUrl;
use std::{cmp, f32};
use std::collections::HashMap;
use std::default::Default;
use std::mem;
use std::sync::Arc;
use style::computed_values::{background_attachment, background_clip, background_origin};
use style::computed_values::{background_repeat, border_style, cursor};
use style::computed_values::{image_rendering, overflow_x, pointer_events, position, visibility};
use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
use style::properties::{self, ComputedValues};
use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
use style::properties::style_structs;
use style::servo::restyle_damage::REPAINT;
use style::values::{Either, RGBA};
use style::values::computed::{Angle, Gradient, GradientItem, LengthOrPercentage, Percentage};
use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position};
use style::values::computed::effects::SimpleShadow;
use style::values::computed::image::{EndingShape, LineDirection};
use style::values::generics::background::BackgroundSize;
use style::values::generics::effects::Filter;
use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape};
use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind};
use style::values::generics::image::{Image, ShapeExtent};
use style::values::generics::image::PaintWorklet;
use style::values::specified::position::{X, Y};
use style_traits::CSSPixel;
use style_traits::cursor::Cursor;
use table_cell::CollapsedBordersForCell;
use webrender_api::{ClipId, ColorF, ComplexClipRegion, GradientStop, LocalClip, RepeatMode};
use webrender_api::{ScrollPolicy, TransformStyle};
use webrender_helpers::{ToBorderRadius, ToMixBlendMode, ToRectF, ToTransformStyle};

trait ResolvePercentage {
    fn resolve(&self, length: u32) -> u32;
}

impl ResolvePercentage for NumberOrPercentage {
    fn resolve(&self, length: u32) -> u32 {
        match *self {
            NumberOrPercentage::Percentage(p) => {
                (p.0 * length as f32).round() as u32
            }
            NumberOrPercentage::Number(n) => {
                n.round() as u32
            }
        }
    }
}

fn convert_repeat_mode(from: RepeatKeyword) -> RepeatMode {
    match from {
        RepeatKeyword::Stretch => RepeatMode::Stretch,
        RepeatKeyword::Repeat => RepeatMode::Repeat,
        RepeatKeyword::Round => RepeatMode::Round,
        RepeatKeyword::Space => RepeatMode::Space,
    }
}

fn establishes_containing_block_for_absolute(positioning: position::T) -> bool {
    match positioning {
        position::T::absolute | position::T::relative | position::T::fixed => true,
        _ => false,
    }
}

trait RgbColor {
    fn rgb(r: u8, g: u8, b: u8) -> Self;
}

impl RgbColor for ColorF {
    fn rgb(r: u8, g: u8, b: u8) -> Self {
        ColorF {
            r: (r as f32) / (255.0 as f32),
            g: (g as f32) / (255.0 as f32),
            b: (b as f32) / (255.0 as f32),
            a: 1.0 as f32
        }
    }
}

static THREAD_TINT_COLORS: [ColorF; 8] = [
    ColorF { r: 6.0 / 255.0, g: 153.0 / 255.0, b: 198.0 / 255.0, a: 0.7 },
    ColorF { r: 255.0 / 255.0, g: 212.0 / 255.0, b: 83.0 / 255.0, a: 0.7 },
    ColorF { r: 116.0 / 255.0, g: 29.0 / 255.0, b: 109.0 / 255.0, a: 0.7 },
    ColorF { r: 204.0 / 255.0, g: 158.0 / 255.0, b: 199.0 / 255.0, a: 0.7 },
    ColorF { r: 242.0 / 255.0, g: 46.0 / 255.0, b: 121.0 / 255.0, a: 0.7 },
    ColorF { r: 116.0 / 255.0, g: 203.0 / 255.0, b: 196.0 / 255.0, a: 0.7 },
    ColorF { r: 255.0 / 255.0, g: 249.0 / 255.0, b: 201.0 / 255.0, a: 0.7 },
    ColorF { r: 137.0 / 255.0, g: 196.0 / 255.0, b: 78.0 / 255.0, a: 0.7 },
];

fn get_cyclic<T>(arr: &[T], index: usize) -> &T {
    &arr[index % arr.len()]
}

#[derive(Debug)]
struct StackingContextInfo {
    children: Vec<StackingContext>,
    scroll_roots: Vec<ScrollRoot>,
}

impl StackingContextInfo {
    fn new() -> StackingContextInfo {
        StackingContextInfo {
            children: Vec::new(),
            scroll_roots: Vec::new(),
        }
    }

    fn take_children(&mut self) -> Vec<StackingContext> {
        mem::replace(&mut self.children, Vec::new())
    }
}

pub struct DisplayListBuildState<'a> {
    pub layout_context: &'a LayoutContext<'a>,
    pub root_stacking_context: StackingContext,
    pub items: HashMap<StackingContextId, Vec<DisplayItem>>,
    stacking_context_info: HashMap<StackingContextId, StackingContextInfo>,
    pub scroll_root_parents: HashMap<ClipId, ClipId>,
    pub processing_scroll_root_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 scroll root id, used to keep track of state when
    /// recursively building and processing the display list.
    pub current_scroll_root_id: ClipId,

    /// The scroll root id 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_scroll_root_id: ClipId,

    /// Vector containing iframe sizes, used to inform the constellation about
    /// new iframe sizes
    pub iframe_sizes: Vec<(BrowsingContextId, TypedSize2D<f32, CSSPixel>)>,

    /// 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 current transform style of the stacking context.
    current_transform_style: TransformStyle,
}

impl<'a> DisplayListBuildState<'a> {
    pub fn new(layout_context: &'a LayoutContext) -> DisplayListBuildState<'a> {
        DisplayListBuildState {
            layout_context: layout_context,
            root_stacking_context: StackingContext::root(layout_context.id),
            items: HashMap::new(),
            stacking_context_info: HashMap::new(),
            scroll_root_parents: HashMap::new(),
            processing_scroll_root_element: false,
            current_stacking_context_id: StackingContextId::root(),
            current_scroll_root_id: layout_context.id.root_scroll_node(),
            containing_block_scroll_root_id: layout_context.id.root_scroll_node(),
            iframe_sizes: Vec::new(),
            clip_stack: Vec::new(),
            containing_block_clip_stack: Vec::new(),
            current_transform_style: TransformStyle::Flat,
        }
    }

    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_stacking_context(&mut self,
                            parent_id: StackingContextId,
                            stacking_context: StackingContext) {
        self.current_transform_style = stacking_context.transform_style;
        let info = self.stacking_context_info
                       .entry(parent_id)
                       .or_insert(StackingContextInfo::new());
        info.children.push(stacking_context);
    }

    fn has_scroll_root(&mut self, id: ClipId) -> bool {
        self.scroll_root_parents.contains_key(&id)
    }

    fn add_scroll_root(&mut self, scroll_root: ScrollRoot, stacking_context_id: StackingContextId) {
        self.scroll_root_parents.insert(scroll_root.id, scroll_root.parent_id);
        let info = self.stacking_context_info
                       .entry(stacking_context_id)
                       .or_insert(StackingContextInfo::new());
        info.scroll_roots.push(scroll_root);
    }

    fn parent_scroll_root_id(&self, scroll_root_id: ClipId) -> ClipId {
        if scroll_root_id.is_root_scroll_node() {
            return scroll_root_id;
        }

        debug_assert!(self.scroll_root_parents.contains_key(&scroll_root_id));
        *self.scroll_root_parents.get(&scroll_root_id).unwrap()
    }

    fn create_base_display_item(&self,
                                bounds: &Rect<Au>,
                                clip: LocalClip,
                                node: OpaqueNode,
                                cursor: Option<Cursor>,
                                section: DisplayListSection)
                                -> BaseDisplayItem {
        let scroll_root_id = if (section == DisplayListSection::BackgroundAndBorders ||
                                 section == DisplayListSection::BlockBackgroundsAndBorders) &&
                                 self.processing_scroll_root_element {
            self.parent_scroll_root_id(self.current_scroll_root_id)
        } else {
            self.current_scroll_root_id
        };

        BaseDisplayItem::new(&bounds,
                             DisplayItemMetadata {
                                 node: node,
                                 pointing: cursor,
                             },
                             clip,
                             section,
                             self.current_stacking_context_id,
                             scroll_root_id)
    }

    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.layout_context.id));

        self.to_display_list_for_stacking_context(&mut list, root_context);

        DisplayList {
            list: list,
        }
    }

    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_or_else(StackingContextInfo::new);

        info.children.sort();

        let pipeline_id = self.layout_context.id;
        if stacking_context.context_type != StackingContextType::Real {
            list.extend(info.scroll_roots.into_iter().map(|root| root.to_define_item(pipeline_id)));
            self.to_display_list_for_items(list, child_items, info.children);
        } else {
            let (push_item, pop_item) = stacking_context.to_display_list_items(pipeline_id);
            list.push(push_item);
            list.extend(info.scroll_roots.into_iter().map(|root| root.to_define_item(pipeline_id)));
            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);
        }
    }
}

/// The logical width of an insertion point: at the moment, a one-pixel-wide line.
const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(1 * AU_PER_PX);

pub enum IdType {
    StackingContext,
    OverflowClip,
    CSSClip,
}

pub trait FragmentDisplayListBuilding {
    /// 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>);

    /// Computes the background size for an image with the given background area according to the
    /// rules in CSS-BACKGROUNDS § 3.9.
    fn compute_background_image_size(&self,
                                     style: &ComputedValues,
                                     bounds: &Rect<Au>,
                                     image: &WebRenderImageInfo, index: usize)
                                     -> Size2D<Au>;

    /// Adds the display items necessary to paint the background image of this fragment to the
    /// appropriate section of the display list.
    fn build_display_list_for_background_image(&self,
                                               state: &mut DisplayListBuildState,
                                               style: &ComputedValues,
                                               display_list_section: DisplayListSection,
                                               absolute_bounds: &Rect<Au>,
                                               clip: &LocalClip,
                                               image_url: &ServoUrl,
                                               background_index: usize);

    /// 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>,
                                              clip: &LocalClip,
                                              webrender_image: WebRenderImageInfo,
                                              index: usize);

    /// Adds the display items necessary to paint the background image created by this fragment's
    /// worklet to the appropriate section of the display list.
    fn build_display_list_for_background_paint_worklet(&self,
                                                       state: &mut DisplayListBuildState,
                                                       style: &ComputedValues,
                                                       display_list_section: DisplayListSection,
                                                       absolute_bounds: &Rect<Au>,
                                                       clip: &LocalClip,
                                                       paint_worklet: &PaintWorklet,
                                                       index: usize);

    fn convert_linear_gradient(&self,
                               bounds: &Rect<Au>,
                               stops: &[GradientItem],
                               direction: &LineDirection,
                               repeating: bool)
                               -> display_list::Gradient;

    fn convert_radial_gradient(&self,
                               bounds: &Rect<Au>,
                               stops: &[GradientItem],
                               shape: &EndingShape,
                               center: &Position,
                               repeating: bool)
                               -> display_list::RadialGradient;

    /// 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>,
                                                  clip: &LocalClip,
                                                  gradient: &Gradient,
                                                  style: &ComputedValues);

    /// 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,
            border_painting_mode: BorderPaintingMode,
            bounds: &Rect<Au>,
            display_list_section: DisplayListSection,
            clip: &Rect<Au>);

    /// Adds the display items necessary to paint the outline of this fragment to the display list
    /// if necessary.
    fn build_display_list_for_outline_if_applicable(&self,
                                                    state: &mut DisplayListBuildState,
                                                    style: &ComputedValues,
                                                    bounds: &Rect<Au>,
                                                    clip: &Rect<Au>);

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

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

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

    /// Adds the display items for this fragment to the given display list.
    ///
    /// Arguments:
    ///
    /// * `state`: The display building state, including the display list currently
    ///   under construction and other metadata useful for constructing it.
    /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
    /// * `stacking_relative_flow_origin`: Position of the origin of the owning flow with respect
    ///   to its nearest ancestor stacking context.
    /// * `relative_containing_block_size`: The size of the containing block that
    ///   `position: relative` makes use of.
    /// * `clip`: The region to clip the display items to.
    fn build_display_list(&mut self,
                          state: &mut DisplayListBuildState,
                          stacking_relative_flow_origin: &Vector2D<Au>,
                          relative_containing_block_size: &LogicalSize<Au>,
                          relative_containing_block_mode: WritingMode,
                          border_painting_mode: BorderPaintingMode,
                          display_list_section: DisplayListSection,
                          clip: &Rect<Au>);

    /// 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,
                                                      clip: &Rect<Au>);

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

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

    /// A helper method that `build_display_list` calls to create per-fragment-type display items.
    fn build_fragment_type_specific_display_items(&mut self,
                                                  state: &mut DisplayListBuildState,
                                                  stacking_relative_border_box: &Rect<Au>,
                                                  clip: &Rect<Au>);

    /// Creates a stacking context for associated fragment.
    fn create_stacking_context(&self,
                               id: StackingContextId,
                               base_flow: &BaseFlow,
                               scroll_policy: ScrollPolicy,
                               mode: StackingContextCreationMode,
                               parent_scroll_id: ClipId)
                               -> StackingContext;


    /// The id of stacking context this fragment would create.
    fn stacking_context_id(&self) -> StackingContextId;

    fn unique_id(&self, id_type: IdType) -> u64;

    fn fragment_type(&self) -> FragmentType;
}

fn handle_overlapping_radii(size: &Size2D<Au>, radii: &BorderRadii<Au>) -> BorderRadii<Au> {
    // No two corners' border radii may add up to more than the length of the edge
    // between them. To prevent that, all radii are scaled down uniformly.
    fn scale_factor(radius_a: Au, radius_b: Au, edge_length: Au) -> f32 {
        let required = radius_a + radius_b;

        if required <= edge_length {
            1.0
        } else {
            edge_length.to_f32_px() / required.to_f32_px()
        }
    }

    let top_factor = scale_factor(radii.top_left.width, radii.top_right.width, size.width);
    let bottom_factor = scale_factor(radii.bottom_left.width, radii.bottom_right.width, size.width);
    let left_factor = scale_factor(radii.top_left.height, radii.bottom_left.height, size.height);
    let right_factor = scale_factor(radii.top_right.height, radii.bottom_right.height, size.height);
    let min_factor = top_factor.min(bottom_factor).min(left_factor).min(right_factor);
    if min_factor < 1.0 {
        radii.scale_by(min_factor)
    } else {
        *radii
    }
}

fn build_border_radius(abs_bounds: &Rect<Au>,
                       border_style: &style_structs::Border)
                       -> BorderRadii<Au> {
    // TODO(cgaebel): Support border radii even in the case of multiple border widths.
    // This is an extension of supporting elliptical radii. For now, all percentage
    // radii will be relative to the width.

    handle_overlapping_radii(&abs_bounds.size, &BorderRadii {
        top_left:     model::specified_border_radius(border_style.border_top_left_radius,
                                                     abs_bounds.size),
        top_right:    model::specified_border_radius(border_style.border_top_right_radius,
                                                     abs_bounds.size),
        bottom_right: model::specified_border_radius(border_style.border_bottom_right_radius,
                                                     abs_bounds.size),
        bottom_left:  model::specified_border_radius(border_style.border_bottom_left_radius,
                                                     abs_bounds.size),
    })
}

/// Get the border radius for the rectangle inside of a rounded border. This is useful
/// for building the clip for the content inside the border.
fn build_border_radius_for_inner_rect(outer_rect: &Rect<Au>,
                                      style: &ComputedValues)
                                      -> BorderRadii<Au> {
    let mut radii = build_border_radius(&outer_rect, style.get_border());
    if radii.is_square() {
        return radii;
    }

    // Since we are going to using the inner rectangle (outer rectangle minus
    // border width), we need to adjust to border radius so that we are smaller
    // rectangle with the same border curve.
    let border_widths = style.logical_border_width().to_physical(style.writing_mode);
    radii.top_left.width = cmp::max(Au(0), radii.top_left.width - border_widths.left);
    radii.bottom_left.width = cmp::max(Au(0), radii.bottom_left.width - border_widths.left);

    radii.top_right.width = cmp::max(Au(0), radii.top_right.width - border_widths.right);
    radii.bottom_right.width = cmp::max(Au(0), radii.bottom_right.width - border_widths.right);

    radii.top_left.height = cmp::max(Au(0), radii.top_left.height - border_widths.top);
    radii.top_right.height = cmp::max(Au(0), radii.top_right.height - border_widths.top);

    radii.bottom_left.height = cmp::max(Au(0), radii.bottom_left.height - border_widths.bottom);
    radii.bottom_right.height = cmp::max(Au(0), radii.bottom_right.height - border_widths.bottom);
    radii
}

fn build_inner_border_box_for_border_rect(border_box: &Rect<Au>,
                                          style: &ComputedValues)
                                          -> Rect<Au> {
    let border_widths = style.logical_border_width().to_physical(style.writing_mode);
    let mut inner_border_box = *border_box;
    inner_border_box.origin.x += border_widths.left;
    inner_border_box.origin.y += border_widths.top;
    inner_border_box.size.width -= border_widths.right + border_widths.left;
    inner_border_box.size.height -= border_widths.bottom + border_widths.top;
    inner_border_box
}

fn convert_gradient_stops(gradient_items: &[GradientItem],
                          total_length: Au) -> Vec<GradientStop> {
    // Determine the position of each stop per CSS-IMAGES § 3.4.

    // Only keep the color stops, discard the color interpolation hints.
    let mut stop_items = gradient_items.iter().filter_map(|item| {
        match *item {
            GenericGradientItem::ColorStop(ref stop) => Some(*stop),
            _ => None,
        }
    }).collect::<Vec<_>>();

    assert!(stop_items.len() >= 2);

    // Run the algorithm from
    // https://drafts.csswg.org/css-images-3/#color-stop-syntax

    // Step 1:
    // If the first color stop does not have a position, set its position to 0%.
    {
        let first = stop_items.first_mut().unwrap();
        if first.position.is_none() {
            first.position = Some(LengthOrPercentage::Percentage(Percentage(0.0)));
        }
    }
    // If the last color stop does not have a position, set its position to 100%.
    {
        let last = stop_items.last_mut().unwrap();
        if last.position.is_none() {
            last.position = Some(LengthOrPercentage::Percentage(Percentage(1.0)));
        }
    }

    // Step 2: Move any stops placed before earlier stops to the
    // same position as the preceding stop.
    let mut last_stop_position = stop_items.first().unwrap().position.unwrap();
    for stop in stop_items.iter_mut().skip(1) {
        if let Some(pos) = stop.position {
            if position_to_offset(last_stop_position, total_length)
                    > position_to_offset(pos, total_length) {
                stop.position = Some(last_stop_position);
            }
            last_stop_position = stop.position.unwrap();
        }
    }

    // Step 3: Evenly space stops without position.
    // Note: Remove the + 2 if fix_gradient_stops is changed.
    let mut stops = Vec::with_capacity(stop_items.len() + 2);
    let mut stop_run = None;
    for (i, stop) in stop_items.iter().enumerate() {
        let offset = match stop.position {
            None => {
                if stop_run.is_none() {
                    // Initialize a new stop run.
                    // `unwrap()` here should never fail because this is the beginning of
                    // a stop run, which is always bounded by a length or percentage.
                    let start_offset =
                        position_to_offset(stop_items[i - 1].position.unwrap(), total_length);
                    // `unwrap()` here should never fail because this is the end of
                    // a stop run, which is always bounded by a length or percentage.
                    let (end_index, end_stop) = stop_items[(i + 1)..]
                        .iter()
                        .enumerate()
                        .find(|&(_, ref stop)| stop.position.is_some())
                        .unwrap();
                    let end_offset = position_to_offset(end_stop.position.unwrap(), total_length);
                    stop_run = Some(StopRun {
                        start_offset: start_offset,
                        end_offset: end_offset,
                        start_index: i - 1,
                        stop_count: end_index,
                    })
                }

                let stop_run = stop_run.unwrap();
                let stop_run_length = stop_run.end_offset - stop_run.start_offset;
                stop_run.start_offset +
                    stop_run_length * (i - stop_run.start_index) as f32 /
                        ((2 + stop_run.stop_count) as f32)
            }
            Some(position) => {
                stop_run = None;
                position_to_offset(position, total_length)
            }
        };
        stops.push(GradientStop {
            offset: offset,
            color: stop.color.to_gfx_color()
        })
    }
    stops
}

#[inline]
/// Duplicate the first and last stops if necessary.
///
/// Explanation by pyfisch:
/// If the last stop is at the same position as the previous stop the
/// last color is ignored by webrender. This differs from the spec
/// (I think so). The  implementations of Chrome and Firefox seem
/// to have the same problem but work fine if the position of the last
/// stop is smaller than 100%. (Otherwise they ignore the last stop.)
///
/// Similarly the first stop is duplicated if it is not placed
/// at the start of the virtual gradient ray.
fn fix_gradient_stops(stops: &mut Vec<GradientStop>) {
    if stops.first().unwrap().offset > 0.0 {
        let color = stops.first().unwrap().color;
        stops.insert(0, GradientStop {
            offset: 0.0,
            color: color,
        })
    }
    if stops.last().unwrap().offset < 1.0 {
        let color = stops.last().unwrap().color;
        stops.push(GradientStop {
            offset: 1.0,
            color: color,
        })
    }
}

/// Returns the the distance to the nearest or farthest corner depending on the comperator.
fn get_distance_to_corner<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Au
    where F: Fn(Au, Au) -> Au
{
    let dist = get_distance_to_sides(size, center, cmp);
    Au::from_f32_px(dist.width.to_f32_px().hypot(dist.height.to_f32_px()))
}

/// Returns the distance to the nearest or farthest sides depending on the comparator.
///
/// The first return value is horizontal distance the second vertical distance.
fn get_distance_to_sides<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
    where F: Fn(Au, Au) -> Au
{
    let top_side = center.y;
    let right_side = size.width - center.x;
    let bottom_side = size.height - center.y;
    let left_side = center.x;
    Size2D::new(cmp(left_side, right_side), cmp(top_side, bottom_side))
}

/// Returns the radius for an ellipse with the same ratio as if it was matched to the sides.
fn get_ellipse_radius<F>(size: &Size2D<Au>, center: &Point2D<Au>, cmp: F) -> Size2D<Au>
    where F: Fn(Au, Au) -> Au
{
    let dist = get_distance_to_sides(size, center, cmp);
    Size2D::new(dist.width.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0),
                dist.height.scale_by(::std::f32::consts::FRAC_1_SQRT_2 * 2.0))
}

/// Determines the radius of a circle if it was not explictly provided.
/// https://drafts.csswg.org/css-images-3/#typedef-size
fn convert_circle_size_keyword(keyword: ShapeExtent,
                               size: &Size2D<Au>,
                               center: &Point2D<Au>) -> Size2D<Au> {
    let radius = match keyword {
        ShapeExtent::ClosestSide | ShapeExtent::Contain => {
            let dist = get_distance_to_sides(size, center, ::std::cmp::min);
            ::std::cmp::min(dist.width, dist.height)
        }
        ShapeExtent::FarthestSide => {
            let dist = get_distance_to_sides(size, center, ::std::cmp::max);
            ::std::cmp::max(dist.width, dist.height)
        }
        ShapeExtent::ClosestCorner => {
            get_distance_to_corner(size, center, ::std::cmp::min)
        },
        ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
            get_distance_to_corner(size, center, ::std::cmp::max)
        },
    };
    Size2D::new(radius, radius)
}

/// Determines the radius of an ellipse if it was not explictly provided.
/// https://drafts.csswg.org/css-images-3/#typedef-size
fn convert_ellipse_size_keyword(keyword: ShapeExtent,
                                size: &Size2D<Au>,
                                center: &Point2D<Au>) -> Size2D<Au> {
    match keyword {
        ShapeExtent::ClosestSide | ShapeExtent::Contain => {
            get_distance_to_sides(size, center, ::std::cmp::min)
        },
        ShapeExtent::FarthestSide => {
            get_distance_to_sides(size, center, ::std::cmp::max)
        },
        ShapeExtent::ClosestCorner => {
            get_ellipse_radius(size, center, ::std::cmp::min)
        },
        ShapeExtent::FarthestCorner | ShapeExtent::Cover => {
            get_ellipse_radius(size, center, ::std::cmp::max)
        },
    }
}

impl FragmentDisplayListBuilding for Fragment {
    fn build_display_list_for_background_if_applicable(&self,
                                                       state: &mut DisplayListBuildState,
                                                       style: &ComputedValues,
                                                       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".
        let background = style.get_background();
        let background_color = style.resolve_color(background.background_color);

        // 'background-clip' determines the area within which the background is painted.
        // http://dev.w3.org/csswg/css-backgrounds-3/#the-background-clip
        let mut bounds = *absolute_bounds;

        // This is the clip for the color (which is the last element in the bg array)
        let color_clip = get_cyclic(&background.background_clip.0,
                                    background.background_image.0.len() - 1);

        match *color_clip {
            background_clip::single_value::T::border_box => {}
            background_clip::single_value::T::padding_box => {
                let border = style.logical_border_width().to_physical(style.writing_mode);
                bounds.origin.x = bounds.origin.x + border.left;
                bounds.origin.y = bounds.origin.y + border.top;
                bounds.size.width = bounds.size.width - border.horizontal();
                bounds.size.height = bounds.size.height - border.vertical();
            }
            background_clip::single_value::T::content_box => {
                let border_padding = self.border_padding.to_physical(style.writing_mode);
                bounds.origin.x = bounds.origin.x + border_padding.left;
                bounds.origin.y = bounds.origin.y + border_padding.top;
                bounds.size.width = bounds.size.width - border_padding.horizontal();
                bounds.size.height = bounds.size.height - border_padding.vertical();
            }
        }

        // Adjust the clipping region as necessary to account for `border-radius`.
        let border_radii = build_border_radius(absolute_bounds, style.get_border());
        let clip = if !border_radii.is_square() {
            LocalClip::RoundedRect(bounds.to_rectf(), ComplexClipRegion::new(
                bounds.to_rectf(),
                border_radii.to_border_radius(),
             ))
        } else {
            LocalClip::Rect(bounds.to_rectf())
        };


        let base = state.create_base_display_item(&bounds,
                                                  clip,
                                                  self.node,
                                                  style.get_cursor(Cursor::Default),
                                                  display_list_section);
        state.add_display_item(
            DisplayItem::SolidColor(box SolidColorDisplayItem {
                base: base,
                color: background_color.to_gfx_color(),
            }));

        // The background image is painted on top of the background color.
        // Implements background image, per spec:
        // http://www.w3.org/TR/CSS21/colors.html#background
        let background = style.get_background();
        for (i, background_image) in background.background_image.0.iter().enumerate().rev() {
            match *background_image {
                Either::First(_) => {}
                Either::Second(Image::Gradient(ref gradient)) => {
                    self.build_display_list_for_background_gradient(state,
                                                                    display_list_section,
                                                                    &absolute_bounds,
                                                                    &clip,
                                                                    gradient,
                                                                    style);
                }
                Either::Second(Image::Url(ref image_url)) => {
                    if let Some(url) = image_url.url() {
                        self.build_display_list_for_background_image(state,
                                                                     style,
                                                                     display_list_section,
                                                                     &bounds,
                                                                     &clip,
                                                                     url,
                                                                     i);
                    }
                }
                Either::Second(Image::PaintWorklet(ref paint_worklet)) => {
                    self.build_display_list_for_background_paint_worklet(state,
                                                                         style,
                                                                         display_list_section,
                                                                         &bounds,
                                                                         &clip,
                                                                         paint_worklet,
                                                                         i);
                }
                Either::Second(Image::Rect(_)) => {
                    // TODO: Implement `-moz-image-rect`
                }
                Either::Second(Image::Element(_)) => {
                    // TODO: Implement `-moz-element`
                }
            }
        }
    }

    fn compute_background_image_size(&self,
                                     style: &ComputedValues,
                                     bounds: &Rect<Au>,
                                     image: &WebRenderImageInfo,
                                     index: usize)
                                     -> Size2D<Au> {
        // If `image_aspect_ratio` < `bounds_aspect_ratio`, the image is tall; otherwise, it is
        // wide.
        let image_aspect_ratio = (image.width as f64) / (image.height as f64);
        let bounds_aspect_ratio = bounds.size.width.to_f64_px() / bounds.size.height.to_f64_px();
        let intrinsic_size = Size2D::new(Au::from_px(image.width as i32),
                                         Au::from_px(image.height as i32));
        let background_size = get_cyclic(&style.get_background().background_size.0, index).clone();
        match (background_size, image_aspect_ratio < bounds_aspect_ratio) {
            (BackgroundSize::Contain, false) | (BackgroundSize::Cover, true) => {
                Size2D::new(bounds.size.width,
                            Au::from_f64_px(bounds.size.width.to_f64_px() / image_aspect_ratio))
            }

            (BackgroundSize::Contain, true) | (BackgroundSize::Cover, false) => {
                Size2D::new(Au::from_f64_px(bounds.size.height.to_f64_px() * image_aspect_ratio),
                            bounds.size.height)
            }

            (BackgroundSize::Explicit { width, height: LengthOrPercentageOrAuto::Auto }, _) => {
                let width = MaybeAuto::from_style(width, bounds.size.width)
                                      .specified_or_default(intrinsic_size.width);
                Size2D::new(width, Au::from_f64_px(width.to_f64_px() / image_aspect_ratio))
            }

            (BackgroundSize::Explicit { width: LengthOrPercentageOrAuto::Auto, height }, _) => {
                let height = MaybeAuto::from_style(height, bounds.size.height)
                                       .specified_or_default(intrinsic_size.height);
                Size2D::new(Au::from_f64_px(height.to_f64_px() * image_aspect_ratio), height)
            }

            (BackgroundSize::Explicit { width, height }, _) => {
                Size2D::new(MaybeAuto::from_style(width, bounds.size.width)
                                 .specified_or_default(intrinsic_size.width),
                       MaybeAuto::from_style(height, bounds.size.height)
                                 .specified_or_default(intrinsic_size.height))
            }
        }
    }

    fn build_display_list_for_background_image(&self,
                                               state: &mut DisplayListBuildState,
                                               style: &ComputedValues,
                                               display_list_section: DisplayListSection,
                                               absolute_bounds: &Rect<Au>,
                                               clip: &LocalClip,
                                               image_url: &ServoUrl,
                                               index: usize) {
        let webrender_image = state.layout_context
                                   .get_webrender_image_for_url(self.node,
                                                                image_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,
                                                        clip,
                                                        webrender_image,
                                                        index);
        }
    }

    fn build_display_list_for_webrender_image(&self,
                                              state: &mut DisplayListBuildState,
                                              style: &ComputedValues,
                                              display_list_section: DisplayListSection,
                                              absolute_bounds: &Rect<Au>,
                                              clip: &LocalClip,
                                              webrender_image: WebRenderImageInfo,
                                              index: usize) {
        debug!("(building display list) building background image");
        let background = style.get_background();

        // Use `background-size` to get the size.
        let mut bounds = *absolute_bounds;
        let image_size = self.compute_background_image_size(style, &bounds,
                                                            &webrender_image, index);

        // Background image should be positioned on the padding box basis.
        let border = style.logical_border_width().to_physical(style.writing_mode);

        // Use 'background-origin' to get the origin value.
        let origin = get_cyclic(&background.background_origin.0, index);
        let (mut origin_x, mut origin_y) = match *origin {
            background_origin::single_value::T::padding_box => {
                (Au(0), Au(0))
            }
            background_origin::single_value::T::border_box => {
                (-border.left, -border.top)
            }
            background_origin::single_value::T::content_box => {
                let border_padding = self.border_padding.to_physical(self.style.writing_mode);
                (border_padding.left - border.left, border_padding.top - border.top)
            }
        };

        // Use `background-attachment` to get the initial virtual origin
        let attachment = get_cyclic(&background.background_attachment.0, index);
        let (virtual_origin_x, virtual_origin_y) = match *attachment {
            background_attachment::single_value::T::scroll => {
                (absolute_bounds.origin.x, absolute_bounds.origin.y)
            }
            background_attachment::single_value::T::fixed => {
                // If the ‘background-attachment’ value for this image is ‘fixed’, then
                // 'background-origin' has no effect.
                origin_x = Au(0);
                origin_y = Au(0);
                (Au(0), Au(0))
            }
        };

        let horiz_position = *get_cyclic(&background.background_position_x.0, index);
        let vert_position = *get_cyclic(&background.background_position_y.0, index);
        // Use `background-position` to get the offset.
        let horizontal_position = horiz_position.to_used_value(bounds.size.width - image_size.width);
        let vertical_position = vert_position.to_used_value(bounds.size.height - image_size.height);

        // The anchor position for this background, based on both the background-attachment
        // and background-position properties.
        let anchor_origin_x = border.left + virtual_origin_x + origin_x + horizontal_position;
        let anchor_origin_y = border.top + virtual_origin_y + origin_y + vertical_position;

        let mut tile_spacing = Size2D::zero();
        let mut stretch_size = image_size;

        // Adjust origin and size based on background-repeat
        let background_repeat = get_cyclic(&background.background_repeat.0, index);
        match background_repeat.0 {
            background_repeat::single_value::RepeatKeyword::NoRepeat => {
                bounds.origin.x = anchor_origin_x;
                bounds.size.width = image_size.width;
            }
            background_repeat::single_value::RepeatKeyword::Repeat => {
                ImageFragmentInfo::tile_image(&mut bounds.origin.x,
                                              &mut bounds.size.width,
                                              anchor_origin_x,
                                              image_size.width);
            }
            background_repeat::single_value::RepeatKeyword::Space => {
                ImageFragmentInfo::tile_image_spaced(&mut bounds.origin.x,
                                                     &mut bounds.size.width,
                                                     &mut tile_spacing.width,
                                                     anchor_origin_x,
                                                     image_size.width);

            }
            background_repeat::single_value::RepeatKeyword::Round => {
                ImageFragmentInfo::tile_image_round(&mut bounds.origin.x,
                                                    &mut bounds.size.width,
                                                    anchor_origin_x,
                                                    &mut stretch_size.width);
            }
        };
        match background_repeat.1 {
            background_repeat::single_value::RepeatKeyword::NoRepeat => {
                bounds.origin.y = anchor_origin_y;
                bounds.size.height = image_size.height;
            }
            background_repeat::single_value::RepeatKeyword::Repeat => {
                ImageFragmentInfo::tile_image(&mut bounds.origin.y,
                                              &mut bounds.size.height,
                                              anchor_origin_y,
                                              image_size.height);
            }
            background_repeat::single_value::RepeatKeyword::Space => {
                ImageFragmentInfo::tile_image_spaced(&mut bounds.origin.y,
                                                     &mut bounds.size.height,
                                                     &mut tile_spacing.height,
                                                     anchor_origin_y,
                                                     image_size.height);

            }
            background_repeat::single_value::RepeatKeyword::Round => {
                ImageFragmentInfo::tile_image_round(&mut bounds.origin.y,
                                                    &mut bounds.size.height,
                                                    anchor_origin_y,
                                                    &mut stretch_size.height);
            }
        };

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

        debug!("(building display list) adding background image.");
        state.add_display_item(DisplayItem::Image(box ImageDisplayItem {
            base: base,
            webrender_image: webrender_image,
            image_data: None,
            stretch_size: stretch_size,
            tile_spacing: tile_spacing,
            image_rendering: style.get_inheritedbox().image_rendering.clone(),
        }));

    }

    fn build_display_list_for_background_paint_worklet(&self,
                                                       state: &mut DisplayListBuildState,
                                                       style: &ComputedValues,
                                                       display_list_section: DisplayListSection,
                                                       absolute_bounds: &Rect<Au>,
                                                       clip: &LocalClip,
                                                       paint_worklet: &PaintWorklet,
                                                       index: usize)
    {
        // This should be the "concrete object size" of the fragment.
        // https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image
        // https://drafts.csswg.org/css-images-3/#concrete-object-size
        // Experimentally, chrome is using the size in px of the box,
        // including padding, but not border or margin, so we follow suit.
        // https://github.com/w3c/css-houdini-drafts/issues/417
        let unbordered_box = self.border_box - style.logical_border_width();
        let size = unbordered_box.size.to_physical(style.writing_mode);
        let name = paint_worklet.name.clone();

        // Get the painter, and the computed values for its properties.
        let (properties, painter) = match state.layout_context.registered_painters.read().get(&name) {
            Some(registered_painter) => (
                registered_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(),
                registered_painter.painter.clone()
            ),
            None => return debug!("Worklet {} called before registration.", name),
        };

        // TODO: add a one-place cache to avoid drawing the paint image every time.
        // https://github.com/servo/servo/issues/17369
        debug!("Drawing a paint image {}({},{}).", name, size.width.to_px(), size.height.to_px());
        let (sender, receiver) = ipc::channel().unwrap();
        painter.draw_a_paint_image(size, properties, sender);

        // TODO: timeout
        let webrender_image = match receiver.recv() {
            Ok(CanvasData::Image(canvas_data)) => {
                WebRenderImageInfo {
                    // TODO: it would be nice to get this data back from the canvas
                    width: size.width.to_px().abs() as u32,
                    height: size.height.to_px().abs() as u32,
                    format: PixelFormat::BGRA8,
                    key: Some(canvas_data.image_key),
                }
            },
            Ok(CanvasData::WebGL(_)) => return warn!("Paint worklet generated WebGL."),
            Err(err) => return warn!("Paint worklet recv generated error ({}).", err),
        };

        self.build_display_list_for_webrender_image(state,
                                                    style,
                                                    display_list_section,
                                                    absolute_bounds,
                                                    clip,
                                                    webrender_image,
                                                    index);
    }

    fn convert_linear_gradient(&self,
                               bounds: &Rect<Au>,
                               stops: &[GradientItem],
                               direction: &LineDirection,
                               repeating: bool)
                               -> display_list::Gradient {
        let angle = match *direction {
            LineDirection::Angle(angle) => angle.radians(),
            LineDirection::Horizontal(x) => {
                match x {
                    X::Left => Angle::Degree(270.).radians(),
                    X::Right => Angle::Degree(90.).radians(),
                }
            },
            LineDirection::Vertical(y) => {
                match y {
                    Y::Top => Angle::Degree(0.).radians(),
                    Y::Bottom => Angle::Degree(180.).radians(),
                }
            },
            LineDirection::Corner(horizontal, vertical) => {
                // This the angle for one of the diagonals of the box. Our angle
                // will either be this one, this one + PI, or one of the other
                // two perpendicular angles.
                let atan = (bounds.size.height.to_f32_px() /
                            bounds.size.width.to_f32_px()).atan();
                match (horizontal, vertical) {
                    (X::Right, Y::Bottom)
                        => f32::consts::PI - atan,
                    (X::Left, Y::Bottom)
                        => f32::consts::PI + atan,
                    (X::Right, Y::Top)
                        => atan,
                    (X::Left, Y::Top)
                        => -atan,
                }
            }
        };

        // Get correct gradient line length, based on:
        // https://drafts.csswg.org/css-images-3/#linear-gradients
        let dir = Point2D::new(angle.sin(), -angle.cos());

        let line_length = (dir.x * bounds.size.width.to_f32_px()).abs() +
                          (dir.y * bounds.size.height.to_f32_px()).abs();

        let inv_dir_length = 1.0 / (dir.x * dir.x + dir.y * dir.y).sqrt();

        // This is the vector between the center and the ending point; i.e. half
        // of the distance between the starting point and the ending point.
        let delta = Vector2D::new(Au::from_f32_px(dir.x * inv_dir_length * line_length / 2.0),
                                  Au::from_f32_px(dir.y * inv_dir_length * line_length / 2.0));

        // This is the length of the gradient line.
        let length = Au::from_f32_px(
            (delta.x.to_f32_px() * 2.0).hypot(delta.y.to_f32_px() * 2.0));

        let mut stops = convert_gradient_stops(stops, length);

        // Only clamped gradients need to be fixed because in repeating gradients
        // there is no "first" or "last" stop because they repeat infinitly in
        // both directions, so the rendering is always correct.
        if !repeating {
            fix_gradient_stops(&mut stops);
        }

        let center = Point2D::new(bounds.size.width / 2, bounds.size.height / 2);

        display_list::Gradient {
            start_point: center - delta,
            end_point: center + delta,
            stops: stops,
            repeating: repeating,
        }
    }

    fn convert_radial_gradient(&self,
                               bounds: &Rect<Au>,
                               stops: &[GradientItem],
                               shape: &EndingShape,
                               center: &Position,
                               repeating: bool)
                               -> display_list::RadialGradient {
        let center = Point2D::new(center.horizontal.to_used_value(bounds.size.width),
                                  center.vertical.to_used_value(bounds.size.height));
        let radius = match *shape {
            GenericEndingShape::Circle(Circle::Radius(length)) => {
                Size2D::new(length, length)
            },
            GenericEndingShape::Circle(Circle::Extent(extent)) => {
                convert_circle_size_keyword(extent, &bounds.size, &center)
            },
            GenericEndingShape::Ellipse(Ellipse::Radii(x, y)) => {
                Size2D::new(x.to_used_value(bounds.size.width), y.to_used_value(bounds.size.height))
            },
            GenericEndingShape::Ellipse(Ellipse::Extent(extent)) => {
                convert_ellipse_size_keyword(extent, &bounds.size, &center)
            },
        };

        let mut stops = convert_gradient_stops(stops, radius.width);
        // Repeating gradients have no last stops that can be ignored. So
        // fixup is not necessary but may actually break the gradient.
        if !repeating {
            fix_gradient_stops(&mut stops);
        }

        display_list::RadialGradient {
            center: center,
            radius: radius,
            stops: stops,
            repeating: repeating,
        }
    }

    fn build_display_list_for_background_gradient(&self,
                                                  state: &mut DisplayListBuildState,
                                                  display_list_section: DisplayListSection,
                                                  absolute_bounds: &Rect<Au>,
                                                  clip: &LocalClip,
                                                  gradient: &Gradient,
                                                  style: &ComputedValues) {
        let border = self.border_width().to_physical(style.writing_mode);
        let mut bounds = *absolute_bounds;
        bounds.origin.x = bounds.origin.x + border.left;
        bounds.origin.y = bounds.origin.y + border.top;
        bounds.size.width = bounds.size.width - border.horizontal();
        bounds.size.height = bounds.size.height - border.vertical();

        let base = state.create_base_display_item(&bounds,
                                                  *clip,
                                                  self.node,
                                                  style.get_cursor(Cursor::Default),
                                                  display_list_section);

        let display_item = match gradient.kind {
            GradientKind::Linear(ref angle_or_corner) => {
                let gradient = self.convert_linear_gradient(&bounds,
                                                            &gradient.items[..],
                                                            angle_or_corner,
                                                            gradient.repeating);
                DisplayItem::Gradient(box GradientDisplayItem {
                    base: base,
                    gradient: gradient,
                })
            }
            GradientKind::Radial(ref shape, ref center, _angle) => {
                let gradient = self.convert_radial_gradient(&bounds,
                                                            &gradient.items[..],
                                                            shape,
                                                            center,
                                                            gradient.repeating);
                DisplayItem::RadialGradient(box RadialGradientDisplayItem {
                    base: base,
                    gradient: gradient,
                })
            }
        };
        state.add_display_item(display_item);
    }

    fn build_display_list_for_box_shadow_if_applicable(&self,
                                                       state: &mut DisplayListBuildState,
                                                       style: &ComputedValues,
                                                       display_list_section: DisplayListSection,
                                                       absolute_bounds: &Rect<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 bounds = shadow_bounds(
                &absolute_bounds.translate(&Vector2D::new(
                    box_shadow.base.horizontal,
                    box_shadow.base.vertical,
                )),
                box_shadow.base.blur,
                box_shadow.spread,
            );

            // TODO(pcwalton): Multiple border radii; elliptical border radii.
            let base = state.create_base_display_item(&bounds,
                                                      LocalClip::from(clip.to_rectf()),
                                                      self.node,
                                                      style.get_cursor(Cursor::Default),
                                                      display_list_section);
            state.add_display_item(DisplayItem::BoxShadow(box BoxShadowDisplayItem {
                base: base,
                box_bounds: *absolute_bounds,
                color: style.resolve_color(box_shadow.base.color).to_gfx_color(),
                offset: Vector2D::new(box_shadow.base.horizontal, box_shadow.base.vertical),
                blur_radius: box_shadow.base.blur,
                spread_radius: box_shadow.spread,
                border_radius: model::specified_border_radius(style.get_border()
                                                                   .border_top_left_radius,
                                                              absolute_bounds.size).width,
                clip_mode: if box_shadow.inset {
                    BoxShadowClipMode::Inset
                } else {
                    BoxShadowClipMode::Outset
                },
            }));
        }
    }

    fn build_display_list_for_borders_if_applicable(
            &self,
            state: &mut DisplayListBuildState,
            style: &ComputedValues,
            border_painting_mode: BorderPaintingMode,
            bounds: &Rect<Au>,
            display_list_section: DisplayListSection,
            clip: &Rect<Au>) {
        let mut border = style.logical_border_width();

        match border_painting_mode {
            BorderPaintingMode::Separate => {}
            BorderPaintingMode::Collapse(collapsed_borders) => {
                collapsed_borders.adjust_border_widths_for_painting(&mut border)
            }
            BorderPaintingMode::Hidden => return,
        }
        if border.is_zero() {
            return
        }

        let border_style_struct = style.get_border();
        let mut colors = SideOffsets2D::new(border_style_struct.border_top_color,
                                            border_style_struct.border_right_color,
                                            border_style_struct.border_bottom_color,
                                            border_style_struct.border_left_color);
        let mut border_style = SideOffsets2D::new(border_style_struct.border_top_style,
                                                  border_style_struct.border_right_style,
                                                  border_style_struct.border_bottom_style,
                                                  border_style_struct.border_left_style);
        if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode {
            collapsed_borders.adjust_border_colors_and_styles_for_painting(&mut colors,
                                                                           &mut border_style,
                                                                           style.writing_mode);
        }

        let colors = SideOffsets2D::new(style.resolve_color(colors.top),
                                        style.resolve_color(colors.right),
                                        style.resolve_color(colors.bottom),
                                        style.resolve_color(colors.left));

        // If this border collapses, then we draw outside the boundaries we were given.
        let mut bounds = *bounds;
        if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode {
            collapsed_borders.adjust_border_bounds_for_painting(&mut bounds, style.writing_mode)
        }

        // Append the border to the display list.
        let base = state.create_base_display_item(&bounds,
                                                  LocalClip::from(clip.to_rectf()),
                                                  self.node,
                                                  style.get_cursor(Cursor::Default),
                                                  display_list_section);

        match border_style_struct.border_image_source {
            Either::First(_) => {
                state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
                    base: base,
                    border_widths: border.to_physical(style.writing_mode),
                    details: BorderDetails::Normal(NormalBorder {
                        color: SideOffsets2D::new(colors.top.to_gfx_color(),
                                                  colors.right.to_gfx_color(),
                                                  colors.bottom.to_gfx_color(),
                                                  colors.left.to_gfx_color()),
                        style: border_style,
                        radius: build_border_radius(&bounds, border_style_struct),
                    }),
                }));
            }
            Either::Second(Image::Gradient(ref gradient)) => {
                match gradient.kind {
                    GradientKind::Linear(angle_or_corner) => {
                        let grad = self.convert_linear_gradient(&bounds,
                                                                &gradient.items[..],
                                                                &angle_or_corner,
                                                                gradient.repeating);

                        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
                            base: base,
                            border_widths: border.to_physical(style.writing_mode),
                            details: BorderDetails::Gradient(display_list::GradientBorder {
                                gradient: grad,

                                // TODO(gw): Support border-image-outset
                                outset: SideOffsets2D::zero(),
                            }),
                        }));
                    }
                    GradientKind::Radial(ref shape, ref center, _angle) => {
                        let grad = self.convert_radial_gradient(&bounds,
                                                                &gradient.items[..],
                                                                shape,
                                                                center,
                                                                gradient.repeating);
                        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
                            base: base,
                            border_widths: border.to_physical(style.writing_mode),
                            details: BorderDetails::RadialGradient(
                                display_list::RadialGradientBorder {
                                    gradient: grad,

                                    // TODO(gw): Support border-image-outset
                                    outset: SideOffsets2D::zero(),
                                }),
                        }));
                    }
                }
            }
            Either::Second(Image::PaintWorklet(..)) => {
                // TODO: Handle border-image with `paint()`.
            }
            Either::Second(Image::Rect(..)) => {
                // TODO: Handle border-image with `-moz-image-rect`.
            }
            Either::Second(Image::Element(..)) => {
                // TODO: Handle border-image with `-moz-element`.
            }
            Either::Second(Image::Url(ref image_url)) => {
                if let Some(url) = image_url.url() {
                    let webrender_image = state.layout_context
                                               .get_webrender_image_for_url(self.node,
                                                                            url.clone(),
                                                                            UsePlaceholder::No);
                    if let Some(webrender_image) = webrender_image {
                        let corners = &border_style_struct.border_image_slice.offsets;

                        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
                            base: base,
                            border_widths: border.to_physical(style.writing_mode),
                            details: BorderDetails::Image(ImageBorder {
                                image: webrender_image,
                                fill: border_style_struct.border_image_slice.fill,
                                slice: SideOffsets2D::new(corners.0.resolve(webrender_image.height),
                                                          corners.1.resolve(webrender_image.width),
                                                          corners.2.resolve(webrender_image.height),
                                                          corners.3.resolve(webrender_image.width)),
                                // TODO(gw): Support border-image-outset
                                outset: SideOffsets2D::zero(),
                                repeat_horizontal: convert_repeat_mode(border_style_struct.border_image_repeat.0),
                                repeat_vertical: convert_repeat_mode(border_style_struct.border_image_repeat.1),
                            }),
                        }));
                    }
                }
            }
        }
    }

    fn build_display_list_for_outline_if_applicable(&self,
                                                    state: &mut DisplayListBuildState,
                                                    style: &ComputedValues,
                                                    bounds: &Rect<Au>,
                                                    clip: &Rect<Au>) {
        use style::values::Either;

        let width = style.get_outline().outline_width;
        if width == Au(0) {
            return
        }

        let outline_style = match style.get_outline().outline_style {
            Either::First(_auto) => border_style::T::solid,
            Either::Second(border_style::T::none) => return,
            Either::Second(border_style) => border_style
        };

        // Outlines are not accounted for in the dimensions of the border box, so adjust the
        // absolute bounds.
        let mut bounds = *bounds;
        let offset = width + style.get_outline().outline_offset;
        bounds.origin.x = bounds.origin.x - offset;
        bounds.origin.y = bounds.origin.y - offset;
        bounds.size.width = bounds.size.width + offset + offset;
        bounds.size.height = bounds.size.height + offset + offset;

        // Append the outline to the display list.
        let color = style.resolve_color(style.get_outline().outline_color).to_gfx_color();
        let base = state.create_base_display_item(&bounds,
                                                  LocalClip::from(clip.to_rectf()),
                                                  self.node,
                                                  style.get_cursor(Cursor::Default),
                                                  DisplayListSection::Outlines);
        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
            base: base,
            border_widths: SideOffsets2D::new_all_same(width),
            details: BorderDetails::Normal(NormalBorder {
                color: SideOffsets2D::new_all_same(color),
                style: SideOffsets2D::new_all_same(outline_style),
                radius: Default::default(),
            }),
        }));
    }

    fn build_debug_borders_around_text_fragments(&self,
                                                 state: &mut DisplayListBuildState,
                                                 style: &ComputedValues,
                                                 stacking_relative_border_box: &Rect<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(stacking_relative_border_box,
                                                  LocalClip::from(clip.to_rectf()),
                                                  self.node,
                                                  style.get_cursor(Cursor::Default),
                                                  DisplayListSection::Content);
        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
            base: base,
            border_widths: SideOffsets2D::new_all_same(Au::from_px(1)),
            details: BorderDetails::Normal(NormalBorder {
                color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)),
                style: SideOffsets2D::new_all_same(border_style::T::solid),
                radius: Default::default(),
            }),
        }));

        // Draw a rectangle representing the baselines.
        let mut baseline = LogicalRect::from_physical(self.style.writing_mode,
                                                      *stacking_relative_content_box,
                                                      container_size);
        baseline.start.b = baseline.start.b + text_fragment.run.ascent();
        baseline.size.block = Au(0);
        let baseline = baseline.to_physical(self.style.writing_mode, container_size);

        let base = state.create_base_display_item(&baseline,
                                                  LocalClip::from(clip.to_rectf()),
                                                  self.node,
                                                  style.get_cursor(Cursor::Default),
                                                  DisplayListSection::Content);
        state.add_display_item(DisplayItem::Line(box LineDisplayItem {
            base: base,
            color: ColorF::rgb(0, 200, 0),
            style: border_style::T::dashed,
        }));
    }

    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(stacking_relative_border_box,
                                                  LocalClip::from(clip.to_rectf()),
                                                  self.node,
                                                  self.style.get_cursor(Cursor::Default),
                                                  DisplayListSection::Content);
        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
            base: base,
            border_widths: SideOffsets2D::new_all_same(Au::from_px(1)),
            details: BorderDetails::Normal(NormalBorder {
                color: SideOffsets2D::new_all_same(ColorF::rgb(0, 0, 200)),
                style: SideOffsets2D::new_all_same(border_style::T::solid),
                radius: Default::default(),
            }),
        }));
    }

    fn build_display_items_for_selection_if_necessary(&self,
                                                      state: &mut DisplayListBuildState,
                                                      stacking_relative_border_box: &Rect<Au>,
                                                      display_list_section: DisplayListSection,
                                                      clip: &Rect<Au>) {
        let scanned_text_fragment_info = match self.specific {
            SpecificFragmentInfo::ScannedText(ref scanned_text_fragment_info) => {
                scanned_text_fragment_info
            }
            _ => return,
        };

        // Draw a highlighted background if the text is selected.
        //
        // TODO: Allow non-text fragments to be selected too.
        if scanned_text_fragment_info.selected() {
            let style = self.selected_style();
            let background_color = style.resolve_color(style.get_background().background_color);
            let base = state.create_base_display_item(stacking_relative_border_box,
                                                      LocalClip::from(clip.to_rectf()),
                                                      self.node,
                                                      self.style.get_cursor(Cursor::Default),
                                                      display_list_section);
            state.add_display_item(
                DisplayItem::SolidColor(box SolidColorDisplayItem {
                    base: base,
                    color: background_color.to_gfx_color(),
            }));
        }

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

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

        let base = state.create_base_display_item(&insertion_point_bounds,
                                                  LocalClip::from(clip.to_rectf()),
                                                  self.node,
                                                  self.style.get_cursor(cursor),
                                                  display_list_section);
        state.add_display_item(DisplayItem::SolidColor(box SolidColorDisplayItem {
            base: base,
            color: self.style().get_color().color.to_gfx_color(),
        }));
    }

    fn build_display_list(&mut self,
                          state: &mut DisplayListBuildState,
                          stacking_relative_flow_origin: &Vector2D<Au>,
                          relative_containing_block_size: &LogicalSize<Au>,
                          relative_containing_block_mode: WritingMode,
                          border_painting_mode: BorderPaintingMode,
                          display_list_section: DisplayListSection,
                          clip: &Rect<Au>) {
        self.restyle_damage.remove(REPAINT);
        if self.style().get_inheritedbox().visibility != visibility::T::visible {
            return
        }

        // Compute the fragment position relative to the parent stacking context. If the fragment
        // itself establishes a stacking context, then the origin of its position will be (0, 0)
        // for the purposes of this computation.
        let stacking_relative_border_box =
            self.stacking_relative_border_box(stacking_relative_flow_origin,
                                              relative_containing_block_size,
                                              relative_containing_block_mode,
                                              CoordinateSystem::Own);

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

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

                    let mut style = node.style.clone();
                    properties::modify_border_style_for_inline_sides(
                        &mut style,
                        node.flags.contains(FIRST_FRAGMENT_OF_ELEMENT),
                        node.flags.contains(LAST_FRAGMENT_OF_ELEMENT));
                    self.build_display_list_for_borders_if_applicable(
                        state,
                        &*style,
                        border_painting_mode,
                        &stacking_relative_border_box,
                        display_list_section,
                        clip);

                    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,
                                                                  border_painting_mode,
                                                                  &stacking_relative_border_box,
                                                                  display_list_section,
                                                                  clip);
                self.build_display_list_for_outline_if_applicable(state,
                                                                  &*self.style,
                                                                  &stacking_relative_border_box,
                                                                  clip);
            }
        }

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

        if empty_rect {
            return
        }

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

        // Create special per-fragment-type display items.
        self.build_fragment_type_specific_display_items(state,
                                                        &stacking_relative_border_box,
                                                        clip);

        if opts::get().show_debug_fragment_borders {
           self.build_debug_borders_around_fragment(state, &stacking_relative_border_box, clip)
        }
    }

    fn build_fragment_type_specific_display_items(&mut self,
                                                  state: &mut DisplayListBuildState,
                                                  stacking_relative_border_box: &Rect<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);

        // Adjust the clipping region as necessary to account for `border-radius`.
        let build_local_clip = |style: &ComputedValues| {
            let radii = build_border_radius_for_inner_rect(&stacking_relative_border_box, style);
            if !radii.is_square() {
                LocalClip::RoundedRect(
                    stacking_relative_border_box.to_rectf(),
                    ComplexClipRegion::new(stacking_relative_content_box.to_rectf(),
                                           radii.to_border_radius(),
                 ))
            } else {
                LocalClip::Rect(stacking_relative_border_box.to_rectf())
            }
        };

        match self.specific {
            SpecificFragmentInfo::TruncatedFragment(box TruncatedFragmentInfo {
                text_info: Some(ref text_fragment),
                ..
            }) |
            SpecificFragmentInfo::ScannedText(box ref text_fragment) => {
                // Create the main text display item.
                self.build_display_list_for_text_fragment(state,
                                                          &*text_fragment,
                                                          &stacking_relative_content_box,
                                                          &self.style.get_inheritedtext().text_shadow.0,
                                                          clip);

                if opts::get().show_debug_fragment_borders {
                    self.build_debug_borders_around_text_fragments(state,
                                                                   self.style(),
                                                                   stacking_relative_border_box,
                                                                   &stacking_relative_content_box,
                                                                   &*text_fragment,
                                                                   clip);
                }
            }
            SpecificFragmentInfo::Generic |
            SpecificFragmentInfo::GeneratedContent(..) |
            SpecificFragmentInfo::Table |
            SpecificFragmentInfo::TableCell |
            SpecificFragmentInfo::TableRow |
            SpecificFragmentInfo::TableWrapper |
            SpecificFragmentInfo::Multicol |
            SpecificFragmentInfo::MulticolColumn |
            SpecificFragmentInfo::InlineBlock(_) |
            SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
            SpecificFragmentInfo::InlineAbsolute(_) |
            SpecificFragmentInfo::TruncatedFragment(_) |
            SpecificFragmentInfo::Svg(_) => {
                if opts::get().show_debug_fragment_borders {
                    self.build_debug_borders_around_fragment(state,
                                                             stacking_relative_border_box,
                                                             clip);
                }
            }
            SpecificFragmentInfo::Iframe(ref fragment_info) => {
                if !stacking_relative_content_box.is_empty() {
                    let base = state.create_base_display_item(
                        &stacking_relative_content_box,
                        build_local_clip(&self.style),
                        self.node,
                        self.style.get_cursor(Cursor::Default),
                        DisplayListSection::Content);
                    let item = DisplayItem::Iframe(box IframeDisplayItem {
                        base: base,
                        iframe: fragment_info.pipeline_id,
                    });

                    let size = Size2D::new(item.bounds().size.width.to_f32_px(),
                                           item.bounds().size.height.to_f32_px());
                    state.iframe_sizes.push((fragment_info.browsing_context_id,
                                             TypedSize2D::from_untyped(&size)));

                    state.add_display_item(item);
                }
            }
            SpecificFragmentInfo::Image(ref mut image_fragment) => {
                // Place the image into the display list.
                if let Some(ref image) = image_fragment.image {
                    let base = state.create_base_display_item(
                        &stacking_relative_content_box,
                        build_local_clip(&self.style),
                        self.node,
                        self.style.get_cursor(Cursor::Default),
                        DisplayListSection::Content);
                    state.add_display_item(DisplayItem::Image(box ImageDisplayItem {
                        base: base,
                        webrender_image: WebRenderImageInfo::from_image(image),
                        image_data: Some(Arc::new(image.bytes.clone())),
                        stretch_size: stacking_relative_content_box.size,
                        tile_spacing: Size2D::zero(),
                        image_rendering: self.style.get_inheritedbox().image_rendering.clone(),
                    }));
                }
            }
            SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => {
                let computed_width = canvas_fragment_info.dom_width.to_px();
                let computed_height = canvas_fragment_info.dom_height.to_px();

                let canvas_data = match canvas_fragment_info.ipc_renderer {
                    Some(ref ipc_renderer) => {
                        let ipc_renderer = ipc_renderer.lock().unwrap();
                        let (sender, receiver) = ipc::channel().unwrap();
                        ipc_renderer.send(CanvasMsg::FromLayout(
                            FromLayoutMsg::SendData(sender))).unwrap();
                        receiver.recv().unwrap()
                    },
                    None => return,
                };

                let base = state.create_base_display_item(
                    &stacking_relative_content_box,
                    build_local_clip(&self.style),
                    self.node,
                    self.style.get_cursor(Cursor::Default),
                    DisplayListSection::Content);
                let display_item = match canvas_data {
                    CanvasData::Image(canvas_data) => {
                        DisplayItem::Image(box ImageDisplayItem {
                            base: base,
                            webrender_image: WebRenderImageInfo {
                                width: computed_width as u32,
                                height: computed_height as u32,
                                format: PixelFormat::BGRA8,
                                key: Some(canvas_data.image_key),
                            },
                            image_data: None,
                            stretch_size: stacking_relative_content_box.size,
                            tile_spacing: Size2D::zero(),
                            image_rendering: image_rendering::T::auto,
                        })
                    }
                    CanvasData::WebGL(context_id) => {
                        DisplayItem::WebGL(box WebGLDisplayItem {
                            base: base,
                            context_id: context_id,
                        })
                    }
                };

                state.add_display_item(display_item);
            }
            SpecificFragmentInfo::UnscannedText(_) => {
                panic!("Shouldn't see unscanned fragments here.")
            }
            SpecificFragmentInfo::TableColumn(_) => {
                panic!("Shouldn't see table column fragments here.")
            }
        }
    }

    fn stacking_context_id(&self) -> StackingContextId {
        StackingContextId::new(self.unique_id(IdType::StackingContext))
    }

    fn create_stacking_context(&self,
                               id: StackingContextId,
                               base_flow: &BaseFlow,
                               scroll_policy: ScrollPolicy,
                               mode: StackingContextCreationMode,
                               parent_scroll_id: ClipId)
                               -> StackingContext {
        let border_box =
             self.stacking_relative_border_box(&base_flow.stacking_relative_position,
                                               &base_flow.early_absolute_position_info
                                                         .relative_containing_block_size,
                                               base_flow.early_absolute_position_info
                                                          .relative_containing_block_mode,
                                               CoordinateSystem::Parent);
        // First, compute the offset of our border box (including relative positioning)
        // from our flow origin, since that is what `BaseFlow::overflow` is relative to.
        let border_box_offset =
            border_box.translate(&-base_flow.stacking_relative_position).origin;
        // Then, using that, compute our overflow region relative to our border box.
        let overflow = base_flow.overflow.paint.translate(&-border_box_offset.to_vector());

        // Create the filter pipeline.
        let effects = self.style().get_effects();
        let mut filters = effects.filter.0.clone();
        if effects.opacity != 1.0 {
            filters.push(Filter::Opacity(effects.opacity))
        }

        let context_type = match mode {
            StackingContextCreationMode::PseudoFloat => StackingContextType::PseudoFloat,
            StackingContextCreationMode::PseudoPositioned => StackingContextType::PseudoPositioned,
            _ => StackingContextType::Real,
        };

        StackingContext::new(id,
                             context_type,
                             &border_box,
                             &overflow,
                             self.effective_z_index(),
                             filters.into(),
                             self.style().get_effects().mix_blend_mode.to_mix_blend_mode(),
                             self.transform_matrix(&border_box),
                             self.style().get_used_transform_style().to_transform_style(),
                             self.perspective_matrix(&border_box),
                             scroll_policy,
                             parent_scroll_id)
    }

    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_color().color
        } else {
            self.style().get_color().color
        };


        // Determine the orientation and cursor to use.
        let (orientation, cursor) = if self.style.writing_mode.is_vertical() {
            // TODO: Distinguish between 'sideways-lr' and 'sideways-rl' writing modes in CSS
            // Writing Modes Level 4.
            (TextOrientation::SidewaysRight, Cursor::VerticalText)
        } else {
            (TextOrientation::Upright, Cursor::Text)
        };

        // Compute location of the baseline.
        //
        // FIXME(pcwalton): Get the real container size.
        let container_size = Size2D::zero();
        let metrics = &text_fragment.run.font_metrics;
        let baseline_origin = stacking_relative_content_box.origin +
            LogicalPoint::new(self.style.writing_mode,
                              Au(0),
                              metrics.ascent).to_physical(self.style.writing_mode,
                                                          container_size).to_vector();

        // Base item for all text/shadows
        let base = state.create_base_display_item(&stacking_relative_content_box,
                                                  LocalClip::from(clip.to_rectf()),
                                                  self.node,
                                                  self.style().get_cursor(cursor),
                                                  DisplayListSection::Content);

        // NB: According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front
        // to back).

        // Shadows
        for shadow in text_shadows.iter() {
            state.add_display_item(DisplayItem::PushTextShadow(box PushTextShadowDisplayItem {
                base: base.clone(),
                blur_radius: shadow.blur,
                offset: Vector2D::new(shadow.horizontal, shadow.vertical),
                color: self.style().resolve_color(shadow.color).to_gfx_color(),
            }));
        }


        // Create display items for text decorations.
        let mut text_decorations = self.style()
                                       .get_inheritedtext()
                                       ._servo_text_decorations_in_effect;
        // Note that the text decoration colors are always the same as the text color.
        text_decorations.underline = text_decorations.underline.map(|_| text_color);
        text_decorations.overline = text_decorations.overline.map(|_| text_color);
        text_decorations.line_through = text_decorations.line_through.map(|_| text_color);

        let stacking_relative_content_box =
            LogicalRect::from_physical(self.style.writing_mode,
                                       *stacking_relative_content_box,
                                       container_size);

        // Underline
        if let Some(ref underline_color) = text_decorations.underline {
            let mut stacking_relative_box = stacking_relative_content_box;
            stacking_relative_box.start.b = stacking_relative_content_box.start.b +
                metrics.ascent - metrics.underline_offset;
            stacking_relative_box.size.block = metrics.underline_size;
            self.build_display_list_for_text_decoration(state,
                                                        underline_color,
                                                        &stacking_relative_box,
                                                        clip);
        }

        // Overline
        if let Some(ref overline_color) = text_decorations.overline {
            let mut stacking_relative_box = stacking_relative_content_box;
            stacking_relative_box.size.block = metrics.underline_size;
            self.build_display_list_for_text_decoration(state,
                                                        overline_color,
                                                        &stacking_relative_box,
                                                        clip);
        }

        // Text
        state.add_display_item(DisplayItem::Text(box TextDisplayItem {
            base: base.clone(),
            text_run: text_fragment.run.clone(),
            range: text_fragment.range,
            text_color: text_color.to_gfx_color(),
            orientation: orientation,
            baseline_origin: baseline_origin,
        }));


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


        // Line-Through
        if let Some(ref line_through_color) = text_decorations.line_through {
            let mut stacking_relative_box = stacking_relative_content_box;
            stacking_relative_box.start.b = stacking_relative_box.start.b + metrics.ascent -
                metrics.strikeout_offset;
            stacking_relative_box.size.block = metrics.strikeout_size;
            self.build_display_list_for_text_decoration(state,
                                                        line_through_color,
                                                        &stacking_relative_box,
                                                        clip);
        }

        // Pair all the PushTextShadows
        for _ in text_shadows {
            state.add_display_item(DisplayItem::PopTextShadow(box PopTextShadowDisplayItem {
                base: base.clone(),
            }));
        }
    }

    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(
            &stacking_relative_box,
            LocalClip::from(clip.to_rectf()),
            self.node,
            self.style.get_cursor(Cursor::Default),
            DisplayListSection::Content);

        state.add_display_item(DisplayItem::SolidColor(box SolidColorDisplayItem {
            base: base,
            color: color.to_gfx_color(),
        }));
    }

    fn unique_id(&self, id_type: IdType) -> u64 {
        let fragment_type = self.fragment_type();
        let id = match id_type {
            IdType::StackingContext | IdType::OverflowClip  => self.node.id() as usize,
            IdType::CSSClip => self as *const _ as usize,
        };
        combine_id_with_fragment_type(id, fragment_type) as u64
    }

    fn fragment_type(&self) -> FragmentType {
        match self.pseudo {
            PseudoElementType::Normal => FragmentType::FragmentBody,
            PseudoElementType::Before(_) => FragmentType::BeforePseudoContent,
            PseudoElementType::After(_) => FragmentType::AfterPseudoContent,
            PseudoElementType::DetailsSummary(_) => FragmentType::FragmentBody,
            PseudoElementType::DetailsContent(_) => FragmentType::FragmentBody,
        }
    }
}

pub trait BlockFlowDisplayListBuilding {
    fn collect_stacking_contexts_for_block(&mut self, state: &mut DisplayListBuildState);

    fn transform_clip_to_coordinate_space(&mut self,
                                          state: &mut DisplayListBuildState,
                                          preserved_state: &mut PreservedDisplayListState);
    fn setup_clipping_for_block(&mut self,
                                state: &mut DisplayListBuildState,
                                preserved_state: &mut PreservedDisplayListState,
                                stacking_context_type: BlockStackingContextType)
                                -> ClipId;
    fn setup_scroll_root_for_overflow(&mut self,
                                      state: &mut DisplayListBuildState,
                                      preserved_state: &mut PreservedDisplayListState,
                                      border_box: &Rect<Au>);
    fn setup_scroll_root_for_css_clip(&mut self,
                                      state: &mut DisplayListBuildState,
                                      preserved_state: &mut PreservedDisplayListState,
                                      stacking_relative_border_box: &Rect<Au>);
    fn create_pseudo_stacking_context_for_block(&mut self,
                                                parent_stacking_context_id: StackingContextId,
                                                parent_scroll_root_id: ClipId,
                                                state: &mut DisplayListBuildState);
    fn create_real_stacking_context_for_block(&mut self,
                                              parent_stacking_context_id: StackingContextId,
                                              parent_scroll_root_id: ClipId,
                                              state: &mut DisplayListBuildState);
    fn build_display_list_for_block(&mut self,
                                    state: &mut DisplayListBuildState,
                                    border_painting_mode: BorderPaintingMode);
}

/// This structure manages ensuring that modification to DisplayListBuildState
/// 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 PreservedDisplayListState::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 PreservedDisplayListState {
    stacking_context_id: StackingContextId,
    scroll_root_id: ClipId,
    containing_block_scroll_root_id: ClipId,
    clips_pushed: usize,
    containing_block_clips_pushed: usize,
    transform_style: TransformStyle,
}

impl PreservedDisplayListState {
    fn new(state: &mut DisplayListBuildState) -> PreservedDisplayListState {
        PreservedDisplayListState {
            stacking_context_id: state.current_stacking_context_id,
            scroll_root_id: state.current_scroll_root_id,
            containing_block_scroll_root_id: state.containing_block_scroll_root_id,
            clips_pushed: 0,
            containing_block_clips_pushed: 0,
            transform_style: state.current_transform_style,
        }
    }

    fn switch_to_containing_block_clip(&mut self, state: &mut DisplayListBuildState) {
        let clip = state.containing_block_clip_stack.last().cloned().unwrap_or_else(max_rect);
        state.clip_stack.push(clip);
        self.clips_pushed += 1;
    }

    fn restore(self, state: &mut DisplayListBuildState) {
        state.current_stacking_context_id = self.stacking_context_id;
        state.current_scroll_root_id = self.scroll_root_id;
        state.containing_block_scroll_root_id = self.containing_block_scroll_root_id;

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

        state.current_transform_style = self.transform_style;
    }

    fn push_clip(&mut self,
                 state: &mut DisplayListBuildState,
                 clip: &Rect<Au>,
                 positioning: position::T) {
        let mut clip = *clip;
        if positioning != position::T::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 position::T::absolute == positioning {
            state.containing_block_clip_stack.push(clip);
            self.containing_block_clips_pushed += 1;
        }
    }
}

impl BlockFlowDisplayListBuilding for BlockFlow {
    fn transform_clip_to_coordinate_space(&mut self,
                                          state: &mut DisplayListBuildState,
                                          preserved_state: &mut PreservedDisplayListState) {
        if state.clip_stack.is_empty() {
            return;
        }
        let border_box = self.stacking_relative_position(CoordinateSystem::Parent);
        let transform = match self.fragment.transform_matrix(&border_box) {
            Some(transform) => transform,
            None => return,
        };

        let perspective = self.fragment.perspective_matrix(&border_box)
                                       .unwrap_or_else(Transform3D::identity);
        let transform = transform.pre_mul(&perspective).inverse();

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

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

                    let clip = transform.transform_rect(&clip);

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

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

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

    fn collect_stacking_contexts_for_block(&mut self, state: &mut DisplayListBuildState) {
        let mut preserved_state = PreservedDisplayListState::new(state);

        let block_stacking_context_type = self.block_stacking_context_type();
        self.base.stacking_context_id = match block_stacking_context_type {
            BlockStackingContextType::NonstackingContext => state.current_stacking_context_id,
            BlockStackingContextType::PseudoStackingContext |
            BlockStackingContextType::StackingContext => self.fragment.stacking_context_id(),
        };
        state.current_stacking_context_id = self.base.stacking_context_id;

        // We are getting the id of the scroll root that contains us here, not the id of
        // any scroll root that we create. If we create a scroll root, its id will be
        // stored in state.current_scroll_root_id. If we should create a stacking context,
        // we don't want it to be clipped by its own scroll root.
        let containing_scroll_root_id = self.setup_clipping_for_block(state,
                                                                      &mut preserved_state,
                                                                      block_stacking_context_type);

        if establishes_containing_block_for_absolute(self.positioning()) {
            state.containing_block_scroll_root_id = state.current_scroll_root_id;
        }

        match block_stacking_context_type {
            BlockStackingContextType::NonstackingContext => {
                self.base.collect_stacking_contexts_for_children(state);
            }
            BlockStackingContextType::PseudoStackingContext => {
                self.create_pseudo_stacking_context_for_block(preserved_state.stacking_context_id,
                                                              containing_scroll_root_id,
                                                              state);
            }
            BlockStackingContextType::StackingContext => {
                self.create_real_stacking_context_for_block(preserved_state.stacking_context_id,
                                                            containing_scroll_root_id,
                                                            state);
            }
        }

        preserved_state.restore(state);
    }

    fn setup_clipping_for_block(&mut self,
                                state: &mut DisplayListBuildState,
                                preserved_state: &mut PreservedDisplayListState,
                                stacking_context_type: BlockStackingContextType)
                                -> ClipId {
        // 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_scroll_root_id = match self.positioning() {
            position::T::absolute => {
                preserved_state.switch_to_containing_block_clip(state);
                state.current_scroll_root_id = state.containing_block_scroll_root_id;
                state.containing_block_scroll_root_id
            }
            position::T::fixed => {
                preserved_state.push_clip(state, &max_rect(), position::T::fixed);
                state.current_scroll_root_id
            }
            _ => state.current_scroll_root_id,
        };
        self.base.scroll_root_id = Some(containing_scroll_root_id);

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

        let stacking_relative_border_box = self.fragment.stacking_relative_border_box(
            &self.base.stacking_relative_position,
            &self.base.early_absolute_position_info.relative_containing_block_size,
            self.base.early_absolute_position_info.relative_containing_block_mode,
            coordinate_system);

        if stacking_context_type == BlockStackingContextType::StackingContext {
            self.transform_clip_to_coordinate_space(state, preserved_state);
        }

        self.setup_scroll_root_for_overflow(state, preserved_state, &stacking_relative_border_box);
        self.setup_scroll_root_for_css_clip(state, preserved_state, &stacking_relative_border_box);
        self.base.clip = state.clip_stack.last().cloned().unwrap_or_else(max_rect);

        match self.positioning() {
            position::T::absolute | position::T::relative | position::T::fixed =>
                state.containing_block_scroll_root_id = state.current_scroll_root_id,
            _ => {}
        }

        containing_scroll_root_id
    }

    fn setup_scroll_root_for_overflow(&mut self,
                                      state: &mut DisplayListBuildState,
                                      preserved_state: &mut PreservedDisplayListState,
                                      border_box: &Rect<Au>) {
        if !self.overflow_style_may_require_scroll_root() {
            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 ||
            overflow_x::T::hidden == self.fragment.style.get_box().overflow_x ||
            overflow_x::T::hidden == self.fragment.style.get_box().overflow_y;

        self.mark_scrolling_overflow(has_scrolling_overflow);
        if !has_scrolling_overflow {
            return;
        }

        // If we already have a scroll root for this flow, just return. This can happen
        // when fragments map to more than one flow, such as in the case of table
        // wrappers. We just accept the first scroll root in that case.
        let new_scroll_root_id = ClipId::new(self.fragment.unique_id(IdType::OverflowClip),
                                             state.layout_context.id.to_webrender());
        if state.has_scroll_root(new_scroll_root_id) {
            return;
        }

        let overflow_x = self.fragment.style.get_box().overflow_x;
        let overflow_y = self.fragment.style.get_box().overflow_y;

        let content_size = self.base.overflow.scroll.origin + self.base.overflow.scroll.size;
        let mut content_size = Size2D::new(content_size.x, content_size.y);
        if overflow_x::T::hidden == overflow_x {
            content_size.width = content_box.size.width;
        }

        if overflow_x::T::hidden == overflow_y {
            content_size.height = content_box.size.height;
        }

        if overflow_x::T::hidden == overflow_y || overflow_x::T::hidden == overflow_x {
            preserved_state.push_clip(state, &border_box, self.positioning());
        }

        let mut root_type = ScrollRootType::ScrollFrame;
        if overflow_x::T::hidden == overflow_y && overflow_x::T::hidden == overflow_x {
            root_type = ScrollRootType::Clip;
        }

        let clip_rect = build_inner_border_box_for_border_rect(&border_box, &self.fragment.style);
        let mut clip = ClippingRegion::from_rect(&clip_rect);
        let radii = build_border_radius_for_inner_rect(&border_box, &self.fragment.style);
        if !radii.is_square() {
            clip.intersect_with_rounded_rect(&clip_rect, &radii)
        }

        let parent_id = self.scroll_root_id(state.layout_context.id);
        state.add_scroll_root(
            ScrollRoot {
                id: new_scroll_root_id,
                parent_id: parent_id,
                clip: clip,
                content_rect: Rect::new(content_box.origin, content_size),
                root_type,
            },
            self.base.stacking_context_id
        );

        self.base.scroll_root_id = Some(new_scroll_root_id);
        state.current_scroll_root_id = new_scroll_root_id;
    }

    /// Adds a scroll root for a block to take the `clip` property into account
    /// per CSS 2.1 § 11.1.2.
    fn setup_scroll_root_for_css_clip(&mut self,
                                      state: &mut DisplayListBuildState,
                                      preserved_state: &mut PreservedDisplayListState,
                                      stacking_relative_border_box: &Rect<Au>) {
        // Account for `clip` per CSS 2.1 § 11.1.2.
        let style_clip_rect = match self.fragment.style().get_effects().clip {
            Either::First(style_clip_rect) => style_clip_rect,
            _ => return,
        };

        let clip_origin = Point2D::new(stacking_relative_border_box.origin.x +
                                       style_clip_rect.left.unwrap_or(Au(0)),
                                       stacking_relative_border_box.origin.y +
                                       style_clip_rect.top.unwrap_or(Au(0)));
        let right = style_clip_rect.right.unwrap_or(stacking_relative_border_box.size.width);
        let bottom = style_clip_rect.bottom.unwrap_or(stacking_relative_border_box.size.height);
        let clip_size = Size2D::new(right - clip_origin.x, bottom - clip_origin.y);

        // We use the node id to create scroll roots for overflow properties, so we
        // use the fragment address to do the same for CSS clipping.
        // TODO(mrobinson): This should be more resilient while maintaining the space
        // efficiency of ScrollRootId.
        let new_scroll_root_id = ClipId::new(self.fragment.unique_id(IdType::CSSClip),
                                             state.layout_context.id.to_webrender());

        // If we already have a scroll root for this flow, just return. This can happen
        // when fragments map to more than one flow, such as in the case of table
        // wrappers. We just accept the first scroll root in that case.
        if state.has_scroll_root(new_scroll_root_id) {
            return;
        }

        let clip_rect = Rect::new(clip_origin, clip_size);
        preserved_state.push_clip(state, &clip_rect, self.positioning());

        let parent_id = self.scroll_root_id(state.layout_context.id);
        state.add_scroll_root(
            ScrollRoot {
                id: new_scroll_root_id,
                parent_id: parent_id,
                clip: ClippingRegion::from_rect(&clip_rect),
                content_rect: Rect::zero(), // content_rect isn't important for clips.
                root_type: ScrollRootType::Clip,
            },
            self.base.stacking_context_id
        );

        self.base.scroll_root_id = Some(new_scroll_root_id);
        state.current_scroll_root_id = new_scroll_root_id;
    }

    fn create_pseudo_stacking_context_for_block(&mut self,
                                                parent_stacking_context_id: StackingContextId,
                                                parent_scroll_root_id: ClipId,
                                                state: &mut DisplayListBuildState) {
        let creation_mode = if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) ||
                               self.fragment.style.get_box().position != position::T::static_ {
            StackingContextCreationMode::PseudoPositioned
        } else {
            assert!(self.base.flags.is_float());
            StackingContextCreationMode::PseudoFloat
        };

        let new_context = self.fragment.create_stacking_context(self.base.stacking_context_id,
                                                                &self.base,
                                                                ScrollPolicy::Scrollable,
                                                                creation_mode,
                                                                parent_scroll_root_id);
        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_scroll_root_id: ClipId,
                                              state: &mut DisplayListBuildState) {
        let scroll_policy = if self.is_fixed() {
            ScrollPolicy::Fixed
        } else {
            ScrollPolicy::Scrollable
        };

        let stacking_context = self.fragment.create_stacking_context(
            self.base.stacking_context_id,
            &self.base,
            scroll_policy,
            StackingContextCreationMode::Normal,
            parent_scroll_root_id);

        state.add_stacking_context(parent_stacking_context_id, stacking_context);
        self.base.collect_stacking_contexts_for_children(state);
    }

    fn build_display_list_for_block(&mut self,
                                    state: &mut DisplayListBuildState,
                                    border_painting_mode: BorderPaintingMode) {
        let background_border_section = if self.base.flags.is_float() {
            DisplayListSection::BackgroundAndBorders
        } else if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) {
            if self.fragment.establishes_stacking_context() {
                DisplayListSection::BackgroundAndBorders
            } else {
                DisplayListSection::BlockBackgroundsAndBorders
            }
        } else {
            DisplayListSection::BlockBackgroundsAndBorders
        };

        state.processing_scroll_root_element = self.has_scrolling_overflow();

        // Add the box that starts the block context.
        self.fragment
            .build_display_list(state,
                                &self.base.stacking_relative_position,
                                &self.base
                                     .early_absolute_position_info
                                     .relative_containing_block_size,
                                self.base
                                    .early_absolute_position_info
                                    .relative_containing_block_mode,
                                border_painting_mode,
                                background_border_section,
                                &self.base.clip);

        self.base.build_display_items_for_debugging_tint(state, self.fragment.node);

        state.processing_scroll_root_element = false;
    }

}

pub trait InlineFlowDisplayListBuilding {
    fn collect_stacking_contexts_for_inline(&mut self, state: &mut DisplayListBuildState);
    fn build_display_list_for_inline_fragment_at_index(&mut self,
                                                       state: &mut DisplayListBuildState,
                                                       index: usize);
    fn build_display_list_for_inline(&mut self, state: &mut DisplayListBuildState);
}

impl InlineFlowDisplayListBuilding for InlineFlow {
    fn collect_stacking_contexts_for_inline(&mut self, state: &mut DisplayListBuildState) {
        self.base.stacking_context_id = state.current_stacking_context_id;
        self.base.scroll_root_id = Some(state.current_scroll_root_id);
        self.base.clip = state.clip_stack.last().cloned().unwrap_or_else(max_rect);

        for mut fragment in self.fragments.fragments.iter_mut() {
            let previous_containing_block_scroll_root_id = state.containing_block_scroll_root_id;
            if establishes_containing_block_for_absolute(fragment.style.get_box().position) {
                state.containing_block_scroll_root_id = state.current_scroll_root_id;
            }

            match fragment.specific {
                SpecificFragmentInfo::InlineBlock(ref mut block_flow) => {
                    let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
                    block_flow.collect_stacking_contexts(state);
                }
                SpecificFragmentInfo::InlineAbsoluteHypothetical(ref mut block_flow) => {
                    let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
                    block_flow.collect_stacking_contexts(state);
                }
                SpecificFragmentInfo::InlineAbsolute(ref mut block_flow) => {
                    let block_flow = FlowRef::deref_mut(&mut block_flow.flow_ref);
                    block_flow.collect_stacking_contexts(state);
                }
                _ if fragment.establishes_stacking_context() => {
                    fragment.stacking_context_id = fragment.stacking_context_id();

                    let current_stacking_context_id = state.current_stacking_context_id;
                    let stacking_context = fragment.create_stacking_context(fragment.stacking_context_id,
                                                                            &self.base,
                                                                            ScrollPolicy::Scrollable,
                                                                            StackingContextCreationMode::Normal,
                                                                            state.current_scroll_root_id);

                    state.add_stacking_context(current_stacking_context_id,
                                               stacking_context);
                }
                _ => fragment.stacking_context_id = state.current_stacking_context_id,
            }
            state.containing_block_scroll_root_id = previous_containing_block_scroll_root_id;
        }

    }

    fn build_display_list_for_inline_fragment_at_index(&mut self,
                                                       state: &mut DisplayListBuildState,
                                                       index: usize) {
        let fragment = self.fragments.fragments.get_mut(index).unwrap();
        fragment.build_display_list(state,
                                    &self.base.stacking_relative_position,
                                    &self.base
                                         .early_absolute_position_info
                                         .relative_containing_block_size,
                                    self.base
                                        .early_absolute_position_info
                                        .relative_containing_block_mode,
                                    BorderPaintingMode::Separate,
                                    DisplayListSection::Content,
                                    &self.base.clip);
    }

    fn build_display_list_for_inline(&mut self, state: &mut DisplayListBuildState) {
        // TODO(#228): Once we form lines and have their cached bounds, we can be smarter and
        // not recurse on a line if nothing in it can intersect the dirty region.
        debug!("Flow: building display list for {} inline fragments", self.fragments.len());

        // We iterate using an index here, because we want to avoid doing a doing
        // a double-borrow of self (one mutable for the method call and one immutable
        // for the self.fragments.fragment iterator itself).
        for index in 0..self.fragments.fragments.len() {
            let (establishes_stacking_context, stacking_context_id) = {
                let fragment = self.fragments.fragments.get(index).unwrap();
                (self.base.stacking_context_id != fragment.stacking_context_id,
                 fragment.stacking_context_id)
            };

            let parent_stacking_context_id = state.current_stacking_context_id;
            if establishes_stacking_context {
                state.current_stacking_context_id = stacking_context_id;
            }

            self.build_display_list_for_inline_fragment_at_index(state, index);

            if establishes_stacking_context {
                state.current_stacking_context_id = parent_stacking_context_id
            }
        }

        if !self.fragments.fragments.is_empty() {
            self.base.build_display_items_for_debugging_tint(state,
                                                             self.fragments.fragments[0].node);
        }
    }
}

pub trait ListItemFlowDisplayListBuilding {
    fn build_display_list_for_list_item(&mut self, state: &mut DisplayListBuildState);
}

impl ListItemFlowDisplayListBuilding for ListItemFlow {
    fn build_display_list_for_list_item(&mut self, state: &mut DisplayListBuildState) {
        // Draw the marker, if applicable.
        for marker in &mut self.marker_fragments {
            marker.build_display_list(state,
                                      &self.block_flow.base.stacking_relative_position,
                                      &self.block_flow
                                           .base
                                           .early_absolute_position_info
                                           .relative_containing_block_size,
                                      self.block_flow
                                          .base
                                          .early_absolute_position_info
                                          .relative_containing_block_mode,
                                      BorderPaintingMode::Separate,
                                      DisplayListSection::Content,
                                      &self.block_flow.base.clip);
        }

        // Draw the rest of the block.
        self.block_flow.build_display_list_for_block(state, BorderPaintingMode::Separate)
    }
}

pub trait FlexFlowDisplayListBuilding {
    fn build_display_list_for_flex(&mut self, state: &mut DisplayListBuildState);
}

impl FlexFlowDisplayListBuilding for FlexFlow {
    fn build_display_list_for_flex(&mut self, state: &mut DisplayListBuildState) {
        // Draw the rest of the block.
        self.as_mut_block().build_display_list_for_block(state, BorderPaintingMode::Separate)
    }
}

trait BaseFlowDisplayListBuilding {
    fn build_display_items_for_debugging_tint(&self,
                                              state: &mut DisplayListBuildState,
                                              node: OpaqueNode);
}

impl BaseFlowDisplayListBuilding for BaseFlow {
    fn build_display_items_for_debugging_tint(&self,
                                              state: &mut DisplayListBuildState,
                                              node: OpaqueNode) {
        if !opts::get().show_debug_parallel_layout {
            return
        }

        let thread_id = self.thread_id;
        let stacking_context_relative_bounds =
            Rect::new(self.stacking_relative_position.to_point(),
                      self.position.size.to_physical(self.writing_mode));

        let mut color = THREAD_TINT_COLORS[thread_id as usize % THREAD_TINT_COLORS.len()];
        color.a = 1.0;
        let base = state.create_base_display_item(
            &stacking_context_relative_bounds.inflate(Au::from_px(2), Au::from_px(2)),
            LocalClip::from(self.clip.to_rectf()),
            node,
            None,
            DisplayListSection::Content);
        state.add_display_item(DisplayItem::Border(box BorderDisplayItem {
            base: base,
            border_widths: SideOffsets2D::new_all_same(Au::from_px(2)),
            details: BorderDetails::Normal(NormalBorder {
                color: SideOffsets2D::new_all_same(color),
                style: SideOffsets2D::new_all_same(border_style::T::solid),
                radius: BorderRadii::all_same(Au(0)),
            }),
        }));
    }
}

trait ComputedValuesCursorUtility {
    fn get_cursor(&self, default_cursor: Cursor) -> Option<Cursor>;
}

impl ComputedValuesCursorUtility for ComputedValues {
    /// Gets the cursor to use given the specific ComputedValues.  `default_cursor` specifies
    /// the cursor to use if `cursor` is `auto`. Typically, this will be `PointerCursor`, but for
    /// text display items it may be `TextCursor` or `VerticalTextCursor`.
    #[inline]
    fn get_cursor(&self, default_cursor: Cursor) -> Option<Cursor> {
        match (self.get_pointing().pointer_events, self.get_pointing().cursor) {
            (pointer_events::T::none, _) => None,
            (pointer_events::T::auto, cursor::Keyword::Auto) => Some(default_cursor),
            (pointer_events::T::auto, cursor::Keyword::Cursor(cursor)) => Some(cursor),
        }
    }
}

// A helper data structure for gradients.
#[derive(Copy, Clone)]
struct StopRun {
    start_offset: f32,
    end_offset: f32,
    start_index: usize,
    stop_count: usize,
}

fn position_to_offset(position: LengthOrPercentage, total_length: Au) -> f32 {
    match position {
        LengthOrPercentage::Length(Au(length)) => length as f32 / total_length.0 as f32,
        LengthOrPercentage::Percentage(percentage) => percentage.0 as f32,
        LengthOrPercentage::Calc(calc) => {
            calc.to_used_value(Some(total_length)).unwrap().0 as f32 / total_length.0 as f32
        },
    }
}

/// Adjusts `content_rect` as necessary for the given spread, and blur so that the resulting
/// bounding rect contains all of a shadow's ink.
fn shadow_bounds(content_rect: &Rect<Au>, blur: Au, spread: Au) -> Rect<Au> {
    let inflation = spread + blur * BLUR_INFLATION_FACTOR;
    content_rect.inflate(inflation, inflation)
}

/// Allows a CSS color to be converted into a graphics color.
pub trait ToGfxColor {
    /// Converts a CSS color to a graphics color.
    fn to_gfx_color(&self) -> ColorF;
}

impl ToGfxColor for RGBA {
    fn to_gfx_color(&self) -> ColorF {
        ColorF::new(self.red_f32(), self.green_f32(), self.blue_f32(), self.alpha_f32())
    }
}

/// Describes how to paint the borders.
#[derive(Copy, Clone)]
pub enum BorderPaintingMode<'a> {
    /// Paint borders separately (`border-collapse: separate`).
    Separate,
    /// Paint collapsed borders.
    Collapse(&'a CollapsedBordersForCell),
    /// Paint no borders.
    Hidden,
}

#[derive(Copy, Clone, PartialEq)]
pub enum StackingContextCreationMode {
    Normal,
    PseudoPositioned,
    PseudoFloat,
}