Backed out 2 changesets (bug 1491395) for failing awsy\test_memory_usage.py TestMemoryUsage.test_open_tabs on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Tue, 18 Sep 2018 21:23:03 +0300
changeset 437081 911c7836b359cfa31f82e8f90e411e4c9709ade7
parent 437080 934ae0ea69fce3856ef04a15b953b26214cfbcf8
child 437082 805f1a2737a0ef58a64cf8e7b6239543d4b45940
push id107988
push userapavel@mozilla.com
push dateTue, 18 Sep 2018 18:26:14 +0000
treeherdermozilla-inbound@805f1a2737a0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1491395
milestone64.0a1
backs out432d22af68c825eebd86482c714b3cffcba408c7
153d6dc389626a489be3b29e9ec4ddb4c4744566
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 2 changesets (bug 1491395) for failing awsy\test_memory_usage.py TestMemoryUsage.test_open_tabs on a CLOSED TREE Backed out changeset 432d22af68c8 (bug 1491395) Backed out changeset 153d6dc38962 (bug 1491395)
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/gpu_cache.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_api/src/units.rs
gfx/webrender_bindings/WebRenderAPI.cpp
gfx/webrender_bindings/WebRenderAPI.h
gfx/webrender_bindings/WebRenderTypes.h
gfx/webrender_bindings/revision.txt
gfx/webrender_bindings/src/bindings.rs
gfx/webrender_bindings/webrender_ffi_generated.h
gfx/wrench/src/main.rs
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
layout/generic/TextDrawTarget.h
layout/tables/nsTableFrame.cpp
widget/cocoa/nsNativeThemeCocoa.mm
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1772,20 +1772,20 @@ impl ClipBatcher {
         root_spatial_node_index: SpatialNodeIndex,
         resource_cache: &ResourceCache,
         gpu_cache: &GpuCache,
         clip_store: &ClipStore,
         clip_scroll_tree: &ClipScrollTree,
         transforms: &mut TransformPalette,
     ) {
         for i in 0 .. clip_node_range.count {
-            let (clip_node, flags, spatial_node_index) = clip_store.get_node_from_range(&clip_node_range, i);
+            let (clip_node, flags) = clip_store.get_node_from_range(&clip_node_range, i);
 
             let clip_transform_id = transforms.get_id(
-                spatial_node_index,
+                clip_node.spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
                 clip_scroll_tree,
             );
 
             let prim_transform_id = transforms.get_id(
                 root_spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
                 clip_scroll_tree,
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,14 +1,14 @@
 /* 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/. */
 
-use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU, DeviceRect, DeviceSize};
-use api::{LayoutSizeAu, LayoutSideOffsets, LayoutPrimitiveInfo, LayoutToDeviceScale};
+use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF};
+use api::{ColorU, DeviceRect, DeviceSize, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
 use api::{DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
 use app_units::Au;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegment};
 use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 use util::{lerp, RectHelpers};
@@ -70,18 +70,18 @@ impl From<BorderRadius> for BorderRadius
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BorderWidthsAu {
     pub left: Au,
     pub top: Au,
     pub right: Au,
     pub bottom: Au,
 }
 
-impl From<LayoutSideOffsets> for BorderWidthsAu {
-    fn from(widths: LayoutSideOffsets) -> Self {
+impl From<BorderWidths> for BorderWidthsAu {
+    fn from(widths: BorderWidths) -> Self {
         BorderWidthsAu {
             left: Au::from_f32_px(widths.left),
             top: Au::from_f32_px(widths.top),
             right: Au::from_f32_px(widths.right),
             bottom: Au::from_f32_px(widths.bottom),
         }
     }
 }
@@ -112,25 +112,25 @@ pub struct BorderCacheKey {
     pub top: BorderSideAu,
     pub bottom: BorderSideAu,
     pub radius: BorderRadiusAu,
     pub widths: BorderWidthsAu,
     pub scale: Au,
 }
 
 impl BorderCacheKey {
-    pub fn new(border: &NormalBorder, widths: &LayoutSideOffsets) -> Self {
+    pub fn new(border: &NormalBorder, widths: &BorderWidths) -> Self {
         BorderCacheKey {
             left: border.left.into(),
             top: border.top.into(),
             right: border.right.into(),
             bottom: border.bottom.into(),
             widths: (*widths).into(),
             radius: border.radius.into(),
-            scale: Au(0),
+            scale: Au::from_f32_px(0.0),
         }
     }
 }
 
 pub fn ensure_no_corner_overlap(
     radius: &mut BorderRadius,
     rect: &LayoutRect,
 ) {
@@ -175,17 +175,17 @@ pub fn ensure_no_corner_overlap(
     }
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn add_normal_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         border: &NormalBorder,
-        widths: &LayoutSideOffsets,
+        widths: &BorderWidths,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
         let mut border = *border;
         ensure_no_corner_overlap(&mut border.radius, &info.rect);
 
         let prim = BrushPrimitive::new(
             BrushKind::new_border(border, *widths),
             None,
@@ -523,100 +523,43 @@ impl DotInfo {
 #[derive(Debug)]
 pub struct BorderSegmentInfo {
     task_rect: DeviceRect,
     segment: BorderSegment,
     radius: DeviceSize,
     widths: DeviceSize,
 }
 
-bitflags! {
-    /// Whether we depend on the available size for the border (effectively in
-    /// the local rect of the primitive), and in which direction.
-    ///
-    /// Note that this relies on the corners being only dependent on the border
-    /// widths and radius.
-    ///
-    /// This is not just a single boolean to allow instance caching for border
-    /// boxes where one of the directions differ but not the one on the affected
-    /// border is.
-    ///
-    /// This allows sharing instances for stuff like paragraphs of different
-    /// heights separated by horizontal borders.
-    pub struct AvailableSizeDependence : u8 {
-        /// There's a dependence on the vertical direction, that is, at least
-        /// one of the right or left edges is dashed or dotted.
-        const VERTICAL = 1 << 0;
-        /// Same but for the horizontal direction.
-        const HORIZONTAL = 1 << 1;
-    }
-}
-
-/// This is the data that describes a single border with (up to) four sides and
-/// four corners.
-///
-/// This object gets created for each border primitive at least once. Note,
-/// however, that the instances this produces via `build_instances()` can and
-/// will be shared by multiple borders, as long as they share the same cache
-/// key.
-///
-/// Segments, however, also get build once per primitive.
-///
-/// So the important invariant to preserve when going through this code is that
-/// the result of `build_instances()` would remain invariant for a given cache
-/// key.
-///
-/// That means, then, that `border_segments` can't depend at all on something
-/// that isn't on the key like the available_size, while the brush segments can
-/// (and will, since we skip painting empty segments caused by things like edges
-/// getting constrained by huge border-radius).
-///
-/// Note that the cache key is not only `BorderCacheKey`, but also a
-/// `size` from `RenderTaskCacheKey`, which will always be zero unless
-/// `available_size_dependence` is non-empty, which is effectively just dashed
-/// and dotted borders for now, since the spacing between the dash and dots
-/// changes depending on that size.
 #[derive(Debug)]
 pub struct BorderRenderTaskInfo {
     pub border_segments: Vec<BorderSegmentInfo>,
     pub size: DeviceIntSize,
-    pub available_size_dependence: AvailableSizeDependence,
-}
-
-#[derive(PartialEq, Eq)]
-enum DependsOnAvailableSize {
-    No,
-    Yes,
 }
 
 /// Information needed to place and draw a border edge.
 #[derive(Debug)]
 struct EdgeInfo {
     /// Offset in local space to place the edge from origin.
     local_offset: f32,
     /// Size of the edge in local space.
     local_size: f32,
     /// Size in device pixels needed in the render task.
     device_size: f32,
-    /// Whether this edge depends on the available size.
-    depends_on_available_size: bool,
 }
 
 impl EdgeInfo {
     fn new(
         local_offset: f32,
         local_size: f32,
         device_size: f32,
-        depends_on_avail_size: DependsOnAvailableSize,
-    ) -> Self {
-        Self {
+    ) -> EdgeInfo {
+        EdgeInfo {
             local_offset,
             local_size,
             device_size,
-            depends_on_available_size: depends_on_avail_size == DependsOnAvailableSize::Yes,
         }
     }
 }
 
 // Given a side width and the available space, compute the half-dash (half of
 // the 'on' segment) and the count of them for a given segment.
 fn compute_half_dash(side_width: f32, total_size: f32) -> (f32, u32) {
     let half_dash = side_width * 1.5;
@@ -646,51 +589,51 @@ fn compute_half_dash(side_width: f32, to
 fn get_edge_info(
     style: BorderStyle,
     side_width: f32,
     avail_size: f32,
     scale: f32,
 ) -> EdgeInfo {
     // To avoid division by zero below.
     if side_width <= 0.0 {
-        return EdgeInfo::new(0.0, 0.0, 0.0, DependsOnAvailableSize::No);
+        return EdgeInfo::new(0.0, 0.0, 0.0);
     }
 
     match style {
         BorderStyle::Dashed => {
             // Basically, two times the dash size.
             let (half_dash, _num_half_dashes) =
                 compute_half_dash(side_width, avail_size);
             let device_size = (2.0 * 2.0 * half_dash * scale).round();
-            EdgeInfo::new(0., avail_size, device_size, DependsOnAvailableSize::Yes)
+            EdgeInfo::new(0., avail_size, device_size)
         }
         BorderStyle::Dotted => {
             let dot_and_space_size = 2.0 * side_width;
             if avail_size < dot_and_space_size * 0.75 {
-                return EdgeInfo::new(0.0, 0.0, 0.0, DependsOnAvailableSize::Yes);
+                return EdgeInfo::new(0.0, 0.0, 0.0);
             }
             let approx_dot_count = avail_size / dot_and_space_size;
             let dot_count = approx_dot_count.floor().max(1.0);
             let used_size = dot_count * dot_and_space_size;
             let extra_space = avail_size - used_size;
             let device_size = dot_and_space_size * scale;
             let offset = (extra_space * 0.5).round();
-            EdgeInfo::new(offset, used_size, device_size, DependsOnAvailableSize::Yes)
+            EdgeInfo::new(offset, used_size, device_size)
         }
         _ => {
-            EdgeInfo::new(0.0, avail_size, 8.0, DependsOnAvailableSize::No)
+            EdgeInfo::new(0.0, avail_size, 8.0)
         }
     }
 }
 
 impl BorderRenderTaskInfo {
     pub fn new(
         rect: &LayoutRect,
         border: &NormalBorder,
-        widths: &LayoutSideOffsets,
+        widths: &BorderWidths,
         scale: LayoutToDeviceScale,
         brush_segments: &mut Vec<BrushSegment>,
     ) -> Option<Self> {
         let mut border_segments = Vec::new();
 
         let dp_width_top = (widths.top * scale.0).ceil();
         let dp_width_bottom = (widths.bottom * scale.0).ceil();
         let dp_width_left = (widths.left * scale.0).ceil();
@@ -756,41 +699,27 @@ impl BorderRenderTaskInfo {
             scale.0,
         );
         let right_edge_info = get_edge_info(
             border.right.style,
             widths.right,
             rect.size.height - local_size_tr.height - local_size_br.height,
             scale.0,
         );
-
         let inner_height = left_edge_info.device_size.max(right_edge_info.device_size).ceil();
 
         let size = DeviceSize::new(
             dp_size_tl.width.max(dp_size_bl.width) + inner_width + dp_size_tr.width.max(dp_size_br.width),
             dp_size_tl.height.max(dp_size_tr.height) + inner_height + dp_size_bl.height.max(dp_size_br.height),
         );
 
         if size.width == 0.0 || size.height == 0.0 {
             return None;
         }
 
-        let mut size_dependence = AvailableSizeDependence::empty();
-        if top_edge_info.depends_on_available_size ||
-            bottom_edge_info.depends_on_available_size
-        {
-            size_dependence.insert(AvailableSizeDependence::HORIZONTAL);
-        }
-
-        if left_edge_info.depends_on_available_size ||
-            right_edge_info.depends_on_available_size
-        {
-            size_dependence.insert(AvailableSizeDependence::VERTICAL);
-        }
-
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x,
                 rect.origin.y + local_size_tl.height + left_edge_info.local_offset,
                 rect.origin.x + widths.left,
                 rect.origin.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
             ),
             DeviceRect::from_floats(
@@ -960,43 +889,19 @@ impl BorderRenderTaskInfo {
             EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT,
             &mut border_segments,
             brush_segments,
         );
 
         Some(BorderRenderTaskInfo {
             border_segments,
             size: size.to_i32(),
-            available_size_dependence: size_dependence,
         })
     }
 
-    /// Returns the cache key size for this task, based on our available size
-    /// dependence computed earlier.
-    #[inline]
-    pub fn cache_key_size(
-        &self,
-        local_size: &LayoutSize,
-        scale: LayoutToDeviceScale,
-    ) -> DeviceIntSize {
-        let mut size = DeviceIntSize::zero();
-        if self.available_size_dependence.is_empty() {
-            return size;
-        }
-
-        let device_size = (*local_size * scale).to_i32();
-        if self.available_size_dependence.contains(AvailableSizeDependence::VERTICAL) {
-            size.height = device_size.height;
-        }
-        if self.available_size_dependence.contains(AvailableSizeDependence::HORIZONTAL) {
-            size.width = device_size.width;
-        }
-        size
-    }
-
     pub fn build_instances(&self, border: &NormalBorder) -> Vec<BorderInstance> {
         let mut instances = Vec::new();
 
         for info in &self.border_segments {
             let (side0, side1, flip0, flip1) = match info.segment {
                 BorderSegment::Left => (&border.left, &border.left, false, false),
                 BorderSegment::Top => (&border.top, &border.top, false, false),
                 BorderSegment::Right => (&border.right, &border.right, true, true),
@@ -1038,17 +943,17 @@ impl BorderRenderTaskInfo {
     }
 
     /// Computes the maximum scale that we allow for this set of border parameters.
     /// capping the scale will result in rendering very large corners at a lower
     /// resolution and stretching them, so they will have the right shape, but
     /// blurrier.
     pub fn get_max_scale(
         radii: &BorderRadius,
-        widths: &LayoutSideOffsets
+        widths: &BorderWidths
     ) -> LayoutToDeviceScale {
         let r = radii.top_left.width
             .max(radii.top_left.height)
             .max(radii.top_right.width)
             .max(radii.top_right.height)
             .max(radii.bottom_left.width)
             .max(radii.bottom_left.height)
             .max(radii.bottom_right.width)
@@ -1064,20 +969,16 @@ impl BorderRenderTaskInfo {
 
 fn add_brush_segment(
     image_rect: LayoutRect,
     task_rect: DeviceRect,
     brush_flags: BrushFlags,
     edge_flags: EdgeAaSegmentMask,
     brush_segments: &mut Vec<BrushSegment>,
 ) {
-    if image_rect.size.width <= 0. || image_rect.size.width <= 0. {
-        return;
-    }
-
     brush_segments.push(
         BrushSegment::new(
             image_rect,
             /* may_need_clip_mask = */ true,
             edge_flags,
             [
                 task_rect.origin.x,
                 task_rect.origin.y,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -97,53 +97,62 @@ enum ClipResult {
     Accept,
     // The clip prevents the region from being drawn.
     Reject,
     // The clip affects part of the region. This may
     // require a clip mask, depending on other factors.
     Partial,
 }
 
+// A clip item range represents one or more ClipItem structs.
+// They are stored in a contiguous array inside the ClipStore,
+// and identified by an (offset, count).
+#[derive(Debug, Copy, Clone)]
+pub struct ClipItemRange {
+    pub index: ClipNodeIndex,
+    pub count: u32,
+}
+
 // A clip node is a single clip source, along with some
 // positioning information and implementation details
 // that control where the GPU data for this clip source
 // can be found.
 pub struct ClipNode {
+    pub spatial_node_index: SpatialNodeIndex,
     pub item: ClipItem,
     pub gpu_cache_handle: GpuCacheHandle,
 }
 
 // Flags that are attached to instances of clip nodes.
 bitflags! {
     pub struct ClipNodeFlags: u8 {
         const SAME_SPATIAL_NODE = 0x1;
         const SAME_COORD_SYSTEM = 0x2;
     }
 }
 
 // Identifier for a clip chain. Clip chains are stored
 // in a contiguous array in the clip store. They are
 // identified by a simple index into that array.
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ClipChainId(pub u32);
 
 // The root of each clip chain is the NONE id. The
 // value is specifically set to u32::MAX so that if
 // any code accidentally tries to access the root
 // node, a bounds error will occur.
 impl ClipChainId {
     pub const NONE: Self = ClipChainId(u32::MAX);
 }
 
 // A clip chain node is an id for a range of clip sources,
 // and a link to a parent clip chain node, or ClipChainId::NONE.
 #[derive(Clone)]
 pub struct ClipChainNode {
-    pub clip_node_index: ClipNodeIndex,
-    pub spatial_node_index: SpatialNodeIndex,
+    pub clip_item_range: ClipItemRange,
     pub parent_clip_chain_id: ClipChainId,
 }
 
 // An index into the clip_nodes array.
 #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipNodeIndex(pub u32);
@@ -152,39 +161,31 @@ pub struct ClipNodeIndex(pub u32);
 // clip chain instance, it's stored in an index
 // buffer style structure. This struct contains
 // an index to the node data itself, as well as
 // some flags describing how this clip node instance
 // is positioned.
 #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct ClipNodeInstance {
-    index_and_flags: u32,
-    spatial_node_index: u32,
-}
+pub struct ClipNodeInstance(pub u32);
 
 impl ClipNodeInstance {
-    fn new(
-        index: ClipNodeIndex,
-        flags: ClipNodeFlags,
-        spatial_node_index: SpatialNodeIndex,
-    ) -> ClipNodeInstance {
-        ClipNodeInstance {
-            index_and_flags: (index.0 & 0x00ffffff) | ((flags.bits() as u32) << 24),
-            spatial_node_index: spatial_node_index.0 as u32,
-        }
+    fn new(index: ClipNodeIndex, flags: ClipNodeFlags) -> ClipNodeInstance {
+        ClipNodeInstance(
+            (index.0 & 0x00ffffff) | ((flags.bits() as u32) << 24)
+        )
     }
 
     fn flags(&self) -> ClipNodeFlags {
-        ClipNodeFlags::from_bits_truncate((self.index_and_flags >> 24) as u8)
+        ClipNodeFlags::from_bits_truncate((self.0 >> 24) as u8)
     }
 
     fn index(&self) -> usize {
-        (self.index_and_flags & 0x00ffffff) as usize
+        (self.0 & 0x00ffffff) as usize
     }
 }
 
 // A range of clip node instances that were found by
 // building a clip chain instance.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -205,17 +206,16 @@ enum ClipSpaceConversion {
     Transform(LayoutToWorldTransform),
 }
 
 // Temporary information that is cached and reused
 // during building of a clip chain instance.
 struct ClipNodeInfo {
     conversion: ClipSpaceConversion,
     node_index: ClipNodeIndex,
-    spatial_node_index: SpatialNodeIndex,
     has_non_root_coord_system: bool,
 }
 
 impl ClipNode {
     pub fn update(
         &mut self,
         gpu_cache: &mut GpuCache,
         resource_cache: &mut ResourceCache,
@@ -344,65 +344,66 @@ impl ClipStore {
             clip_nodes: Vec::new(),
             clip_chain_nodes: Vec::new(),
             clip_node_indices: Vec::new(),
             clip_node_info: Vec::new(),
             clip_node_collectors: Vec::new(),
         }
     }
 
+    pub fn add_clip_items(
+        &mut self,
+        clip_items: Vec<ClipItem>,
+        spatial_node_index: SpatialNodeIndex,
+    ) -> ClipItemRange {
+        debug_assert!(!clip_items.is_empty());
+
+        let range = ClipItemRange {
+            index: ClipNodeIndex(self.clip_nodes.len() as u32),
+            count: clip_items.len() as u32,
+        };
+
+        let nodes = clip_items
+            .into_iter()
+            .map(|item| {
+                ClipNode {
+                    item,
+                    spatial_node_index,
+                    gpu_cache_handle: GpuCacheHandle::new(),
+                }
+            });
+
+        self.clip_nodes.extend(nodes);
+        range
+    }
+
     pub fn get_clip_chain(&self, clip_chain_id: ClipChainId) -> &ClipChainNode {
         &self.clip_chain_nodes[clip_chain_id.0 as usize]
     }
 
-    pub fn add_clip_chain_node_index(
+    pub fn add_clip_chain(
         &mut self,
-        clip_node_index: ClipNodeIndex,
-        spatial_node_index: SpatialNodeIndex,
+        clip_item_range: ClipItemRange,
         parent_clip_chain_id: ClipChainId,
     ) -> ClipChainId {
         let id = ClipChainId(self.clip_chain_nodes.len() as u32);
         self.clip_chain_nodes.push(ClipChainNode {
-            clip_node_index,
-            spatial_node_index,
+            clip_item_range,
             parent_clip_chain_id,
         });
         id
     }
 
-    pub fn add_clip_chain_node(
-        &mut self,
-        item: ClipItem,
-        spatial_node_index: SpatialNodeIndex,
-        parent_clip_chain_id: ClipChainId,
-    ) -> ClipChainId {
-        let clip_node_index = ClipNodeIndex(self.clip_nodes.len() as u32);
-        self.clip_nodes.push(ClipNode {
-            item,
-            gpu_cache_handle: GpuCacheHandle::new(),
-        });
-
-        self.add_clip_chain_node_index(
-            clip_node_index,
-            spatial_node_index,
-            parent_clip_chain_id,
-        )
-    }
-
     pub fn get_node_from_range(
         &self,
         node_range: &ClipNodeRange,
         index: u32,
-    ) -> (&ClipNode, ClipNodeFlags, SpatialNodeIndex) {
+    ) -> (&ClipNode, ClipNodeFlags) {
         let instance = self.clip_node_indices[(node_range.first + index) as usize];
-        (
-            &self.clip_nodes[instance.index()],
-            instance.flags(),
-            SpatialNodeIndex(instance.spatial_node_index as usize),
-        )
+        (&self.clip_nodes[instance.index()], instance.flags())
     }
 
     pub fn get_node_from_range_mut(
         &mut self,
         node_range: &ClipNodeRange,
         index: u32,
     ) -> (&mut ClipNode, ClipNodeFlags) {
         let instance = self.clip_node_indices[(node_range.first + index) as usize];
@@ -451,55 +452,55 @@ impl ClipStore {
         // smallest possible local/device clip area.
 
         self.clip_node_info.clear();
         let mut current_clip_chain_id = clip_chain_id;
 
         // for each clip chain node
         while current_clip_chain_id != ClipChainId::NONE {
             let clip_chain_node = &self.clip_chain_nodes[current_clip_chain_id.0 as usize];
+            let node_count = clip_chain_node.clip_item_range.count;
 
-            // Check if any clip node index should actually be
-            // handled during compositing of a rasterization root.
-            match self.clip_node_collectors.iter_mut().find(|c| {
-                clip_chain_node.spatial_node_index < c.raster_root
-            }) {
-                Some(collector) => {
-                    collector.insert(current_clip_chain_id);
-                }
-                None => {
-                    if !add_clip_node_to_current_chain(
-                        clip_chain_node.clip_node_index,
-                        clip_chain_node.spatial_node_index,
-                        spatial_node_index,
-                        &mut local_clip_rect,
-                        &mut self.clip_node_info,
-                        &self.clip_nodes,
-                        clip_scroll_tree,
-                    ) {
-                        return None;
+            // for each clip node (clip source) in this clip chain node
+            for i in 0 .. node_count {
+                let clip_node_index = ClipNodeIndex(clip_chain_node.clip_item_range.index.0 + i);
+                let clip_node = &self.clip_nodes[clip_node_index.0 as usize];
+
+                // Check if any clip node index should actually be
+                // handled during compositing of a rasterization root.
+                match self.clip_node_collectors.iter_mut().find(|c| {
+                    clip_node.spatial_node_index < c.raster_root
+                }) {
+                    Some(collector) => {
+                        collector.insert(clip_node_index);
+                    }
+                    None => {
+                        if !add_clip_node_to_current_chain(
+                            clip_node_index,
+                            spatial_node_index,
+                            &mut local_clip_rect,
+                            &mut self.clip_node_info,
+                            &self.clip_nodes,
+                            clip_scroll_tree,
+                        ) {
+                            return None;
+                        }
                     }
                 }
             }
 
             current_clip_chain_id = clip_chain_node.parent_clip_chain_id;
         }
 
         // Add any collected clips from primitives that should be
         // handled as part of this rasterization root.
         if let Some(clip_node_collector) = clip_node_collector {
-            for clip_chain_id in &clip_node_collector.clips {
-                let (clip_node_index, clip_spatial_node_index) = {
-                    let clip_chain_node = &self.clip_chain_nodes[clip_chain_id.0 as usize];
-                    (clip_chain_node.clip_node_index, clip_chain_node.spatial_node_index)
-                };
-
+            for clip_node_index in &clip_node_collector.clips {
                 if !add_clip_node_to_current_chain(
-                    clip_node_index,
-                    clip_spatial_node_index,
+                    *clip_node_index,
                     spatial_node_index,
                     &mut local_clip_rect,
                     &mut self.clip_node_info,
                     &self.clip_nodes,
                     clip_scroll_tree,
                 ) {
                     return None;
                 }
@@ -591,22 +592,18 @@ impl ClipStore {
                         }
 
                         ClipItem::Rectangle(_, ClipMode::Clip) => {
                             !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM)
                         }
                     };
 
                     // Store this in the index buffer for this clip chain instance.
-                    let instance = ClipNodeInstance::new(
-                        node_info.node_index,
-                        flags,
-                        node_info.spatial_node_index,
-                    );
-                    self.clip_node_indices.push(instance);
+                    self.clip_node_indices
+                        .push(ClipNodeInstance::new(node_info.node_index, flags));
 
                     has_non_root_coord_system |= node_info.has_non_root_coord_system;
                 }
             }
         }
 
         // Get the range identifying the clip nodes in the index buffer.
         let clips_range = ClipNodeRange {
@@ -1117,67 +1114,66 @@ pub fn project_inner_rect(
     ))
 }
 
 // Collects a list of unique clips to be applied to a rasterization
 // root at the end of primitive preparation.
 #[derive(Debug)]
 pub struct ClipNodeCollector {
     raster_root: SpatialNodeIndex,
-    clips: FastHashSet<ClipChainId>,
+    clips: FastHashSet<ClipNodeIndex>,
 }
 
 impl ClipNodeCollector {
     pub fn new(
         raster_root: SpatialNodeIndex,
     ) -> Self {
         ClipNodeCollector {
             raster_root,
             clips: FastHashSet::default(),
         }
     }
 
     pub fn insert(
         &mut self,
-        clip_chain_id: ClipChainId,
+        clip_node_index: ClipNodeIndex,
     ) {
-        self.clips.insert(clip_chain_id);
+        self.clips.insert(clip_node_index);
     }
 }
 
 // Add a clip node into the list of clips to be processed
 // for the current clip chain. Returns false if the clip
 // results in the entire primitive being culled out.
 fn add_clip_node_to_current_chain(
     clip_node_index: ClipNodeIndex,
-    clip_spatial_node_index: SpatialNodeIndex,
     spatial_node_index: SpatialNodeIndex,
     local_clip_rect: &mut LayoutRect,
     clip_node_info: &mut Vec<ClipNodeInfo>,
     clip_nodes: &[ClipNode],
     clip_scroll_tree: &ClipScrollTree,
 ) -> bool {
     let clip_node = &clip_nodes[clip_node_index.0 as usize];
-    let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_spatial_node_index.0];
+    let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_node.spatial_node_index.0 as usize];
     let ref_spatial_node = &clip_scroll_tree.spatial_nodes[spatial_node_index.0];
 
     // Determine the most efficient way to convert between coordinate
     // systems of the primitive and clip node.
-    let conversion = if spatial_node_index == clip_spatial_node_index {
+    let conversion = if spatial_node_index == clip_node.spatial_node_index {
         Some(ClipSpaceConversion::Local)
     } else if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
         let scale_offset = ref_spatial_node
             .coordinate_system_relative_scale_offset
             .difference(
                 &clip_spatial_node.coordinate_system_relative_scale_offset
             );
         Some(ClipSpaceConversion::ScaleOffset(scale_offset))
     } else {
         let xf = clip_scroll_tree.get_relative_transform(
-            clip_spatial_node_index,
+            clip_node.spatial_node_index,
             ROOT_SPATIAL_NODE_INDEX,
         );
 
         xf.map(|xf| {
             ClipSpaceConversion::Transform(xf.with_destination::<WorldPixel>())
         })
     };
 
@@ -1210,15 +1206,14 @@ fn add_clip_node_to_current_chain(
                     //           find some good test cases where this
                     //           would be a worthwhile perf win.
                 }
             }
         }
         clip_node_info.push(ClipNodeInfo {
             conversion,
             node_index: clip_node_index,
-            spatial_node_index: clip_spatial_node_index,
             has_non_root_coord_system: clip_spatial_node.coordinate_system_id != CoordinateSystemId::root(),
         })
     }
 
     true
 }
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -58,26 +58,23 @@ impl CoordinateSystemId {
 pub struct ClipScrollTree {
     /// Nodes which determine the positions (offsets and transforms) for primitives
     /// and clips.
     pub spatial_nodes: Vec<SpatialNode>,
 
     /// A list of transforms that establish new coordinate systems.
     /// Spatial nodes only establish a new coordinate system when
     /// they have a transform that is not a simple 2d translation.
-    coord_systems: Vec<CoordinateSystem>,
+    pub coord_systems: Vec<CoordinateSystem>,
 
     pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayoutPoint, ScrollClamping)>,
 
     /// A set of pipelines which should be discarded the next time this
     /// tree is drained.
     pub pipelines_to_discard: FastHashSet<PipelineId>,
-
-    /// Temporary stack of nodes to update when traversing the tree.
-    nodes_to_update: Vec<(SpatialNodeIndex, TransformUpdateState)>,
 }
 
 #[derive(Clone)]
 pub struct TransformUpdateState {
     pub parent_reference_frame_transform: LayoutToWorldFastTransform,
     pub parent_accumulated_scroll_offset: LayoutVector2D,
     pub nearest_scrolling_ancestor_offset: LayoutVector2D,
     pub nearest_scrolling_ancestor_viewport: LayoutRect,
@@ -99,17 +96,16 @@ pub struct TransformUpdateState {
 
 impl ClipScrollTree {
     pub fn new() -> Self {
         ClipScrollTree {
             spatial_nodes: Vec::new(),
             coord_systems: Vec::new(),
             pending_scroll_offsets: FastHashMap::default(),
             pipelines_to_discard: FastHashSet::default(),
-            nodes_to_update: Vec::new(),
         }
     }
 
     /// Calculate the relative transform from `ref_node_index`
     /// to `target_node_index`. It's assumed that `ref_node_index`
     /// is a parent of `target_node_index`. This method will
     /// panic if that invariant isn't true!
     pub fn get_relative_transform(
@@ -257,49 +253,72 @@ impl ClipScrollTree {
         let mut transform_palette = TransformPalette::new(self.spatial_nodes.len());
         if self.spatial_nodes.is_empty() {
             return transform_palette;
         }
 
         self.coord_systems.clear();
         self.coord_systems.push(CoordinateSystem::root());
 
-        let root_node_index = self.root_reference_frame_index();
-        let state = TransformUpdateState {
+        let root_reference_frame_index = self.root_reference_frame_index();
+        let mut state = TransformUpdateState {
             parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
             parent_accumulated_scroll_offset: LayoutVector2D::zero(),
             nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
             nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
             current_coordinate_system_id: CoordinateSystemId::root(),
             coordinate_system_relative_scale_offset: ScaleOffset::identity(),
             invertible: true,
         };
-        debug_assert!(self.nodes_to_update.is_empty());
-        self.nodes_to_update.push((root_node_index, state));
+
+        self.update_node(
+            root_reference_frame_index,
+            &mut state,
+            &mut transform_palette,
+            scene_properties,
+        );
+
+        transform_palette
+    }
 
-        while let Some((node_index, mut state)) = self.nodes_to_update.pop() {
+    fn update_node(
+        &mut self,
+        node_index: SpatialNodeIndex,
+        state: &mut TransformUpdateState,
+        transform_palette: &mut TransformPalette,
+        scene_properties: &SceneProperties,
+    ) {
+        // TODO(gw): This is an ugly borrow check workaround to clone these.
+        //           Restructure this to avoid the clones!
+        let mut state = state.clone();
+        let node_children = {
             let node = match self.spatial_nodes.get_mut(node_index.0) {
                 Some(node) => node,
-                None => continue,
+                None => return,
             };
 
             node.update(&mut state, &mut self.coord_systems, scene_properties);
-            node.push_gpu_data(&mut transform_palette, node_index);
+            node.push_gpu_data(transform_palette, node_index);
+
+            if node.children.is_empty() {
+                return;
+            }
 
-            if !node.children.is_empty() {
-                node.prepare_state_for_children(&mut state);
-                self.nodes_to_update.extend(node.children
-                    .iter()
-                    .rev()
-                    .map(|child_index| (*child_index, state.clone()))
-                );
-            }
+            node.prepare_state_for_children(&mut state);
+            node.children.clone()
+        };
+
+        for child_node_index in node_children {
+            self.update_node(
+                child_node_index,
+                &mut state,
+                transform_palette,
+                scene_properties,
+            );
         }
-
-        transform_palette
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
         for node in &mut self.spatial_nodes {
             let external_id = match node.node_type {
                 SpatialNodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
                 _ => continue,
             };
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -27,82 +27,54 @@ use picture::{PictureCompositeMode, Pict
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, ImageSource};
 use prim_store::{BorderSource, BrushSegment, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
-use std::{f32, mem};
+use std::{f32, iter, mem};
 use tiling::{CompositeOps, ScrollbarPrimitive};
 use util::{MaxRect, RectHelpers};
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
     r: 0.3,
     g: 0.3,
     b: 0.3,
     a: 0.6,
 };
 
-#[derive(Debug, Copy, Clone)]
-struct ClipNode {
-    id: ClipChainId,
-    count: usize,
-}
-
-impl ClipNode {
-    fn new(id: ClipChainId, count: usize) -> ClipNode {
-        ClipNode {
-            id,
-            count,
-        }
-    }
-}
-
 /// A data structure that keeps track of mapping between API ClipIds and the indices used
 /// internally in the ClipScrollTree to avoid having to do HashMap lookups. ClipIdToIndexMapper is
 /// responsible for mapping both ClipId to ClipChainIndex and ClipId to SpatialNodeIndex.
 #[derive(Default)]
 pub struct ClipIdToIndexMapper {
-    clip_node_map: FastHashMap<ClipId, ClipNode>,
+    clip_chain_map: FastHashMap<ClipId, ClipChainId>,
     spatial_node_map: FastHashMap<ClipId, SpatialNodeIndex>,
 }
 
 impl ClipIdToIndexMapper {
-    pub fn add_clip_chain(
-        &mut self,
-        id: ClipId,
-        index: ClipChainId,
-        count: usize,
-    ) {
-        let _old_value = self.clip_node_map.insert(id, ClipNode::new(index, count));
+    pub fn add_clip_chain(&mut self, id: ClipId, index: ClipChainId) {
+        let _old_value = self.clip_chain_map.insert(id, index);
         debug_assert!(_old_value.is_none());
     }
 
-    pub fn map_to_parent_clip_chain(
-        &mut self,
-        id: ClipId,
-        parent_id: &ClipId,
-    ) {
-        let parent_node = self.clip_node_map[parent_id];
-        self.add_clip_chain(id, parent_node.id, parent_node.count);
+    pub fn map_to_parent_clip_chain(&mut self, id: ClipId, parent_id: &ClipId) {
+        let parent_chain_id = self.get_clip_chain_id(parent_id);
+        self.add_clip_chain(id, parent_chain_id);
     }
 
     pub fn map_spatial_node(&mut self, id: ClipId, index: SpatialNodeIndex) {
         let _old_value = self.spatial_node_map.insert(id, index);
         debug_assert!(_old_value.is_none());
     }
 
-    fn get_clip_node(&self, id: &ClipId) -> ClipNode {
-        self.clip_node_map[id]
-    }
-
     pub fn get_clip_chain_id(&self, id: &ClipId) -> ClipChainId {
-        self.clip_node_map[id].id
+        self.clip_chain_map[id]
     }
 
     pub fn get_spatial_node_index(&self, id: ClipId) -> SpatialNodeIndex {
         match id {
             ClipId::Clip(..) |
             ClipId::Spatial(..) => {
                 self.spatial_node_map[&id]
             }
@@ -697,63 +669,43 @@ impl<'a> DisplayListFlattener<'a> {
                 };
 
                 // Create a linked list of clip chain nodes. To do this, we will
                 // create a clip chain node + clip source for each listed clip id,
                 // and link these together, with the root of this list parented to
                 // the parent clip chain node found above. For this API, the clip
                 // id that is specified for an existing clip chain node is used to
                 // get the index of the clip sources that define that clip node.
+
                 let mut clip_chain_id = parent_clip_chain_id;
 
                 // For each specified clip id
                 for item in self.get_clip_chain_items(pipeline_id, item.clip_chain_items()) {
                     // Map the ClipId to an existing clip chain node.
-                    let item_clip_node = self
+                    let item_clip_chain_id = self
                         .id_to_index_mapper
-                        .get_clip_node(&item);
-
-                    let mut clip_node_clip_chain_id = item_clip_node.id;
-
-                    // Each 'clip node' (as defined by the WR API) can contain one or
-                    // more clip items (e.g. rects, image masks, rounded rects). When
-                    // each of these clip nodes is stored internally, they are stored
-                    // as a clip chain (one clip item per node), eventually parented
-                    // to the parent clip node. For a user defined clip chain, we will
-                    // need to walk the linked list of clip chain nodes for each clip
-                    // node, accumulating them into one clip chain that is then
-                    // parented to the clip chain parent.
-
-                    for _ in 0 .. item_clip_node.count {
-                        // Get the id of the clip sources entry for that clip chain node.
-                        let (clip_node_index, spatial_node_index) = {
-                            let clip_chain = self
-                                .clip_store
-                                .get_clip_chain(clip_node_clip_chain_id);
-
-                            clip_node_clip_chain_id = clip_chain.parent_clip_chain_id;
-
-                            (clip_chain.clip_node_index, clip_chain.spatial_node_index)
-                        };
-
-                        // Add a new clip chain node, which references the same clip sources, and
-                        // parent it to the current parent.
-                        clip_chain_id = self
-                            .clip_store
-                            .add_clip_chain_node_index(
-                                clip_node_index,
-                                spatial_node_index,
-                                clip_chain_id,
-                            );
-                    }
+                        .get_clip_chain_id(&item);
+                    // Get the id of the clip sources entry for that clip chain node.
+                    let clip_item_range = self
+                        .clip_store
+                        .get_clip_chain(item_clip_chain_id)
+                        .clip_item_range;
+                    // Add a new clip chain node, which references the same clip sources, and
+                    // parent it to the current parent.
+                    clip_chain_id = self
+                        .clip_store
+                        .add_clip_chain(clip_item_range, parent_clip_chain_id);
+                    // For the next clip node, use this new clip chain node as the parent,
+                    // to form a linked list.
+                    parent_clip_chain_id = clip_chain_id;
                 }
 
                 // Map the last entry in the clip chain to the supplied ClipId. This makes
                 // this ClipId available as a source to other user defined clip chains.
-                self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_id, 0);
+                self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_id);
             },
             SpecificDisplayItem::ScrollFrame(ref info) => {
                 self.flatten_scroll_frame(
                     &item,
                     info,
                     pipeline_id,
                     &clip_and_scroll_ids,
                     &reference_frame_relative_offset
@@ -793,28 +745,26 @@ impl<'a> DisplayListFlattener<'a> {
         &mut self,
         clip_items: Vec<ClipItem>,
         spatial_node_index: SpatialNodeIndex,
         parent_clip_chain_id: ClipChainId,
     ) -> ClipChainId {
         if clip_items.is_empty() {
             parent_clip_chain_id
         } else {
-            let mut clip_chain_id = parent_clip_chain_id;
+            // Add a range of clip sources.
+            let clip_item_range = self
+                .clip_store
+                .add_clip_items(clip_items, spatial_node_index);
 
-            for item in clip_items {
-                clip_chain_id = self.clip_store
-                                    .add_clip_chain_node(
-                                        item,
-                                        spatial_node_index,
-                                        clip_chain_id,
-                                    );
-            }
-
-            clip_chain_id
+            // Add clip chain node that references the clip source range.
+            self.clip_store.add_clip_chain(
+                clip_item_range,
+                parent_clip_chain_id,
+            )
         }
     }
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
@@ -1222,17 +1172,17 @@ impl<'a> DisplayListFlattener<'a> {
             origin_in_parent_reference_frame,
             pipeline_id,
         );
         self.id_to_index_mapper.map_spatial_node(reference_frame_id, index);
 
         match parent_id {
             Some(ref parent_id) =>
                 self.id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
-            _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainId::NONE, 0),
+            _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainId::NONE),
         }
         index
     }
 
     pub fn setup_viewport_offset(
         &mut self,
         inner_rect: DeviceUintRect,
         device_pixel_scale: DevicePixelScale,
@@ -1280,74 +1230,54 @@ impl<'a> DisplayListFlattener<'a> {
     ) -> ClipChainId
     where
         I: IntoIterator<Item = ComplexClipRegion>
     {
         // Add a new ClipNode - this is a ClipId that identifies a list of clip items,
         // and the positioning node associated with those clip sources.
 
         // Map from parent ClipId to existing clip-chain.
-        let mut parent_clip_chain_index = self
+        let parent_clip_chain_index = self
             .id_to_index_mapper
             .get_clip_chain_id(&parent_id);
         // Map the ClipId for the positioning node to a spatial node index.
         let spatial_node = self.id_to_index_mapper.get_spatial_node_index(parent_id);
 
+        // Build the clip sources from the supplied region.
+        // TODO(gw): We should fix this up to take advantage of the recent
+        //           work to avoid heap allocations where possible!
+        let clip_rect = iter::once(ClipItem::Rectangle(clip_region.main, ClipMode::Clip));
+        let clip_image = clip_region.image_mask.map(ClipItem::Image);
+        let clips_complex = clip_region.complex_clips
+            .into_iter()
+            .map(|complex| ClipItem::new_rounded_rect(
+                complex.rect,
+                complex.radii,
+                complex.mode,
+            ));
+        let clips = clip_rect.chain(clip_image).chain(clips_complex).collect();
+
+        // Add those clip sources to the clip store.
+        let clip_item_range = self
+            .clip_store
+            .add_clip_items(clips, spatial_node);
+
         // Add a mapping for this ClipId in case it's referenced as a positioning node.
         self.id_to_index_mapper
             .map_spatial_node(new_node_id, spatial_node);
 
-        let mut clip_count = 0;
-
-        // Build the clip sources from the supplied region.
-        parent_clip_chain_index = self
+        // Add the new clip chain entry
+        let clip_chain_id = self
             .clip_store
-            .add_clip_chain_node(
-                ClipItem::Rectangle(clip_region.main, ClipMode::Clip),
-                spatial_node,
-                parent_clip_chain_index,
-            );
-        clip_count += 1;
-
-        if let Some(image_mask) = clip_region.image_mask {
-            parent_clip_chain_index = self
-                .clip_store
-                .add_clip_chain_node(
-                    ClipItem::Image(image_mask),
-                    spatial_node,
-                    parent_clip_chain_index,
-                );
-            clip_count += 1;
-        }
-
-        for region in clip_region.complex_clips {
-            let clip_item = ClipItem::new_rounded_rect(
-                region.rect,
-                region.radii,
-                region.mode,
-            );
-
-            parent_clip_chain_index = self
-                .clip_store
-                .add_clip_chain_node(
-                    clip_item,
-                    spatial_node,
-                    parent_clip_chain_index,
-                );
-            clip_count += 1;
-        }
+            .add_clip_chain(clip_item_range, parent_clip_chain_index);
 
         // Map the supplied ClipId -> clip chain id.
-        self.id_to_index_mapper.add_clip_chain(
-            new_node_id,
-            parent_clip_chain_index,
-            clip_count,
-        );
+        self.id_to_index_mapper.add_clip_chain(new_node_id, clip_chain_id);
 
-        parent_clip_chain_index
+        clip_chain_id
     }
 
     pub fn add_scroll_frame(
         &mut self,
         new_node_id: ClipId,
         parent_id: ClipId,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -227,40 +227,30 @@ impl Row {
 pub enum GpuCacheUpdate {
     Copy {
         block_index: usize,
         block_count: usize,
         address: GpuCacheAddress,
     },
 }
 
-pub struct GpuDebugChunk {
-    pub address: GpuCacheAddress,
-    pub fresh: bool,
-    pub tag: u8,
-    pub size: u16,
-}
-
 #[must_use]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GpuCacheUpdateList {
     /// The frame current update list was generated from.
     pub frame_id: FrameId,
     /// The current height of the texture. The render thread
     /// should resize the texture if required.
     pub height: u32,
     /// List of updates to apply.
     pub updates: Vec<GpuCacheUpdate>,
     /// A flat list of GPU blocks that are pending upload
     /// to GPU memory.
     pub blocks: Vec<GpuBlockData>,
-    /// Whole state GPU block metadata for debugging.
-    #[cfg_attr(feature = "serde", serde(skip))]
-    pub debug_chunks: Vec<GpuDebugChunk>,
 }
 
 // Holds the free lists of fixed size blocks. Mostly
 // just serves to work around the borrow checker.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct FreeBlockLists {
     free_list_1: Option<BlockIndex>,
@@ -540,28 +530,24 @@ impl<'a> Drop for GpuDataRequest<'a> {
 pub struct GpuCache {
     /// Current frame ID.
     frame_id: FrameId,
     /// CPU-side texture allocator.
     texture: Texture,
     /// Number of blocks requested this frame that don't
     /// need to be re-uploaded.
     saved_block_count: usize,
-    /// True if the Renderer expects to receive the metadata
-    /// about GPU blocks with on each update.
-    in_debug: bool,
 }
 
 impl GpuCache {
     pub fn new() -> Self {
         GpuCache {
             frame_id: FrameId::new(0),
             texture: Texture::new(),
             saved_block_count: 0,
-            in_debug: false,
         }
     }
 
     /// Begin a new frame.
     pub fn begin_frame(&mut self) {
         debug_assert!(self.texture.pending_blocks.is_empty());
         self.frame_id = self.frame_id + 1;
         self.texture.evict_old_blocks(self.frame_id);
@@ -652,41 +638,21 @@ impl GpuCache {
         self.frame_id
     }
 
     /// Extract the pending updates from the cache.
     pub fn extract_updates(&mut self) -> GpuCacheUpdateList {
         GpuCacheUpdateList {
             frame_id: self.frame_id,
             height: self.texture.height,
-            debug_chunks: if self.in_debug {
-                self.texture.updates
-                    .iter()
-                    .map(|update| match *update {
-                        GpuCacheUpdate::Copy { address, block_index, block_count } => GpuDebugChunk {
-                            address,
-                            fresh: self.frame_id == self.texture.blocks[block_index].last_access_time,
-                            tag: 0, //TODO
-                            size: block_count.min(0xFFFF) as u16,
-                        }
-                    })
-                    .collect()
-            } else {
-                Vec::new()
-            },
             updates: mem::replace(&mut self.texture.updates, Vec::new()),
             blocks: mem::replace(&mut self.texture.pending_blocks, Vec::new()),
         }
     }
 
-    /// Enable GPU block debugging.
-    pub fn set_debug(&mut self, enable: bool) {
-        self.in_debug = enable;
-    }
-
     /// Get the actual GPU address in the texture for a given slot ID.
     /// It's assumed at this point that the given slot has been requested
     /// and built for this frame. Attempting to get the address for a
     /// freed or pending slot will panic!
     pub fn get_address(&self, id: &GpuCacheHandle) -> GpuCacheAddress {
         let location = id.location.expect("handle not requested or allocated!");
         let block = &self.texture.blocks[location.block_index.0];
         debug_assert_eq!(block.epoch, location.epoch);
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -22,16 +22,19 @@ pub struct HitTestSpatialNode {
     /// World transform for content transformed by this node.
     world_content_transform: LayoutToWorldFastTransform,
 
     /// World viewport transform for content transformed by this node.
     world_viewport_transform: LayoutToWorldFastTransform,
 }
 
 pub struct HitTestClipNode {
+    /// The positioning node for this clip node.
+    spatial_node: SpatialNodeIndex,
+
     /// A particular point must be inside all of these regions to be considered clipped in
     /// for the purposes of a hit test.
     region: HitTestRegion,
 }
 
 impl HitTestClipNode {
     fn new(node: &ClipNode) -> Self {
         let region = match node.item {
@@ -39,16 +42,17 @@ impl HitTestClipNode {
             ClipItem::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
             ClipItem::Image(ref mask) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
             ClipItem::LineDecoration(_) |
             ClipItem::BoxShadow(_) => HitTestRegion::Invalid,
         };
 
         HitTestClipNode {
+            spatial_node: node.spatial_node_index,
             region,
         }
     }
 }
 
 #[derive(Clone)]
 pub struct HitTestingItem {
     rect: LayoutRect,
@@ -170,44 +174,41 @@ impl HitTester {
             test,
         );
 
         if !parent_clipped_in {
             test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
             return false;
         }
 
-        if !self.is_point_clipped_in_for_clip_node(
-            point,
-            descriptor.clip_node_index,
-            descriptor.spatial_node_index,
-            test,
-        ) {
-            test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
-            return false;
+        for i in 0 .. descriptor.clip_item_range.count {
+            let clip_node_index = ClipNodeIndex(descriptor.clip_item_range.index.0 + i);
+            if !self.is_point_clipped_in_for_clip_node(point, clip_node_index, test) {
+                test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::NotClippedIn);
+                return false;
+            }
         }
 
         test.set_in_clip_chain_cache(clip_chain_id, ClippedIn::ClippedIn);
         true
     }
 
     fn is_point_clipped_in_for_clip_node(
         &self,
         point: WorldPoint,
         node_index: ClipNodeIndex,
-        spatial_node_index: SpatialNodeIndex,
         test: &mut HitTest
     ) -> bool {
         if let Some(clipped_in) = test.node_cache.get(&node_index) {
             return *clipped_in == ClippedIn::ClippedIn;
         }
 
         let node = &self.clip_nodes[node_index.0 as usize];
         let transform = self
-            .spatial_nodes[spatial_node_index.0]
+            .spatial_nodes[node.spatial_node.0 as usize]
             .world_viewport_transform;
         let transformed_point = match transform
             .inverse()
             .and_then(|inverted| inverted.transform_point2d(&point))
         {
             Some(point) => point,
             None => {
                 test.node_cache.insert(node_index, ClippedIn::NotClippedIn);
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,19 +1,18 @@
 /* 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/. */
 
 use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, PictureRect};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, PictureToRasterTransform};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
-use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
-use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
-use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect};
-use api::{PicturePixel, RasterPixel};
+use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
+use api::{PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets, WorldPixel};
+use api::{BorderWidths, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect, PicturePixel, RasterPixel};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
 use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 use euclid::{TypedTransform3D, TypedRect};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
@@ -346,17 +345,17 @@ pub struct VisibleGradientTile {
 #[derive(Debug)]
 pub enum BorderSource {
     Image(ImageRequest),
     Border {
         handle: Option<RenderTaskCacheEntryHandle>,
         cache_key: BorderCacheKey,
         task_info: Option<BorderRenderTaskInfo>,
         border: NormalBorder,
-        widths: LayoutSideOffsets,
+        widths: BorderWidths,
     },
 }
 
 #[derive(Debug)]
 pub enum BrushKind {
     Solid {
         color: ColorF,
         opacity_binding: OpacityBinding,
@@ -438,17 +437,17 @@ impl BrushKind {
         BrushKind::Solid {
             color,
             opacity_binding: OpacityBinding::new(),
         }
     }
 
     // Construct a brush that is a border with `border` style and `widths`
     // dimensions.
-    pub fn new_border(mut border: NormalBorder, widths: LayoutSideOffsets) -> BrushKind {
+    pub fn new_border(mut border: NormalBorder, widths: BorderWidths) -> BrushKind {
         // FIXME(emilio): Is this the best place to do this?
         border.normalize(&widths);
 
         let cache_key = BorderCacheKey::new(&border, &widths);
         BrushKind::Border {
             source: BorderSource::Border {
                 border,
                 widths,
@@ -518,24 +517,24 @@ pub struct BrushSegment {
     pub may_need_clip_mask: bool,
     pub edge_flags: EdgeAaSegmentMask,
     pub extra_data: [f32; 4],
     pub brush_flags: BrushFlags,
 }
 
 impl BrushSegment {
     pub fn new(
-        local_rect: LayoutRect,
+        rect: LayoutRect,
         may_need_clip_mask: bool,
         edge_flags: EdgeAaSegmentMask,
         extra_data: [f32; 4],
         brush_flags: BrushFlags,
-    ) -> Self {
-        Self {
-            local_rect,
+    ) -> BrushSegment {
+        BrushSegment {
+            local_rect: rect,
             clip_task_id: BrushSegmentTaskId::Opaque,
             may_need_clip_mask,
             edge_flags,
             extra_data,
             brush_flags,
         }
     }
 }
@@ -2069,19 +2068,17 @@ fn write_brush_segment_description(
         metadata.local_rect,
         None,
         metadata.local_clip_rect
     );
 
     // Segment the primitive on all the local-space clip sources that we can.
     let mut local_clip_count = 0;
     for i in 0 .. clip_chain.clips_range.count {
-        let (clip_node, flags, _) = frame_state
-            .clip_store
-            .get_node_from_range(&clip_chain.clips_range, i);
+        let (clip_node, flags) = frame_state.clip_store.get_node_from_range(&clip_chain.clips_range, i);
 
         // If this clip item is positioned by another positioning node, its relative position
         // could change during scrolling. This means that we would need to resegment. Instead
         // of doing that, only segment with clips that have the same positioning node.
         // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only
         // when necessary while scrolling.
         if !flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) {
             continue;
@@ -2849,42 +2846,35 @@ impl Primitive {
             // TODO(gw): When drawing in screen raster mode, we should also incorporate a
             //           scale factor from the world transform to get an appropriately
             //           sized border task.
             let world_scale = LayoutToWorldScale::new(1.0);
             let mut scale = world_scale * frame_context.device_pixel_scale;
             let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius, &widths);
             scale.0 = scale.0.min(max_scale.0);
             let scale_au = Au::from_f32_px(scale.0);
-
-            // NOTE(emilio): This `needs_update` relies on the local rect for a
-            // given primitive being immutable. If that changes, this code
-            // should probably handle changes to it as well, retaining the old
-            // size in cache_key.
             let needs_update = scale_au != cache_key.scale;
-
             let mut new_segments = Vec::new();
 
-            let local_rect = &self.metadata.local_rect;
             if needs_update {
                 cache_key.scale = scale_au;
 
                 *task_info = BorderRenderTaskInfo::new(
-                    local_rect,
+                    &self.metadata.local_rect,
                     border,
                     widths,
                     scale,
                     &mut new_segments,
                 );
             }
 
             *handle = task_info.as_ref().map(|task_info| {
                 frame_state.resource_cache.request_render_task(
                     RenderTaskCacheKey {
-                        size: task_info.cache_key_size(&local_rect.size, scale),
+                        size: DeviceIntSize::zero(),
                         kind: RenderTaskCacheKeyKind::Border(cache_key.clone()),
                     },
                     frame_state.gpu_cache,
                     frame_state.render_tasks,
                     None,
                     false,          // todo
                     |render_tasks| {
                         let task = RenderTask::new_border(
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -752,20 +752,16 @@ impl RenderBackend {
 
                         self.low_priority_scene_tx.send(SceneBuilderRequest::SetFrameBuilderConfig(
                             self.frame_config.clone()
                         )).unwrap();
 
                         // We don't want to forward this message to the renderer.
                         return true;
                     }
-                    DebugCommand::EnableGpuCacheDebug(enable) => {
-                        self.gpu_cache.set_debug(enable);
-                        ResultMsg::DebugCommand(option)
-                    }
                     DebugCommand::FetchDocuments => {
                         let json = self.get_docs_for_debugger();
                         ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
                     }
                     DebugCommand::FetchClipScrollTree => {
                         let json = self.get_clip_scroll_tree_for_debugger();
                         ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json))
                     }
@@ -816,26 +812,16 @@ impl RenderBackend {
                         // Note: we can't pass `LoadCapture` here since it needs to arrive
                         // before the `PublishDocument` messages sent by `load_capture`.
                         return true;
                     }
                     DebugCommand::ClearCaches(mask) => {
                         self.resource_cache.clear(mask);
                         return true;
                     }
-                    DebugCommand::SimulateLongSceneBuild(time_ms) => {
-                        self.scene_tx.send(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)).unwrap();
-                        return true;
-                    }
-                    DebugCommand::SimulateLongLowPrioritySceneBuild(time_ms) => {
-                        self.low_priority_scene_tx.send(
-                            SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(time_ms)
-                        ).unwrap();
-                        return true;
-                    }
                     _ => ResultMsg::DebugCommand(option),
                 };
                 self.result_tx.send(msg).unwrap();
                 self.notifier.wake_up();
             }
             ApiMsg::ShutDown => {
                 return false;
             }
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -22,25 +22,21 @@ use batch::{BatchKind, BatchTextures, Br
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
 use device::{ExternalTexture, FBOId, TextureSlot};
 use device::{FileWatcherHandler, ShaderError, TextureFilter,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
-#[cfg(feature = "debug_renderer")]
-use euclid::rect;
-use euclid::Transform3D;
+use euclid::{rect, Transform3D};
 use frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use gleam::gl;
 use glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
-#[cfg(feature = "debug_renderer")]
-use gpu_cache::GpuDebugChunk;
 #[cfg(feature = "pathfinder")]
 use gpu_glyph_renderer::GpuGlyphRenderer;
 use internal_types::{SourceTexture, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError};
 use internal_types::{CacheTextureId, DebugOutput, FastHashMap, RenderedDocument, ResultMsg};
 use internal_types::{TextureUpdateList, TextureUpdateOp, TextureUpdateSource};
 use internal_types::{RenderTargetInfo, SavedTargetIndex};
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, FrameProfileCounters,
@@ -226,32 +222,32 @@ bitflags! {
         const GPU_SAMPLE_QUERIES    = 1 << 4;
         const DISABLE_BATCHING      = 1 << 5;
         const EPOCHS                = 1 << 6;
         const COMPACT_PROFILER      = 1 << 7;
         const ECHO_DRIVER_MESSAGES  = 1 << 8;
         const NEW_FRAME_INDICATOR   = 1 << 9;
         const NEW_SCENE_INDICATOR   = 1 << 10;
         const SHOW_OVERDRAW         = 1 << 11;
-        const GPU_CACHE_DBG         = 1 << 12;
     }
 }
 
 fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option<bool> {
     if before & select != after & select {
         Some(after.contains(select))
     } else {
         None
     }
 }
 
 #[repr(C)]
 #[derive(Copy, Clone, Debug)]
 pub enum ShaderColorMode {
     FromRenderPassMode = 0,
+
     Alpha = 1,
     SubpixelConstantTextColor = 2,
     SubpixelWithBgColorPass0 = 3,
     SubpixelWithBgColorPass1 = 4,
     SubpixelWithBgColorPass2 = 5,
     SubpixelDualSource = 6,
     Bitmap = 7,
     ColorBitmap = 8,
@@ -1358,18 +1354,16 @@ pub struct Renderer {
     pub gpu_profile: GpuProfiler<GpuProfileTag>,
     vaos: RendererVAOs,
 
     prim_header_f_texture: VertexDataTexture,
     prim_header_i_texture: VertexDataTexture,
     transforms_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
     gpu_cache_texture: CacheTexture,
-    #[cfg(feature = "debug_renderer")]
-    gpu_cache_debug_chunks: Vec<GpuDebugChunk>,
 
     gpu_cache_frame_id: FrameId,
     gpu_cache_overflow: bool,
 
     pipeline_info: PipelineInfo,
 
     // Manages and resolves source textures IDs to real texture IDs.
     texture_resolver: SourceTextureResolver,
@@ -1711,17 +1705,16 @@ impl Renderer {
             }
         })?;
 
         let low_priority_scene_tx = if options.support_low_priority_transactions {
             let (low_priority_scene_tx, low_priority_scene_rx) = channel();
             let lp_builder = LowPrioritySceneBuilder {
                 rx: low_priority_scene_rx,
                 tx: scene_tx.clone(),
-                simulate_slow_ms: 0,
             };
 
             thread::Builder::new().name(lp_scene_thread_name.clone()).spawn(move || {
                 register_thread_with_profiler(lp_scene_thread_name.clone());
                 if let Some(ref thread_listener) = *thread_listener_for_lp_scene_builder {
                     thread_listener.thread_started(&lp_scene_thread_name);
                 }
 
@@ -1818,18 +1811,16 @@ impl Renderer {
             dither_matrix_texture,
             external_image_handler: None,
             output_image_handler: None,
             size_of_op: options.size_of_op,
             output_targets: FastHashMap::default(),
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
-            #[cfg(feature = "debug_renderer")]
-            gpu_cache_debug_chunks: Vec::new(),
             gpu_cache_frame_id: FrameId::new(0),
             gpu_cache_overflow: false,
             texture_cache_upload_pbo,
             texture_resolver,
             renderer_errors: Vec::new(),
             #[cfg(feature = "capture")]
             read_fbo,
             #[cfg(feature = "replay")]
@@ -1918,21 +1909,17 @@ impl Renderer {
                     //            2) New frame contains a texture cache update which
                     //               frees Texture X.
                     //            3) bad stuff happens.
 
                     //TODO: associate `document_id` with target window
                     self.pending_texture_updates.push(texture_update_list);
                     self.backend_profile_counters = profile_counters;
                 }
-                ResultMsg::UpdateGpuCache(mut list) => {
-                    #[cfg(feature = "debug_renderer")]
-                    {
-                        self.gpu_cache_debug_chunks = mem::replace(&mut list.debug_chunks, Vec::new());
-                    }
+                ResultMsg::UpdateGpuCache(list) => {
                     self.pending_gpu_cache_updates.push(list);
                 }
                 ResultMsg::UpdateResources {
                     updates,
                     cancel_rendering,
                 } => {
                     self.pending_texture_updates.push(updates);
                     self.device.begin_frame();
@@ -2158,19 +2145,16 @@ impl Renderer {
                 self.set_debug_flag(DebugFlags::PROFILER_DBG, enable);
             }
             DebugCommand::EnableTextureCacheDebug(enable) => {
                 self.set_debug_flag(DebugFlags::TEXTURE_CACHE_DBG, enable);
             }
             DebugCommand::EnableRenderTargetDebug(enable) => {
                 self.set_debug_flag(DebugFlags::RENDER_TARGET_DBG, enable);
             }
-            DebugCommand::EnableGpuCacheDebug(enable) => {
-                self.set_debug_flag(DebugFlags::GPU_CACHE_DBG, enable);
-            }
             DebugCommand::EnableGpuTimeQueries(enable) => {
                 self.set_debug_flag(DebugFlags::GPU_TIME_QUERIES, enable);
             }
             DebugCommand::EnableGpuSampleQueries(enable) => {
                 self.set_debug_flag(DebugFlags::GPU_SAMPLE_QUERIES, enable);
             }
             DebugCommand::EnableNewFrameIndicator(enable) => {
                 self.set_debug_flag(DebugFlags::NEW_FRAME_INDICATOR, enable);
@@ -2197,19 +2181,17 @@ impl Renderer {
             DebugCommand::FetchScreenshot => {
                 let json = self.get_screenshot_for_debugger();
                 self.debug_server.send(json);
             }
             DebugCommand::SaveCapture(..) |
             DebugCommand::LoadCapture(..) => {
                 panic!("Capture commands are not welcome here! Did you build with 'capture' feature?")
             }
-            DebugCommand::ClearCaches(_)
-            | DebugCommand::SimulateLongSceneBuild(_)
-            | DebugCommand::SimulateLongLowPrioritySceneBuild(_) => {}
+            DebugCommand::ClearCaches(_) => {}
             DebugCommand::InvalidateGpuCache => {
                 match self.gpu_cache_texture.bus {
                     CacheBus::PixelBuffer { ref mut rows, .. } => {
                         info!("Invalidating GPU caches");
                         for row in rows {
                             row.is_dirty = true;
                         }
                     }
@@ -2490,17 +2472,16 @@ impl Renderer {
         // always pass an extra update list with at least one block in it.
         let gpu_cache_height = self.gpu_cache_texture.get_height();
         if gpu_cache_height != 0 && GPU_CACHE_RESIZE_TEST {
             self.pending_gpu_cache_updates.push(GpuCacheUpdateList {
                 frame_id: FrameId::new(0),
                 height: gpu_cache_height,
                 blocks: vec![[1f32; 4].into()],
                 updates: Vec::new(),
-                debug_chunks: Vec::new(),
             });
         }
 
         let (updated_blocks, max_requested_height) = self
             .pending_gpu_cache_updates
             .iter()
             .fold((0, gpu_cache_height), |(count, height), list| {
                 (count + list.blocks.len(), cmp::max(height, list.height))
@@ -3471,17 +3452,16 @@ impl Renderer {
             .as_mut()
             .expect("Found external image, but no handler set!");
 
         let mut list = GpuCacheUpdateList {
             frame_id: FrameId::new(0),
             height: self.gpu_cache_texture.get_height(),
             blocks: Vec::new(),
             updates: Vec::new(),
-            debug_chunks: Vec::new(),
         };
 
         for deferred_resolve in deferred_resolves {
             self.gpu_profile.place_marker("deferred resolve");
             let props = &deferred_resolve.image_properties;
             let ext_image = props
                 .external_image
                 .expect("BUG: Deferred resolves must be external images!");
@@ -3802,26 +3782,23 @@ impl Renderer {
 
             self.texture_resolver.end_pass(
                 cur_alpha,
                 cur_color,
             );
         }
 
         self.texture_resolver.end_frame();
+        if let Some(framebuffer_size) = framebuffer_size {
+            self.draw_render_target_debug(framebuffer_size);
+            self.draw_texture_cache_debug(framebuffer_size);
+        }
 
         #[cfg(feature = "debug_renderer")]
-        {
-            if let Some(framebuffer_size) = framebuffer_size {
-                self.draw_render_target_debug(framebuffer_size);
-                self.draw_texture_cache_debug(framebuffer_size);
-                self.draw_gpu_cache_debug(framebuffer_size);
-            }
-            self.draw_epoch_debug();
-        }
+        self.draw_epoch_debug();
 
         // Garbage collect any frame outputs that weren't used this frame.
         let device = &mut self.device;
         self.output_targets
             .retain(|_, target| if target.last_access != frame_id {
                 device.delete_fbo(target.fbo_id);
                 false
             } else {
@@ -3870,17 +3847,16 @@ impl Renderer {
         new_flags.toggle(toggle);
         self.set_debug_flags(new_flags);
     }
 
     pub fn save_cpu_profile(&self, filename: &str) {
         write_profile(filename);
     }
 
-    #[cfg(feature = "debug_renderer")]
     fn draw_render_target_debug(&mut self, framebuffer_size: DeviceUintSize) {
         if !self.debug_flags.contains(DebugFlags::RENDER_TARGET_DBG) {
             return;
         }
 
         let mut spacing = 16;
         let mut size = 512;
         let fb_width = framebuffer_size.width as i32;
@@ -3909,17 +3885,16 @@ impl Renderer {
 
                 let dest_rect = rect(x, y, size, size);
                 self.device.blit_render_target(src_rect, dest_rect);
                 target_index += 1;
             }
         }
     }
 
-    #[cfg(feature = "debug_renderer")]
     fn draw_texture_cache_debug(&mut self, framebuffer_size: DeviceUintSize) {
         if !self.debug_flags.contains(DebugFlags::TEXTURE_CACHE_DBG) {
             return;
         }
 
         let mut spacing = 16;
         let mut size = 512;
         let fb_width = framebuffer_size.width as i32;
@@ -3969,17 +3944,17 @@ impl Renderer {
     #[cfg(feature = "debug_renderer")]
     fn draw_epoch_debug(&mut self) {
         if !self.debug_flags.contains(DebugFlags::EPOCHS) {
             return;
         }
 
         let debug_renderer = match self.debug.get_mut(&mut self.device) {
             Some(render) => render,
-            None => return,
+            None => { return; }
         };
 
         let dy = debug_renderer.line_height();
         let x0: f32 = 30.0;
         let y0: f32 = 30.0;
         let mut y = y0;
         let mut text_width = 0.0;
         for (pipeline, epoch) in  &self.pipeline_info.epochs {
@@ -3998,54 +3973,16 @@ impl Renderer {
             y0 - margin,
             x0 + text_width + margin,
             y + margin,
             ColorU::new(25, 25, 25, 200),
             ColorU::new(51, 51, 51, 200),
         );
     }
 
-    #[cfg(feature = "debug_renderer")]
-    fn draw_gpu_cache_debug(&mut self, framebuffer_size: DeviceUintSize) {
-        if !self.debug_flags.contains(DebugFlags::GPU_CACHE_DBG) {
-            return;
-        }
-
-        let debug_renderer = match self.debug.get_mut(&mut self.device) {
-            Some(render) => render,
-            None => return,
-        };
-
-        let (x_off, y_off) = (30f32, 30f32);
-        //let x_end = framebuffer_size.width as f32 - x_off;
-        let y_end = framebuffer_size.height as f32 - y_off;
-        debug_renderer.add_quad(
-            x_off,
-            y_off,
-            x_off + MAX_VERTEX_TEXTURE_WIDTH as f32,
-            y_end,
-            ColorU::new(80, 80, 80, 80),
-            ColorU::new(80, 80, 80, 80),
-        );
-
-        for chunk in &self.gpu_cache_debug_chunks {
-            let color = match chunk.tag {
-                _ => ColorU::new(250, 0, 0, 200),
-            };
-            debug_renderer.add_quad(
-                x_off + chunk.address.u as f32,
-                y_off + chunk.address.v as f32,
-                x_off + chunk.address.u as f32 + chunk.size as f32,
-                y_off + chunk.address.v as f32 + 1.0,
-                color,
-                color,
-            );
-        }
-    }
-
     /// Pass-through to `Device::read_pixels_into`, used by Gecko's WR bindings.
     pub fn read_pixels_into(&mut self, rect: DeviceUintRect, format: ReadPixelsFormat, output: &mut [u8]) {
         self.device.read_pixels_into(rect, format, output);
     }
 
     pub fn read_pixels_rgba8(&mut self, rect: DeviceUintRect) -> Vec<u8> {
         let mut pixels = vec![0; (rect.size.width * rect.size.height * 4) as usize];
         self.device.read_pixels_into(rect, ReadPixelsFormat::Rgba8, &mut pixels);
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -14,18 +14,16 @@ use picture::PictureIdGenerator;
 use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::mem::replace;
 use time::precise_time_ns;
 use util::drain_filter;
-use std::thread;
-use std::time::Duration;
 
 /// Represents the work associated to a transaction before scene building.
 pub struct Transaction {
     pub document_id: DocumentId,
     pub display_list_updates: Vec<DisplayListUpdate>,
     pub removed_pipelines: Vec<PipelineId>,
     pub epoch_updates: Vec<(PipelineId, Epoch)>,
     pub request_scene_build: Option<SceneRequest>,
@@ -110,18 +108,16 @@ pub struct BuiltScene {
 
 // Message from render backend to scene builder.
 pub enum SceneBuilderRequest {
     Transaction(Box<Transaction>),
     DeleteDocument(DocumentId),
     WakeUp,
     Flush(MsgSender<()>),
     SetFrameBuilderConfig(FrameBuilderConfig),
-    SimulateLongSceneBuild(u32),
-    SimulateLongLowPrioritySceneBuild(u32),
     Stop,
     #[cfg(feature = "replay")]
     LoadScenes(Vec<LoadScene>),
 }
 
 // Message from scene builder to render backend.
 pub enum SceneBuilderResult {
     Transaction(Box<BuiltTransaction>, Option<Sender<SceneSwapResult>>),
@@ -140,17 +136,16 @@ pub enum SceneSwapResult {
 pub struct SceneBuilder {
     documents: FastHashMap<DocumentId, Scene>,
     rx: Receiver<SceneBuilderRequest>,
     tx: Sender<SceneBuilderResult>,
     api_tx: MsgSender<ApiMsg>,
     config: FrameBuilderConfig,
     hooks: Option<Box<SceneBuilderHooks + Send>>,
     picture_id_generator: PictureIdGenerator,
-    simulate_slow_ms: u32,
 }
 
 impl SceneBuilder {
     pub fn new(
         config: FrameBuilderConfig,
         api_tx: MsgSender<ApiMsg>,
         hooks: Option<Box<SceneBuilderHooks + Send>>,
     ) -> (Self, Sender<SceneBuilderRequest>, Receiver<SceneBuilderResult>) {
@@ -160,17 +155,16 @@ impl SceneBuilder {
             SceneBuilder {
                 documents: FastHashMap::default(),
                 rx: in_rx,
                 tx: out_tx,
                 api_tx,
                 config,
                 hooks,
                 picture_id_generator: PictureIdGenerator::new(),
-                simulate_slow_ms: 0,
             },
             in_tx,
             out_rx,
         )
     }
 
     /// The scene builder thread's event loop.
     pub fn run(&mut self) {
@@ -200,20 +194,16 @@ impl SceneBuilder {
                     self.load_scenes(msg);
                 }
                 Ok(SceneBuilderRequest::Stop) => {
                     self.tx.send(SceneBuilderResult::Stopped).unwrap();
                     // We don't need to send a WakeUp to api_tx because we only
                     // get the Stop when the RenderBackend loop is exiting.
                     break;
                 }
-                Ok(SceneBuilderRequest::SimulateLongSceneBuild(time_ms)) => {
-                    self.simulate_slow_ms = time_ms
-                }
-                Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(_)) => {}
                 Err(_) => {
                     break;
                 }
             }
 
             if let Some(ref hooks) = self.hooks {
                 hooks.poke();
             }
@@ -340,20 +330,16 @@ impl SceneBuilder {
         rasterized_blobs.append(&mut txn.rasterized_blobs);
 
         drain_filter(
             &mut txn.notifications,
             |n| { n.when() == Checkpoint::SceneBuilt },
             |n| { n.notify(); },
         );
 
-        if self.simulate_slow_ms > 0 {
-            thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
-        }
-
         Box::new(BuiltTransaction {
             document_id: txn.document_id,
             build_frame: txn.build_frame || built_scene.is_some(),
             render_frame: txn.render_frame,
             built_scene,
             rasterized_blobs,
             resource_updates: replace(&mut txn.resource_updates, Vec::new()),
             blob_rasterizer: replace(&mut txn.blob_rasterizer, None),
@@ -417,17 +403,16 @@ impl SceneBuilder {
 /// A scene builder thread which executes expensive operations such as blob rasterization
 /// with a lower priority than the normal scene builder thread.
 ///
 /// After rasterizing blobs, the secene building request is forwarded to the normal scene
 /// builder where the FrameBuilder is generated.
 pub struct LowPrioritySceneBuilder {
     pub rx: Receiver<SceneBuilderRequest>,
     pub tx: Sender<SceneBuilderRequest>,
-    pub simulate_slow_ms: u32,
 }
 
 impl LowPrioritySceneBuilder {
     pub fn run(&mut self) {
         loop {
             match self.rx.recv() {
                 Ok(SceneBuilderRequest::Transaction(txn)) => {
                     let txn = self.process_transaction(txn);
@@ -435,19 +420,16 @@ impl LowPrioritySceneBuilder {
                 }
                 Ok(SceneBuilderRequest::DeleteDocument(document_id)) => {
                     self.tx.send(SceneBuilderRequest::DeleteDocument(document_id)).unwrap();
                 }
                 Ok(SceneBuilderRequest::Stop) => {
                     self.tx.send(SceneBuilderRequest::Stop).unwrap();
                     break;
                 }
-                Ok(SceneBuilderRequest::SimulateLongLowPrioritySceneBuild(time_ms)) => {
-                    self.simulate_slow_ms = time_ms;
-                }
                 Ok(other) => {
                     self.tx.send(other).unwrap();
                 }
                 Err(_) => {
                     break;
                 }
             }
         }
@@ -456,15 +438,11 @@ impl LowPrioritySceneBuilder {
     fn process_transaction(&mut self, mut txn: Box<Transaction>) -> Box<Transaction> {
         let blob_requests = replace(&mut txn.blob_requests, Vec::new());
         let mut more_rasterized_blobs = txn.blob_rasterizer.as_mut().map_or(
             Vec::new(),
             |rasterizer| rasterizer.rasterize(&blob_requests),
         );
         txn.rasterized_blobs.append(&mut more_rasterized_blobs);
 
-        if self.simulate_slow_ms > 0 {
-            thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
-        }
-
         txn
     }
 }
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -603,18 +603,16 @@ pub struct CapturedDocument {
 #[derive(Clone, Deserialize, Serialize)]
 pub enum DebugCommand {
     /// Display the frame profiler on screen.
     EnableProfiler(bool),
     /// Display all texture cache pages on screen.
     EnableTextureCacheDebug(bool),
     /// Display intermediate render targets on screen.
     EnableRenderTargetDebug(bool),
-    /// Display the contents of GPU cache.
-    EnableGpuCacheDebug(bool),
     /// Display GPU timing results.
     EnableGpuTimeQueries(bool),
     /// Display GPU overdraw results
     EnableGpuSampleQueries(bool),
     /// Configure if dual-source blending is used, if available.
     EnableDualSourceBlending(bool),
     /// Show an indicator that moves every time a frame is rendered.
     EnableNewFrameIndicator(bool),
@@ -635,22 +633,16 @@ pub enum DebugCommand {
     /// Save a capture of all the documents state.
     SaveCapture(PathBuf, CaptureBits),
     /// Load a capture of all the documents state.
     LoadCapture(PathBuf, MsgSender<CapturedDocument>),
     /// Clear cached resources, forcing them to be re-uploaded from templates.
     ClearCaches(ClearCache),
     /// Invalidate GPU cache, forcing the update from the CPU mirror.
     InvalidateGpuCache,
-    /// Causes the scene builder to pause for a given amount of miliseconds each time it
-    /// processes a transaction.
-    SimulateLongSceneBuild(u32),
-    /// Causes the low priority scene builder to pause for a given amount of miliseconds
-    /// each time it processes a transaction.
-    SimulateLongLowPrioritySceneBuild(u32),
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     /// Add/remove/update images and fonts.
     UpdateResources(Vec<ResourceUpdate>),
     /// Gets the glyph dimensions
     GetGlyphDimensions(
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #[cfg(any(feature = "serialize", feature = "deserialize"))]
 use GlyphInstance;
 use euclid::{SideOffsets2D, TypedRect};
 use std::ops::Not;
 use {ColorF, FontInstanceKey, GlyphOptions, ImageKey, LayoutPixel, LayoutPoint};
 use {LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D, PipelineId, PropertyBinding};
-use LayoutSideOffsets;
 
 
 // NOTE: some of these structs have an "IMPLICIT" comment.
 // This indicates that the BuiltDisplayList will have serialized
 // a list of values nearby that this item consumes. The traversal
 // iterator should handle finding these.
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
@@ -273,17 +272,17 @@ impl NormalBorder {
         b.top.color = color;
         b.bottom.color = color;
         b
     }
 
     /// Normalizes a border so that we don't render disallowed stuff, like inset
     /// borders that are less than two pixels wide.
     #[inline]
-    pub fn normalize(&mut self, widths: &LayoutSideOffsets) {
+    pub fn normalize(&mut self, widths: &BorderWidths) {
         #[inline]
         fn renders_small_border_solid(style: BorderStyle) -> bool {
             match style {
                 BorderStyle::Groove |
                 BorderStyle::Ridge |
                 BorderStyle::Inset |
                 BorderStyle::Outset => true,
                 _ => false,
@@ -360,17 +359,17 @@ pub struct NinePatchBorder {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum BorderDetails {
     Normal(NormalBorder),
     NinePatch(NinePatchBorder),
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BorderDisplayItem {
-    pub widths: LayoutSideOffsets,
+    pub widths: BorderWidths,
     pub details: BorderDetails,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum BorderRadiusKind {
     Uniform,
     NonUniform,
@@ -382,16 +381,25 @@ pub struct BorderRadius {
     pub top_left: LayoutSize,
     pub top_right: LayoutSize,
     pub bottom_left: LayoutSize,
     pub bottom_right: LayoutSize,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct BorderWidths {
+    pub left: f32,
+    pub top: f32,
+    pub right: f32,
+    pub bottom: f32,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct BorderSide {
     pub color: ColorF,
     pub style: BorderStyle,
 }
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Hash, Eq)]
 pub enum BorderStyle {
--- a/gfx/webrender_api/src/display_list.rs
+++ b/gfx/webrender_api/src/display_list.rs
@@ -8,28 +8,28 @@ use euclid::SideOffsets2D;
 use serde::de::Deserializer;
 #[cfg(feature = "serialize")]
 use serde::ser::{Serializer, SerializeSeq};
 use serde::{Deserialize, Serialize};
 use std::io::{Read, Write};
 use std::marker::PhantomData;
 use std::{io, mem, ptr, slice};
 use time::precise_time_ns;
-use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode};
+use {AlphaType, BorderDetails, BorderDisplayItem, BorderRadius, BorderWidths, BoxShadowClipMode};
 use {BoxShadowDisplayItem, ClipAndScrollInfo, ClipChainId, ClipChainItem, ClipDisplayItem, ClipId};
 use {ColorF, ComplexClipRegion, DisplayItem, ExtendMode, ExternalScrollId, FilterOp};
 use {FontInstanceKey, GlyphInstance, GlyphOptions, GlyphRasterSpace, Gradient, GradientBuilder};
 use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
-use {ImageRendering, LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSideOffsets, LayoutSize};
-use {LayoutTransform, LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, MixBlendMode};
-use {PipelineId, PropertyBinding, PushReferenceFrameDisplayListItem};
-use {PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
-use {RectangleDisplayItem, ReferenceFrame, ScrollFrameDisplayItem, ScrollSensitivity, Shadow};
-use {SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, StickyOffsetBounds};
-use {TextDisplayItem, TransformStyle, YuvColorSpace, YuvData, YuvImageDisplayItem};
+use {ImageRendering, LayoutPoint, LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform};
+use {LayoutVector2D, LineDisplayItem, LineOrientation, LineStyle, MixBlendMode, PipelineId};
+use {PropertyBinding, PushReferenceFrameDisplayListItem, PushStackingContextDisplayItem};
+use {RadialGradient, RadialGradientDisplayItem, RectangleDisplayItem, ReferenceFrame};
+use {ScrollFrameDisplayItem, ScrollSensitivity, Shadow, SpecificDisplayItem, StackingContext};
+use {StickyFrameDisplayItem, StickyOffsetBounds, TextDisplayItem, TransformStyle, YuvColorSpace};
+use {YuvData, YuvImageDisplayItem};
 
 // We don't want to push a long text-run. If a text-run is too long, split it into several parts.
 // This needs to be set to (renderer::MAX_VERTEX_TEXTURE_WIDTH - VECS_PER_TEXT_RUN) * 2
 pub const MAX_TEXT_RUN_LENGTH: usize = 2040;
 
 // We start at 2, because the root reference is always 0 and the root scroll node is always 1.
 // TODO(mrobinson): It would be a good idea to eliminate the root scroll frame which is only
 // used by Servo.
@@ -1149,17 +1149,17 @@ impl DisplayListBuilder {
         let gradient = builder.radial_gradient(center, radius, extend_mode);
         self.push_stops(builder.stops());
         gradient
     }
 
     pub fn push_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
-        widths: LayoutSideOffsets,
+        widths: BorderWidths,
         details: BorderDetails,
     ) {
         let item = SpecificDisplayItem::Border(BorderDisplayItem { details, widths });
 
         self.push_item(item, info);
     }
 
     pub fn push_box_shadow(
--- a/gfx/webrender_api/src/units.rs
+++ b/gfx/webrender_api/src/units.rs
@@ -72,17 +72,16 @@ pub type RasterVector3D = TypedVector3D<
 pub struct LayoutPixel;
 
 pub type LayoutRect = TypedRect<f32, LayoutPixel>;
 pub type LayoutPoint = TypedPoint2D<f32, LayoutPixel>;
 pub type LayoutPoint3D = TypedPoint3D<f32, LayoutPixel>;
 pub type LayoutVector2D = TypedVector2D<f32, LayoutPixel>;
 pub type LayoutVector3D = TypedVector3D<f32, LayoutPixel>;
 pub type LayoutSize = TypedSize2D<f32, LayoutPixel>;
-pub type LayoutSideOffsets = TypedSideOffsets2D<f32, LayoutPixel>;
 
 /// Geometry in the document's coordinate space (logical pixels).
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct WorldPixel;
 
 pub type WorldRect = TypedRect<f32, WorldPixel>;
 pub type WorldPoint = TypedPoint2D<f32, WorldPixel>;
 pub type WorldSize = TypedSize2D<f32, WorldPixel>;
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -1176,33 +1176,33 @@ DisplayListBuilder::PushIFrame(const wr:
   wr_dp_push_iframe(mWrState, aBounds, MergeClipLeaf(aBounds),
                     aIsBackfaceVisible, aPipeline, aIgnoreMissingPipeline);
 }
 
 void
 DisplayListBuilder::PushBorder(const wr::LayoutRect& aBounds,
                                const wr::LayoutRect& aClip,
                                bool aIsBackfaceVisible,
-                               const wr::LayoutSideOffsets& aWidths,
+                               const wr::BorderWidths& aWidths,
                                const Range<const wr::BorderSide>& aSides,
                                const wr::BorderRadius& aRadius)
 {
   MOZ_ASSERT(aSides.length() == 4);
   if (aSides.length() != 4) {
     return;
   }
   wr_dp_push_border(mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
                     aWidths, aSides[0], aSides[1], aSides[2], aSides[3], aRadius);
 }
 
 void
 DisplayListBuilder::PushBorderImage(const wr::LayoutRect& aBounds,
                                     const wr::LayoutRect& aClip,
                                     bool aIsBackfaceVisible,
-                                    const wr::LayoutSideOffsets& aWidths,
+                                    const wr::BorderWidths& aWidths,
                                     wr::ImageKey aImage,
                                     const uint32_t aWidth,
                                     const uint32_t aHeight,
                                     const wr::SideOffsets2D<uint32_t>& aSlice,
                                     const wr::SideOffsets2D<float>& aOutset,
                                     const wr::RepeatMode& aRepeatHorizontal,
                                     const wr::RepeatMode& aRepeatVertical)
 {
@@ -1210,17 +1210,17 @@ DisplayListBuilder::PushBorderImage(cons
                           aIsBackfaceVisible, aWidths, aImage, aWidth, aHeight,
                           aSlice, aOutset, aRepeatHorizontal, aRepeatVertical);
 }
 
 void
 DisplayListBuilder::PushBorderGradient(const wr::LayoutRect& aBounds,
                                        const wr::LayoutRect& aClip,
                                        bool aIsBackfaceVisible,
-                                       const wr::LayoutSideOffsets& aWidths,
+                                       const wr::BorderWidths& aWidths,
                                        const uint32_t aWidth,
                                        const uint32_t aHeight,
                                        const wr::SideOffsets2D<uint32_t>& aSlice,
                                        const wr::LayoutPoint& aStartPoint,
                                        const wr::LayoutPoint& aEndPoint,
                                        const nsTArray<wr::GradientStop>& aStops,
                                        wr::ExtendMode aExtendMode,
                                        const wr::SideOffsets2D<float>& aOutset)
@@ -1231,17 +1231,17 @@ DisplayListBuilder::PushBorderGradient(c
                              aStops.Elements(), aStops.Length(),
                              aExtendMode, aOutset);
 }
 
 void
 DisplayListBuilder::PushBorderRadialGradient(const wr::LayoutRect& aBounds,
                                              const wr::LayoutRect& aClip,
                                              bool aIsBackfaceVisible,
-                                             const wr::LayoutSideOffsets& aWidths,
+                                             const wr::BorderWidths& aWidths,
                                              const wr::LayoutPoint& aCenter,
                                              const wr::LayoutSize& aRadius,
                                              const nsTArray<wr::GradientStop>& aStops,
                                              wr::ExtendMode aExtendMode,
                                              const wr::SideOffsets2D<float>& aOutset)
 {
   wr_dp_push_border_radial_gradient(
     mWrState, aBounds, MergeClipLeaf(aClip), aIsBackfaceVisible,
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -417,49 +417,49 @@ public:
                   wr::PipelineId aPipeline,
                   bool aIgnoreMissingPipeline);
 
   // XXX WrBorderSides are passed with Range.
   // It is just to bypass compiler bug. See Bug 1357734.
   void PushBorder(const wr::LayoutRect& aBounds,
                   const wr::LayoutRect& aClip,
                   bool aIsBackfaceVisible,
-                  const wr::LayoutSideOffsets& aWidths,
+                  const wr::BorderWidths& aWidths,
                   const Range<const wr::BorderSide>& aSides,
                   const wr::BorderRadius& aRadius);
 
   void PushBorderImage(const wr::LayoutRect& aBounds,
                        const wr::LayoutRect& aClip,
                        bool aIsBackfaceVisible,
-                       const wr::LayoutSideOffsets& aWidths,
+                       const wr::BorderWidths& aWidths,
                        wr::ImageKey aImage,
                        const uint32_t aWidth,
                        const uint32_t aHeight,
                        const wr::SideOffsets2D<uint32_t>& aSlice,
                        const wr::SideOffsets2D<float>& aOutset,
                        const wr::RepeatMode& aRepeatHorizontal,
                        const wr::RepeatMode& aRepeatVertical);
 
   void PushBorderGradient(const wr::LayoutRect& aBounds,
                           const wr::LayoutRect& aClip,
                           bool aIsBackfaceVisible,
-                          const wr::LayoutSideOffsets& aWidths,
+                          const wr::BorderWidths& aWidths,
                           const uint32_t aWidth,
                           const uint32_t aHeight,
                           const wr::SideOffsets2D<uint32_t>& aSlice,
                           const wr::LayoutPoint& aStartPoint,
                           const wr::LayoutPoint& aEndPoint,
                           const nsTArray<wr::GradientStop>& aStops,
                           wr::ExtendMode aExtendMode,
                           const wr::SideOffsets2D<float>& aOutset);
 
   void PushBorderRadialGradient(const wr::LayoutRect& aBounds,
                                 const wr::LayoutRect& aClip,
                                 bool aIsBackfaceVisible,
-                                const wr::LayoutSideOffsets& aWidths,
+                                const wr::BorderWidths& aWidths,
                                 const wr::LayoutPoint& aCenter,
                                 const wr::LayoutSize& aRadius,
                                 const nsTArray<wr::GradientStop>& aStops,
                                 wr::ExtendMode aExtendMode,
                                 const wr::SideOffsets2D<float>& aOutset);
 
   void PushText(const wr::LayoutRect& aBounds,
                 const wr::LayoutRect& aClip,
--- a/gfx/webrender_bindings/WebRenderTypes.h
+++ b/gfx/webrender_bindings/WebRenderTypes.h
@@ -481,19 +481,19 @@ static inline wr::BorderRadius ToBorderR
   wr::BorderRadius br;
   br.top_left = ToLayoutSize(topLeft);
   br.top_right = ToLayoutSize(topRight);
   br.bottom_left = ToLayoutSize(bottomLeft);
   br.bottom_right = ToLayoutSize(bottomRight);
   return br;
 }
 
-static inline wr::LayoutSideOffsets ToBorderWidths(float top, float right, float bottom, float left)
+static inline wr::BorderWidths ToBorderWidths(float top, float right, float bottom, float left)
 {
-  wr::LayoutSideOffsets bw;
+  wr::BorderWidths bw;
   bw.top = top;
   bw.right = right;
   bw.bottom = bottom;
   bw.left = left;
   return bw;
 }
 
 static inline wr::SideOffsets2D<uint32_t> ToSideOffsets2D_u32(uint32_t top, uint32_t right, uint32_t bottom, uint32_t left)
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-6fa046c936b9d720726d27c3b0514ee66a305b69
+da76f6aad61c1ba769566861ec41b42d511fa456
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -2153,17 +2153,17 @@ pub extern "C" fn wr_dp_push_line(state:
 
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border(state: &mut WrState,
                                     rect: LayoutRect,
                                     clip: LayoutRect,
                                     is_backface_visible: bool,
-                                    widths: LayoutSideOffsets,
+                                    widths: BorderWidths,
                                     top: BorderSide,
                                     right: BorderSide,
                                     bottom: BorderSide,
                                     left: BorderSide,
                                     radius: BorderRadius) {
     debug_assert!(unsafe { is_in_main_thread() });
 
     let border_details = BorderDetails::Normal(NormalBorder {
@@ -2183,17 +2183,17 @@ pub extern "C" fn wr_dp_push_border(stat
                       border_details);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_image(state: &mut WrState,
                                           rect: LayoutRect,
                                           clip: LayoutRect,
                                           is_backface_visible: bool,
-                                          widths: LayoutSideOffsets,
+                                          widths: BorderWidths,
                                           image: WrImageKey,
                                           width: u32,
                                           height: u32,
                                           slice: SideOffsets2D<u32>,
                                           outset: SideOffsets2D<f32>,
                                           repeat_horizontal: RepeatMode,
                                           repeat_vertical: RepeatMode) {
     debug_assert!(unsafe { is_in_main_thread() });
@@ -2214,17 +2214,17 @@ pub extern "C" fn wr_dp_push_border_imag
          .push_border(&prim_info, widths.into(), border_details);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_gradient(state: &mut WrState,
                                              rect: LayoutRect,
                                              clip: LayoutRect,
                                              is_backface_visible: bool,
-                                             widths: LayoutSideOffsets,
+                                             widths: BorderWidths,
                                              width: u32,
                                              height: u32,
                                              slice: SideOffsets2D<u32>,
                                              start_point: LayoutPoint,
                                              end_point: LayoutPoint,
                                              stops: *const GradientStop,
                                              stops_count: usize,
                                              extend_mode: ExtendMode,
@@ -2260,17 +2260,17 @@ pub extern "C" fn wr_dp_push_border_grad
          .push_border(&prim_info, widths.into(), border_details);
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_border_radial_gradient(state: &mut WrState,
                                                     rect: LayoutRect,
                                                     clip: LayoutRect,
                                                     is_backface_visible: bool,
-                                                    widths: LayoutSideOffsets,
+                                                    widths: BorderWidths,
                                                     center: LayoutPoint,
                                                     radius: LayoutSize,
                                                     stops: *const GradientStop,
                                                     stops_count: usize,
                                                     extend_mode: ExtendMode,
                                                     outset: SideOffsets2D<f32>) {
     debug_assert!(unsafe { is_in_main_thread() });
 
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -654,33 +654,30 @@ struct TypedVector2D {
   bool operator==(const TypedVector2D& aOther) const {
     return x == aOther.x &&
            y == aOther.y;
   }
 };
 
 using LayoutVector2D = TypedVector2D<float, LayoutPixel>;
 
-template<typename T, typename U>
-struct TypedSideOffsets2D {
-  T top;
-  T right;
-  T bottom;
-  T left;
+struct BorderWidths {
+  float left;
+  float top;
+  float right;
+  float bottom;
 
-  bool operator==(const TypedSideOffsets2D& aOther) const {
-    return top == aOther.top &&
+  bool operator==(const BorderWidths& aOther) const {
+    return left == aOther.left &&
+           top == aOther.top &&
            right == aOther.right &&
-           bottom == aOther.bottom &&
-           left == aOther.left;
+           bottom == aOther.bottom;
   }
 };
 
-using LayoutSideOffsets = TypedSideOffsets2D<float, LayoutPixel>;
-
 // Represents RGBA screen colors with floating point numbers.
 //
 // All components must be between 0.0 and 1.0.
 // An alpha value of 1.0 is opaque while 0.0 is fully transparent.
 struct ColorF {
   float r;
   float g;
   float b;
@@ -699,16 +696,31 @@ struct BorderSide {
   BorderStyle style;
 
   bool operator==(const BorderSide& aOther) const {
     return color == aOther.color &&
            style == aOther.style;
   }
 };
 
+template<typename T, typename U>
+struct TypedSideOffsets2D {
+  T top;
+  T right;
+  T bottom;
+  T left;
+
+  bool operator==(const TypedSideOffsets2D& aOther) const {
+    return top == aOther.top &&
+           right == aOther.right &&
+           bottom == aOther.bottom &&
+           left == aOther.left;
+  }
+};
+
 // The default side offset type with no unit.
 template<typename T>
 using SideOffsets2D = TypedSideOffsets2D<T, UnknownUnit>;
 
 using LayoutPoint = TypedPoint2D<float, LayoutPixel>;
 
 struct GradientStop {
   float offset;
@@ -1224,62 +1236,62 @@ void wr_dp_pop_stacking_context(WrState 
                                 bool aIsReferenceFrame)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border(WrState *aState,
                        LayoutRect aRect,
                        LayoutRect aClip,
                        bool aIsBackfaceVisible,
-                       LayoutSideOffsets aWidths,
+                       BorderWidths aWidths,
                        BorderSide aTop,
                        BorderSide aRight,
                        BorderSide aBottom,
                        BorderSide aLeft,
                        BorderRadius aRadius)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_gradient(WrState *aState,
                                 LayoutRect aRect,
                                 LayoutRect aClip,
                                 bool aIsBackfaceVisible,
-                                LayoutSideOffsets aWidths,
+                                BorderWidths aWidths,
                                 uint32_t aWidth,
                                 uint32_t aHeight,
                                 SideOffsets2D<uint32_t> aSlice,
                                 LayoutPoint aStartPoint,
                                 LayoutPoint aEndPoint,
                                 const GradientStop *aStops,
                                 uintptr_t aStopsCount,
                                 ExtendMode aExtendMode,
                                 SideOffsets2D<float> aOutset)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_image(WrState *aState,
                              LayoutRect aRect,
                              LayoutRect aClip,
                              bool aIsBackfaceVisible,
-                             LayoutSideOffsets aWidths,
+                             BorderWidths aWidths,
                              WrImageKey aImage,
                              uint32_t aWidth,
                              uint32_t aHeight,
                              SideOffsets2D<uint32_t> aSlice,
                              SideOffsets2D<float> aOutset,
                              RepeatMode aRepeatHorizontal,
                              RepeatMode aRepeatVertical)
 WR_FUNC;
 
 WR_INLINE
 void wr_dp_push_border_radial_gradient(WrState *aState,
                                        LayoutRect aRect,
                                        LayoutRect aClip,
                                        bool aIsBackfaceVisible,
-                                       LayoutSideOffsets aWidths,
+                                       BorderWidths aWidths,
                                        LayoutPoint aCenter,
                                        LayoutSize aRadius,
                                        const GradientStop *aStops,
                                        uintptr_t aStopsCount,
                                        ExtendMode aExtendMode,
                                        SideOffsets2D<float> aOutset)
 WR_FUNC;
 
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -617,28 +617,16 @@ fn render<'a>(
                             DebugFlags::GPU_TIME_QUERIES | DebugFlags::GPU_SAMPLE_QUERIES
                         );
                         do_render = true;
                     }
                     VirtualKeyCode::V => {
                         wrench.renderer.toggle_debug_flags(DebugFlags::SHOW_OVERDRAW);
                         do_render = true;
                     }
-                    VirtualKeyCode::G => {
-                        // go through the API so that we reach the render backend
-                        wrench.api.send_debug_cmd(DebugCommand::EnableGpuCacheDebug(
-                            !wrench.renderer.get_debug_flags().contains(webrender::DebugFlags::GPU_CACHE_DBG)
-                        ));
-                        // force scene rebuild to see the full set of used GPU cache entries
-                        let mut txn = Transaction::new();
-                        txn.set_root_pipeline(wrench.root_pipeline_id);
-                        wrench.api.send_transaction(wrench.document_id, txn);
-
-                        do_frame = true;
-                    }
                     VirtualKeyCode::R => {
                         wrench.set_page_zoom(ZoomFactor::new(1.0));
                         do_frame = true;
                     }
                     VirtualKeyCode::M => {
                         wrench.api.notify_memory_pressure();
                         do_render = true;
                     }
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -548,17 +548,16 @@ impl Wrench {
             "Esc - Quit",
             "H - Toggle help",
             "R - Toggle recreating display items each frame",
             "P - Toggle profiler",
             "O - Toggle showing intermediate targets",
             "I - Toggle showing texture caches",
             "B - Toggle showing alpha primitive rects",
             "V - Toggle showing overdraw",
-            "G - Toggle showing gpu cache updates",
             "S - Toggle compact profiler",
             "Q - Toggle GPU queries for time and samples",
             "M - Trigger memory pressure event",
             "T - Save CPU profile to a file",
             "C - Save a capture to captures/wrench/",
             "X - Do a hit test at the current cursor position",
         ];
 
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -814,17 +814,22 @@ impl YamlFrameReader {
         };
         info.rect = item[bounds_key]
             .as_rect()
             .expect("borders must have bounds");
         let widths = item["width"]
             .as_vec_f32()
             .expect("borders must have width(s)");
         let widths = broadcast(&widths, 4);
-        let widths = LayoutSideOffsets::new(widths[0], widths[3], widths[2], widths[1]);
+        let widths = BorderWidths {
+            top: widths[0],
+            left: widths[1],
+            bottom: widths[2],
+            right: widths[3],
+        };
         let border_details = if let Some(border_type) = item["border-type"].as_str() {
             match border_type {
                 "normal" => {
                     let colors = item["color"]
                         .as_vec_colorf()
                         .expect("borders must have color(s)");
                     let styles = item["style"]
                         .as_vec_string()
--- a/layout/generic/TextDrawTarget.h
+++ b/layout/generic/TextDrawTarget.h
@@ -443,17 +443,17 @@ public:
 
   void StrokeRect(const Rect &aRect,
                   const Pattern &aPattern,
                   const StrokeOptions &aStrokeOptions,
                   const DrawOptions &aOptions) override {
     MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR &&
                        aStrokeOptions.mDashLength == 0);
 
-    wr::LayoutSideOffsets widths = {
+    wr::BorderWidths widths = {
         aStrokeOptions.mLineWidth,
         aStrokeOptions.mLineWidth,
         aStrokeOptions.mLineWidth,
         aStrokeOptions.mLineWidth
     };
     wr::ColorF color = wr::ToColorF(static_cast<const ColorPattern&>(aPattern).mColor);
     wr::BorderSide sides[4] = {
         { color, wr::BorderStyle::Solid },
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -7549,17 +7549,17 @@ BCBlockDirSeg::CreateWebRenderCommands(B
     wrSide[i] = wr::ToBorderSide(ToDeviceColor(param->mBorderColor), NS_STYLE_BORDER_STYLE_NONE);
   }
   wrSide[eSideLeft] = wr::ToBorderSide(ToDeviceColor(param->mBorderColor), param->mBorderStyle);
 
   wr::BorderRadius borderRadii = wr::EmptyBorderRadius();
 
   // All border style is set to none except left side. So setting the widths of
   // each side to width of rect is fine.
-  wr::LayoutSideOffsets borderWidths = wr::ToBorderWidths(roundedRect.size.width,
+  wr::BorderWidths borderWidths = wr::ToBorderWidths(roundedRect.size.width,
                                                      roundedRect.size.width,
                                                      roundedRect.size.width,
                                                      roundedRect.size.width);
   Range<const wr::BorderSide> wrsides(wrSide, 4);
   aBuilder.PushBorder(roundedRect,
                       roundedRect,
                       param->mBackfaceIsVisible,
                       borderWidths,
@@ -7836,17 +7836,17 @@ BCInlineDirSeg::CreateWebRenderCommands(
     wrSide[i] = wr::ToBorderSide(ToDeviceColor(param->mBorderColor), NS_STYLE_BORDER_STYLE_NONE);
   }
   wrSide[eSideTop] = wr::ToBorderSide(ToDeviceColor(param->mBorderColor), param->mBorderStyle);
 
   wr::BorderRadius borderRadii = wr::EmptyBorderRadius();
 
   // All border style is set to none except top side. So setting the widths of
   // each side to height of rect is fine.
-  wr::LayoutSideOffsets borderWidths = wr::ToBorderWidths(roundedRect.size.height,
+  wr::BorderWidths borderWidths = wr::ToBorderWidths(roundedRect.size.height,
                                                      roundedRect.size.height,
                                                      roundedRect.size.height,
                                                      roundedRect.size.height);
   Range<const wr::BorderSide> wrsides(wrSide, 4);
   aBuilder.PushBorder(roundedRect,
                       roundedRect,
                       param->mBackfaceIsVisible,
                       borderWidths,
@@ -8081,17 +8081,17 @@ nsTableFrame::CreateWebRenderCommandsFor
 {
   BCPaintBorderAction action(aBuilder, aSc, aOffsetToReferenceFrame);
   // We always draw whole table border for webrender. Passing the visible rect
   // dirty rect.
   IterateBCBorders(action, aVisibleRect - aOffsetToReferenceFrame);
 
   LayoutDeviceRect allBorderRect;
   wr::BorderSide wrSide[4];
-  wr::LayoutSideOffsets wrWidths;
+  wr::BorderWidths wrWidths;
   wr::BorderRadius borderRadii = wr::EmptyBorderRadius();
   bool backfaceIsVisible = false;
   NS_FOR_CSS_SIDES(side) {
     auto param = action.mCreateWebRenderCommandsData.mBevelBorders[side];
     LayoutDeviceRect borderRect;
     nscolor borderColor = NS_RGBA(0, 0, 0, 255);
     uint8_t borderStyle = NS_STYLE_BORDER_STYLE_NONE;
     if (param.isSome()) {
--- a/widget/cocoa/nsNativeThemeCocoa.mm
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -3937,17 +3937,17 @@ nsNativeThemeCocoa::CreateWebRenderComma
         wr::ToBorderSide(kMultilineTextFieldTopBorderColor, NS_STYLE_BORDER_STYLE_SOLID),
         wr::ToBorderSide(kMultilineTextFieldSidesAndBottomBorderColor, NS_STYLE_BORDER_STYLE_SOLID),
         wr::ToBorderSide(kMultilineTextFieldSidesAndBottomBorderColor, NS_STYLE_BORDER_STYLE_SOLID),
         wr::ToBorderSide(kMultilineTextFieldSidesAndBottomBorderColor, NS_STYLE_BORDER_STYLE_SOLID),
       };
 
       wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
       float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
-      wr::LayoutSideOffsets borderWidths =
+      wr::BorderWidths borderWidths =
         wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
 
       mozilla::Range<const wr::BorderSide> wrsides(side, 4);
       aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
 
       return true;
     }
 
@@ -3960,17 +3960,17 @@ nsNativeThemeCocoa::CreateWebRenderComma
         wr::ToBorderSide(kListboxTopBorderColor, NS_STYLE_BORDER_STYLE_SOLID),
         wr::ToBorderSide(kListBoxSidesAndBottomBorderColor, NS_STYLE_BORDER_STYLE_SOLID),
         wr::ToBorderSide(kListBoxSidesAndBottomBorderColor, NS_STYLE_BORDER_STYLE_SOLID),
         wr::ToBorderSide(kListBoxSidesAndBottomBorderColor, NS_STYLE_BORDER_STYLE_SOLID),
       };
 
       wr::BorderRadius borderRadius = wr::EmptyBorderRadius();
       float borderWidth = presContext->CSSPixelsToDevPixels(1.0f);
-      wr::LayoutSideOffsets borderWidths =
+      wr::BorderWidths borderWidths =
         wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
 
       mozilla::Range<const wr::BorderSide> wrsides(side, 4);
       aBuilder.PushBorder(bounds, bounds, true, borderWidths, wrsides, borderRadius);
       return true;
     }
 
     case StyleAppearance::MozMacSourceList: