Bug 1466549 - Update webrender to aff9f409f3d6a3518c38c1f7755657f564c1083a. r=Gankro
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 06 Jun 2018 07:34:22 -0400
changeset 421630 8fd78043032e9b40703eeaf03fe58263e8fcba28
parent 421629 70081a033f198ea3eceae6f85bee8c45abd8262f
child 421631 ebecd80e1816f33fab9cd4e62f3daecafe3d6ec8
push id104077
push userncsoregi@mozilla.com
push dateWed, 06 Jun 2018 22:02:45 +0000
treeherdermozilla-inbound@5e2b8c50e708 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGankro
bugs1466549
milestone62.0a1
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
Bug 1466549 - Update webrender to aff9f409f3d6a3518c38c1f7755657f564c1083a. r=Gankro MozReview-Commit-ID: 2Vauiblv7eW
gfx/webrender/doc/CLIPPING.md
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/cs_border_segment.glsl
gfx/webrender/res/cs_clip_border.glsl
gfx/webrender/res/ps_border_corner.glsl
gfx/webrender/res/ps_border_edge.glsl
gfx/webrender/res/shared_border.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/ellipse.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/gpu_cache.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/shade.rs
gfx/webrender/src/tiling.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/src/api.rs
gfx/webrender_bindings/revision.txt
gfx/wrench/src/rawtest.rs
--- a/gfx/webrender/doc/CLIPPING.md
+++ b/gfx/webrender/doc/CLIPPING.md
@@ -1,97 +1,53 @@
-# Clipping in WebRender
-
-The WebRender display list allows defining clips in two different ways. The
-first is specified directly on each display item and cannot be reused between
-items. The second is specified using the `SpecificDisplayItem::Clip` display item
-and can be reused between items as well as used to define scrollable regions.
-
-## Clips
-
-Clips are defined using the ClipRegion in both cases.
+# Clipping and Positioning in WebRender
 
-```rust
-pub struct ClipRegion {
-    pub main: LayoutRect,
-    pub complex: ItemRange<ComplexClip>,
-    pub image_mask: Option<ImageMask>,
-}
-```
-
-`main` defines a rectangular clip, while the other members make that rectangle
-smaller. `complex`, if it is not empty, defines the boundaries of a rounded
-rectangle. While `image_mask` defines the positioning, repetition, and data of
-a masking image.
+Each non-structural WebRender display list item has
+ * A `ClipId` of a positioning node
+ * A `ClipId` of a clip chain
+ * An item-specific rectangular clip rectangle
 
-## Item Clips
+The positioning node determines how that item is positioned. It's assumed that the
+positioning node and the item are children of the same reference frame. The clip
+chain determines how that item is clipped. This should be fully independent of
+how the node is positioned and items can be clipped by any `ClipChain` regardless
+of the reference frame of their member clips. Finally, the item-specific
+clipping rectangle is applied directly to the item and should never result in the
+creation of a clip mask itself.
 
-Item clips are simply a `ClipRegion` structure defined directly on the
-`DisplayItem`. The important thing to note about these clips is that all the
-coordinate in `ClipRegion` **are in the same coordinate space as the item
-itself**. This different than for clips defined by `SpecificDisplayItem::Clip`.
-
-## Clip Display Items
+# The `ClipScrollTree`
 
-Clip display items allow items to share clips in order to increase performance
-(shared clips are only rasterized once) and to allow for scrolling regions.
-Display items can be assigned a clip display item using the `clip_id`
-field. An item can be assigned any clip that is defined by its parent stacking
-context or any of the ancestors. The behavior of assigning an id outside of
-this hierarchy is undefined, because that situation does not occur in CSS
+The ClipScrollTree contains two sorts of elements. The first sort are nodes
+that affect the positioning of display primitives and other clips. These
+nodes can currently be reference frames, scroll frames, or sticky frames.
+The second sort of node is a clip node, which specifies some combination of
+a clipping rectangle, a collection of rounded clipping rectangles, and an
+optional image mask. These nodes are created and added to the display list
+during display list flattening.
 
-The clip display item has a `ClipRegion` as well as several other fields:
-
-```rust
-pub struct ClipDisplayItem {
-    pub id: ClipId,
-    pub parent_id: ClipId,
-}
-```
+# `ClipChain`s
 
-A `ClipDisplayItem` also gets a clip and bounds from the `BaseDisplayItem`. The
-clip is shared among all items that use this `ClipDisplayItem`. Critically,
-**coordinates in this ClipRegion are defined relative to the bounds of the
-ClipDisplayItem itself**. Additionally, WebRender only supports clip rectangles
-that start at the origin of the `BaseDisplayItem` bounds.
-
-The `BaseDisplayItem` bounds are known as the *content rectangle* of the clip. If
-the content rectangle is larger than *main* clipping rectangle, the clip will
-be a scrolling clip and participate in scrolling event capture and
-transformation.
-
-`ClipDisplayItems` are positioned, like all other items, relatively to their
-containing stacking context, yet they also live in a parallel tree defined by
-their `parent_id`. Child clips also include any clipping and scrolling that
-their ancestors do. In this way, a clip is positioned by a stacking context,
-but that position may be adjusted by any scroll offset of its parent clips.
-
-## Clip ids
+A `ClipChain` represents some collection of `ClipId`s of clipping nodes in the
+`ClipScrollTree`. The collection defines a clip mask which can be applied
+to a given display item primitive. A `ClipChain` is automatically created
+for every clipping node in the `ClipScrollTree` from the particular node
+and every ancestor clipping node. Additionally, the API exposes functionality
+to create a `ClipChain` given an arbitrary list of clipping nodes and the
+`ClipId` of a parent `ClipChain`. These custom `ClipChain`s will not take
+into account ancestor clipping nodes in the `ClipScrollTree` when clipping
+the item.
 
-All clips defined by a `ClipDisplayItem` have an id. It is useful to associate
-an external id with WebRender id in order to allow for tracking and updating
-scroll positions using the WebRender API. In order to make this as cheap as
-possible and to avoid having to create a `HashMap` to map between the two types
-of ids, the WebRender API provides an optional id argument in
-`DisplayListBuilder::define_clip`. The only types of ids that are supported
-here are those created with `ClipId::new(...)`. If this argument is not
-provided `define_clip` will return a uniquely generated id. Thus, the following
-should always be true:
+Important information about `ClipChain`s:
+ * The `ClipId`s in the list must refer to clipping nodes in the `ClipScrollTree`.
+   The list should not contain `ClipId` of positioning nodes or other `ClipChain`s.
+ * The `ClipId` of a clip node serves at the `ClipId` of that node's automatically
+   generated `ClipChain` as well.
+
+# The Future
 
-```rust
-let id = ClipId::new(my_internal_id, pipeline_id);
-let generated_id = define_clip(content_rect, clip, id);
-assert!(id == generated_id);
-```
-
-Note that calling `define_clip` multiple times with the same `clip_id` value
-results in undefined behaviour, and should be avoided. There is a debug mode
-assertion to catch this.
-
-## Pending changes
-1. Normalize the way that clipping coordinates are defined. Having them
-   specified in two different ways makes for a confusing API. This should be
-   fixed.  ([github issue](https://github.com/servo/webrender/issues/1090))
-
-1. It should be possible to specify more than one predefined clip for an item.
-   This is necessary for items that live in a scrolling frame, but are also
-   clipped by a clip that lives outside that frame.
-   ([github issue](https://github.com/servo/webrender/issues/840))
+In general, the clipping API is becoming more and more stable as it has become
+more flexible. Some ideas for improving the API further:
+ * Creating a distinction between ids that refer to `ClipScrollTree` nodes and individual
+  `ClipChain`s. This would make it harder to accidentally misuse the API, but require
+   `DisplayListBuilder::define_clip` to return two different types of ids.
+ * Separate out the clipping nodes from the positioning nodes. Perhaps WebRender only
+   needs an API where `ClipChains` are defined individually. This could potentially
+   prevent unnecessary `ClipChain` creation during display list flattening.
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -90,20 +90,20 @@ void brush_vs(
         local_rect = segment_rect;
         stretch_size = local_rect.size;
 
         // Note: Here we can assume that texels in device
         //       space map to local space, due to how border-image
         //       works. That assumption may not hold if this
         //       is used for other purposes in the future.
         if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_X) != 0) {
-            stretch_size.x = texel_rect.z - texel_rect.x;
+            stretch_size.x = (texel_rect.z - texel_rect.x) / uDevicePixelRatio;
         }
         if ((brush_flags & BRUSH_FLAG_SEGMENT_REPEAT_Y) != 0) {
-            stretch_size.y = texel_rect.w - texel_rect.y;
+            stretch_size.y = (texel_rect.w - texel_rect.y) / uDevicePixelRatio;
         }
 
         uv0 = res.uv_rect.p0 + texel_rect.xy;
         uv1 = res.uv_rect.p0 + texel_rect.zw;
     }
 
     vUv.z = res.layer;
 
--- a/gfx/webrender/res/cs_border_segment.glsl
+++ b/gfx/webrender/res/cs_border_segment.glsl
@@ -8,34 +8,38 @@
 // are the colors of each edge making up the corner.
 flat varying vec4 vColor0[2];
 flat varying vec4 vColor1[2];
 
 // A point + tangent defining the line where the edge
 // transition occurs. Used for corners only.
 flat varying vec4 vColorLine;
 
-// x = segment, y = styles, z = edge axes
-flat varying ivec3 vConfig;
+// x = segment, y = styles, z = edge axes, w = clip mode
+flat varying ivec4 vConfig;
 
 // xy = Local space position of the clip center.
 // zw = Scale the rect origin by this to get the outer
 // corner from the segment rectangle.
 flat varying vec4 vClipCenter_Sign;
 
 // An outer and inner elliptical radii for border
 // corner clipping.
 flat varying vec4 vClipRadii;
 
 // Reference point for determine edge clip lines.
 flat varying vec4 vEdgeReference;
 
 // Stores widths/2 and widths/3 to save doing this in FS.
 flat varying vec4 vPartialWidths;
 
+// Clipping parameters for dot or dash.
+flat varying vec4 vClipParams1;
+flat varying vec4 vClipParams2;
+
 // Local space position
 varying vec2 vPos;
 
 #define SEGMENT_TOP_LEFT        0
 #define SEGMENT_TOP_RIGHT       1
 #define SEGMENT_BOTTOM_RIGHT    2
 #define SEGMENT_BOTTOM_LEFT     3
 #define SEGMENT_LEFT            4
@@ -50,25 +54,31 @@ varying vec2 vPos;
 #define BORDER_STYLE_DOTTED       3
 #define BORDER_STYLE_DASHED       4
 #define BORDER_STYLE_HIDDEN       5
 #define BORDER_STYLE_GROOVE       6
 #define BORDER_STYLE_RIDGE        7
 #define BORDER_STYLE_INSET        8
 #define BORDER_STYLE_OUTSET       9
 
+#define CLIP_NONE   0
+#define CLIP_DASH   1
+#define CLIP_DOT    2
+
 #ifdef WR_VERTEX_SHADER
 
 in vec2 aTaskOrigin;
 in vec4 aRect;
 in vec4 aColor0;
 in vec4 aColor1;
 in int aFlags;
 in vec2 aWidths;
 in vec2 aRadii;
+in vec4 aClipParams1;
+in vec4 aClipParams2;
 
 vec2 get_outer_corner_scale(int segment) {
     vec2 p;
 
     switch (segment) {
         case SEGMENT_TOP_LEFT:
             p = vec2(0.0, 0.0);
             break;
@@ -115,16 +125,17 @@ vec4[2] get_colors_for_side(vec4 color, 
 
     return result;
 }
 
 void main(void) {
     int segment = aFlags & 0xff;
     int style0 = (aFlags >> 8) & 0xff;
     int style1 = (aFlags >> 16) & 0xff;
+    int clip_mode = (aFlags >> 24) & 0xff;
 
     vec2 outer_scale = get_outer_corner_scale(segment);
     vec2 outer = outer_scale * aRect.zw;
     vec2 clip_sign = 1.0 - 2.0 * outer_scale;
 
     // Set some flags used by the FS to determine the
     // orientation of the two edges in this corner.
     ivec2 edge_axis;
@@ -156,30 +167,45 @@ void main(void) {
         case SEGMENT_LEFT:
         case SEGMENT_RIGHT:
         default:
             edge_axis = ivec2(0, 0);
             edge_reference = vec2(0.0);
             break;
     }
 
-    vConfig = ivec3(
+    vConfig = ivec4(
         segment,
         style0 | (style1 << 16),
-        edge_axis.x | (edge_axis.y << 16)
+        edge_axis.x | (edge_axis.y << 16),
+        clip_mode
     );
     vPartialWidths = vec4(aWidths / 3.0, aWidths / 2.0);
     vPos = aRect.zw * aPosition.xy;
 
     vColor0 = get_colors_for_side(aColor0, style0);
     vColor1 = get_colors_for_side(aColor1, style1);
     vClipCenter_Sign = vec4(outer + clip_sign * aRadii, clip_sign);
     vClipRadii = vec4(aRadii, max(aRadii - aWidths, 0.0));
     vColorLine = vec4(outer, aWidths.y * -clip_sign.y, aWidths.x * clip_sign.x);
     vEdgeReference = vec4(edge_reference, edge_reference + aWidths);
+    vClipParams1 = aClipParams1;
+    vClipParams2 = aClipParams2;
+
+    // For the case of dot clips, optimize the number of pixels that
+    // are hit to just include the dot itself.
+    // TODO(gw): We should do something similar in the future for
+    //           dash clips!
+    if (clip_mode == CLIP_DOT) {
+        // Expand by a small amount to allow room for AA around
+        // the dot.
+        float expanded_radius = aClipParams1.z + 2.0;
+        vPos = vClipParams1.xy + expanded_radius * (2.0 * aPosition.xy - 1.0);
+        vPos = clamp(vPos, vec2(0.0), aRect.zw);
+    }
 
     gl_Position = uTransform * vec4(aTaskOrigin + aRect.xy + vPos, 0.0, 1.0);
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec4 evaluate_color_for_style_in_corner(
     vec2 clip_relative_pos,
@@ -271,32 +297,56 @@ vec4 evaluate_color_for_style_in_edge(
             break;
     }
 
     return color[0];
 }
 
 void main(void) {
     float aa_range = compute_aa_range(vPos);
-    float d = -1.0;
     vec4 color0, color1;
 
     int segment = vConfig.x;
     ivec2 style = ivec2(vConfig.y & 0xffff, vConfig.y >> 16);
     ivec2 edge_axis = ivec2(vConfig.z & 0xffff, vConfig.z >> 16);
+    int clip_mode = vConfig.w;
 
     float mix_factor = 0.0;
     if (edge_axis.x != edge_axis.y) {
         float d_line = distance_to_line(vColorLine.xy, vColorLine.zw, vPos);
         mix_factor = distance_aa(aa_range, -d_line);
     }
 
     // Check if inside corner clip-region
     vec2 clip_relative_pos = vPos - vClipCenter_Sign.xy;
     bool in_clip_region = all(lessThan(vClipCenter_Sign.zw * clip_relative_pos, vec2(0.0)));
+    float d = -1.0;
+
+    switch (clip_mode) {
+        case CLIP_DOT: {
+            // Set clip distance based or dot position and radius.
+            d = distance(vClipParams1.xy, vPos) - vClipParams1.z;
+            break;
+        }
+        case CLIP_DASH: {
+            // Get SDF for the two line/tangent clip lines,
+            // do SDF subtract to get clip distance.
+            float d0 = distance_to_line(vClipParams1.xy,
+                                        vClipParams1.zw,
+                                        vPos);
+            float d1 = distance_to_line(vClipParams2.xy,
+                                        vClipParams2.zw,
+                                        vPos);
+            d = max(d0, -d1);
+            break;
+        }
+        case CLIP_NONE:
+        default:
+            break;
+    }
 
     if (in_clip_region) {
         float d_radii_a = distance_to_ellipse(clip_relative_pos, vClipRadii.xy, aa_range);
         float d_radii_b = distance_to_ellipse(clip_relative_pos, vClipRadii.zw, aa_range);
         float d_radii = max(d_radii_a, -d_radii_b);
         d = max(d, d_radii);
 
         color0 = evaluate_color_for_style_in_corner(
deleted file mode 100644
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ /dev/null
@@ -1,204 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,clip_shared
-
-varying vec3 vPos;
-
-flat varying vec2 vClipCenter;
-
-flat varying vec4 vPoint_Tangent0;
-flat varying vec4 vPoint_Tangent1;
-flat varying vec3 vDotParams;
-flat varying vec2 vAlphaMask;
-flat varying vec4 vTaskRect;
-
-#ifdef WR_VERTEX_SHADER
-
-in vec4 aDashOrDot0;
-in vec4 aDashOrDot1;
-
-// Matches BorderCorner enum in border.rs
-#define CORNER_TOP_LEFT     0
-#define CORNER_TOP_RIGHT    1
-#define CORNER_BOTTOM_LEFT  2
-#define CORNER_BOTTOM_RIGHT 3
-
-// Matches BorderCornerClipKind enum in border.rs
-#define CLIP_MODE_DASH      0
-#define CLIP_MODE_DOT       1
-
-// Header for a border corner clip.
-struct BorderCorner {
-    RectWithSize rect;
-    vec2 clip_center;
-    int corner;
-    int clip_mode;
-};
-
-BorderCorner fetch_border_corner(ivec2 address) {
-    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
-    return BorderCorner(RectWithSize(data[0].xy, data[0].zw),
-                        data[1].xy,
-                        int(data[1].z),
-                        int(data[1].w));
-}
-
-// Per-dash clip information.
-struct BorderClipDash {
-    vec4 point_tangent_0;
-    vec4 point_tangent_1;
-};
-
-BorderClipDash fetch_border_clip_dash(ivec2 address) {
-    return BorderClipDash(aDashOrDot0, aDashOrDot1);
-}
-
-// Per-dot clip information.
-struct BorderClipDot {
-    vec3 center_radius;
-};
-
-BorderClipDot fetch_border_clip_dot(ivec2 address) {
-    return BorderClipDot(aDashOrDot0.xyz);
-}
-
-void main(void) {
-    ClipMaskInstance cmi = fetch_clip_item();
-    ClipArea area = fetch_clip_area(cmi.render_task_address);
-    ClipScrollNode scroll_node = fetch_clip_scroll_node(cmi.scroll_node_id);
-
-    // Fetch the header information for this corner clip.
-    BorderCorner corner = fetch_border_corner(cmi.clip_data_address);
-    vClipCenter = corner.clip_center;
-
-    // Get local vertex position for the corner rect.
-    // TODO(gw): We could reduce the number of pixels written here by calculating a tight
-    // fitting bounding box of the dash itself like we do for dots below.
-    vec2 pos = corner.rect.p0 + aPosition.xy * corner.rect.size;
-
-    if (cmi.segment == 0) {
-        // The first segment is used to zero out the border corner.
-        vAlphaMask = vec2(0.0);
-        vDotParams = vec3(0.0);
-        vPoint_Tangent0 = vec4(1.0);
-        vPoint_Tangent1 = vec4(1.0);
-    } else {
-        vec2 sign_modifier;
-        switch (corner.corner) {
-            case CORNER_TOP_LEFT:
-                sign_modifier = vec2(-1.0);
-                break;
-            case CORNER_TOP_RIGHT:
-                sign_modifier = vec2(1.0, -1.0);
-                break;
-            case CORNER_BOTTOM_RIGHT:
-                sign_modifier = vec2(1.0);
-                break;
-            case CORNER_BOTTOM_LEFT:
-                sign_modifier = vec2(-1.0, 1.0);
-                break;
-            default:
-                sign_modifier = vec2(0.0);
-        };
-
-        switch (corner.clip_mode) {
-            case CLIP_MODE_DASH: {
-                // Fetch the information about this particular dash.
-                BorderClipDash dash = fetch_border_clip_dash(cmi.clip_data_address);
-                vPoint_Tangent0 = dash.point_tangent_0 * sign_modifier.xyxy;
-                vPoint_Tangent1 = dash.point_tangent_1 * sign_modifier.xyxy;
-                vDotParams = vec3(0.0);
-                vAlphaMask = vec2(0.0, 1.0);
-                break;
-            }
-            case CLIP_MODE_DOT: {
-                BorderClipDot cdot = fetch_border_clip_dot(cmi.clip_data_address);
-                vPoint_Tangent0 = vec4(1.0);
-                vPoint_Tangent1 = vec4(1.0);
-                vDotParams = vec3(cdot.center_radius.xy * sign_modifier, cdot.center_radius.z);
-                vAlphaMask = vec2(1.0, 1.0);
-
-                // Generate a tighter bounding rect for dots based on their position. Dot
-                // centers are given relative to clip center, so we need to move the dot
-                // rectangle into the clip space with an origin at the top left. First,
-                // we expand the radius slightly to ensure we get full coverage on all the pixels
-                // of the dots.
-                float expanded_radius = cdot.center_radius.z + 2.0;
-                pos = (vClipCenter + vDotParams.xy - vec2(expanded_radius));
-                pos += (aPosition.xy * vec2(expanded_radius * 2.0));
-                pos = clamp(pos, corner.rect.p0, corner.rect.p0 + corner.rect.size);
-
-                break;
-            }
-            default:
-                vPoint_Tangent0 = vPoint_Tangent1 = vec4(1.0);
-                vDotParams = vec3(0.0);
-                vAlphaMask = vec2(0.0);
-        }
-    }
-
-    // Transform to world pos
-    vec4 world_pos = scroll_node.transform * vec4(pos, 0.0, 1.0);
-    world_pos.xyz /= world_pos.w;
-
-    // Scale into device pixels.
-    vec2 device_pos = world_pos.xy * uDevicePixelRatio;
-
-    // Position vertex within the render task area.
-    vec2 task_rect_origin = area.common_data.task_rect.p0;
-    vec2 final_pos = device_pos - area.screen_origin + task_rect_origin;
-
-    // We pass the task rectangle to the fragment shader so that we can do one last clip
-    // in order to ensure that we don't draw outside the task rectangle.
-    vTaskRect.xy = task_rect_origin;
-    vTaskRect.zw = task_rect_origin + area.common_data.task_rect.size;
-
-    // Calculate the local space position for this vertex.
-    vec4 node_pos = get_node_pos(world_pos.xy, scroll_node);
-    vPos = node_pos.xyw;
-
-    gl_Position = uTransform * vec4(final_pos, 0.0, 1.0);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    vec2 local_pos = vPos.xy / vPos.z;
-
-    // Get local space position relative to the clip center.
-    vec2 clip_relative_pos = local_pos - vClipCenter;
-
-    // Get the signed distances to the two clip lines.
-    float d0 = distance_to_line(vPoint_Tangent0.xy,
-                                vPoint_Tangent0.zw,
-                                clip_relative_pos);
-    float d1 = distance_to_line(vPoint_Tangent1.xy,
-                                vPoint_Tangent1.zw,
-                                clip_relative_pos);
-
-    // Get AA widths based on zoom / scale etc.
-    float aa_range = compute_aa_range(local_pos);
-
-    // SDF subtract edges for dash clip
-    float dash_distance = max(d0, -d1);
-
-    // Get distance from dot.
-    float dot_distance = distance(clip_relative_pos, vDotParams.xy) - vDotParams.z;
-
-    // Select between dot/dash clip based on mode.
-    float d = mix(dash_distance, dot_distance, vAlphaMask.x);
-
-    // Apply AA.
-    d = distance_aa(aa_range, d);
-
-    // Completely mask out clip if zero'ing out the rect.
-    d = d * vAlphaMask.y;
-
-    // Make sure that we don't draw outside the task rectangle.
-    d = d * point_inside_rect(gl_FragCoord.xy, vTaskRect.xy, vTaskRect.zw);
-
-    oFragColor = vec4(d, 0.0, 0.0, 1.0);
-}
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ /dev/null
@@ -1,426 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,prim_shared,shared_border,ellipse
-
-// Edge color transition
-flat varying vec4 vColor00;
-flat varying vec4 vColor01;
-flat varying vec4 vColor10;
-flat varying vec4 vColor11;
-flat varying vec4 vColorEdgeLine;
-
-// Border radius
-flat varying vec2 vClipCenter;
-flat varying vec4 vRadii0;
-flat varying vec4 vRadii1;
-flat varying vec2 vClipSign;
-flat varying vec4 vEdgeDistance;
-flat varying float vSDFSelect;
-
-flat varying float vIsBorderRadiusLessThanBorderWidth;
-
-// Border style
-flat varying float vAlphaSelect;
-
-varying vec2 vLocalPos;
-
-#ifdef WR_VERTEX_SHADER
-// Matches BorderCornerSide enum in border.rs
-#define SIDE_BOTH       0
-#define SIDE_FIRST      1
-#define SIDE_SECOND     2
-
-vec2 get_radii(vec2 radius, vec2 invalid) {
-    if (all(greaterThan(radius, vec2(0.0)))) {
-        return radius;
-    }
-
-    return invalid;
-}
-
-void set_radii(int style,
-               vec2 radii,
-               vec2 widths,
-               vec2 adjusted_widths) {
-    vRadii0.xy = get_radii(radii, 2.0 * widths);
-    vRadii0.zw = get_radii(radii - widths, -widths);
-
-    switch (style) {
-        case BORDER_STYLE_RIDGE:
-        case BORDER_STYLE_GROOVE:
-            vRadii1.xy = radii - adjusted_widths;
-            // See comment in default branch
-            vRadii1.zw = vec2(-100.0);
-            break;
-        case BORDER_STYLE_DOUBLE:
-            vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
-            vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
-            break;
-        default:
-            // These aren't needed, so we set them to some reasonably large
-            // negative value so later computations will discard them. This
-            // avoids branches and numerical issues in the fragment shader.
-            vRadii1.xy = vec2(-100.0);
-            vRadii1.zw = vec2(-100.0);
-            break;
-    }
-}
-
-void set_edge_line(vec2 border_width,
-                   vec2 outer_corner,
-                   vec2 gradient_sign) {
-    vec2 gradient = border_width * gradient_sign;
-    vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
-}
-
-void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
-    vec4 modulate;
-
-    switch (style) {
-        case BORDER_STYLE_GROOVE:
-            modulate = vec4(1.0 - 0.3 * delta.x,
-                            1.0 + 0.3 * delta.x,
-                            1.0 - 0.3 * delta.y,
-                            1.0 + 0.3 * delta.y);
-
-            break;
-        case BORDER_STYLE_RIDGE:
-            modulate = vec4(1.0 + 0.3 * delta.x,
-                            1.0 - 0.3 * delta.x,
-                            1.0 + 0.3 * delta.y,
-                            1.0 - 0.3 * delta.y);
-            break;
-        default:
-            modulate = vec4(1.0);
-            break;
-    }
-
-    // Optionally mask out one side of the border corner,
-    // depending on the instance kind.
-    switch (instance_kind) {
-        case SIDE_FIRST:
-            color0.a = 0.0;
-            break;
-        case SIDE_SECOND:
-            color1.a = 0.0;
-            break;
-        default: break;
-    }
-
-    vColor00 = vec4(clamp(color0.rgb * modulate.x, vec3(0.0), vec3(color0.a)), color0.a);
-    vColor01 = vec4(clamp(color0.rgb * modulate.y, vec3(0.0), vec3(color0.a)), color0.a);
-    vColor10 = vec4(clamp(color1.rgb * modulate.z, vec3(0.0), vec3(color1.a)), color1.a);
-    vColor11 = vec4(clamp(color1.rgb * modulate.w, vec3(0.0), vec3(color1.a)), color1.a);
-}
-
-int select_style(int color_select, vec2 fstyle) {
-    ivec2 style = ivec2(fstyle);
-
-    switch (color_select) {
-        case SIDE_BOTH:
-        {
-            // TODO(gw): A temporary hack! While we don't support
-            //           border corners that have dots or dashes
-            //           with another style, pretend they are solid
-            //           border corners.
-            bool has_dots = style.x == BORDER_STYLE_DOTTED ||
-                            style.y == BORDER_STYLE_DOTTED;
-            bool has_dashes = style.x == BORDER_STYLE_DASHED ||
-                              style.y == BORDER_STYLE_DASHED;
-            if (style.x != style.y && (has_dots || has_dashes))
-                return BORDER_STYLE_SOLID;
-            return style.x;
-        }
-        case SIDE_FIRST:
-            return style.x;
-        case SIDE_SECOND:
-            return style.y;
-        default:
-            return 0;
-    }
-}
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.specific_prim_address);
-    int sub_part = prim.user_data0;
-    BorderCorners corners = get_border_corners(border, prim.local_rect);
-
-    vec2 p0, p1;
-
-    // TODO(gw): We'll need to pass through multiple styles
-    //           once we support style transitions per corner.
-    int style;
-    vec4 edge_distances;
-    vec4 color0, color1;
-    vec2 color_delta;
-    vec4 edge_mask;
-
-    // TODO(gw): Now that all border styles are supported, the
-    //           statement below can be tidied up quite a bit.
-
-    switch (sub_part) {
-        case 0: {
-            p0 = corners.tl_outer;
-            p1 = corners.tl_inner;
-            color0 = border.colors[0];
-            color1 = border.colors[1];
-            vClipCenter = corners.tl_outer + border.radii[0].xy;
-            vClipSign = vec2(1.0);
-            style = select_style(prim.user_data1, border.style.yx);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[0].xy,
-                      border.widths.xy,
-                      adjusted_widths.xy);
-            set_edge_line(border.widths.xy,
-                          corners.tl_outer,
-                          vec2(1.0, 1.0));
-            edge_distances = vec4(p0 + adjusted_widths.xy,
-                                  p0 + inv_adjusted_widths.xy);
-            color_delta = vec2(1.0);
-            vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[0].xy,
-                                                              border.widths.xy)) ? 1.0 : 0.0;
-            edge_mask = vec4(1.0, 1.0, 0.0, 0.0);
-            break;
-        }
-        case 1: {
-            p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
-            p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
-            color0 = border.colors[1];
-            color1 = border.colors[2];
-            vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
-            vClipSign = vec2(-1.0, 1.0);
-            style = select_style(prim.user_data1, border.style.zy);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[0].zw,
-                      border.widths.zy,
-                      adjusted_widths.zy);
-            set_edge_line(border.widths.zy,
-                          corners.tr_outer,
-                          vec2(-1.0, 1.0));
-            edge_distances = vec4(p1.x - adjusted_widths.z,
-                                  p0.y + adjusted_widths.y,
-                                  p1.x - border.widths.z + adjusted_widths.z,
-                                  p0.y + inv_adjusted_widths.y);
-            color_delta = vec2(1.0, -1.0);
-            vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[0].zw,
-                                                              border.widths.zy)) ? 1.0 : 0.0;
-            edge_mask = vec4(0.0, 1.0, 1.0, 0.0);
-            break;
-        }
-        case 2: {
-            p0 = corners.br_inner;
-            p1 = corners.br_outer;
-            color0 = border.colors[2];
-            color1 = border.colors[3];
-            vClipCenter = corners.br_outer - border.radii[1].xy;
-            vClipSign = vec2(-1.0, -1.0);
-            style = select_style(prim.user_data1, border.style.wz);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[1].xy,
-                      border.widths.zw,
-                      adjusted_widths.zw);
-            set_edge_line(border.widths.zw,
-                          corners.br_outer,
-                          vec2(-1.0, -1.0));
-            edge_distances = vec4(p1.x - adjusted_widths.z,
-                                  p1.y - adjusted_widths.w,
-                                  p1.x - border.widths.z + adjusted_widths.z,
-                                  p1.y - border.widths.w + adjusted_widths.w);
-            color_delta = vec2(-1.0);
-            vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[1].xy,
-                                                              border.widths.zw)) ? 1.0 : 0.0;
-            edge_mask = vec4(0.0, 0.0, 1.0, 1.0);
-            break;
-        }
-        case 3: {
-            p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
-            p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
-            color0 = border.colors[3];
-            color1 = border.colors[0];
-            vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
-            vClipSign = vec2(1.0, -1.0);
-            style = select_style(prim.user_data1, border.style.xw);
-            vec4 adjusted_widths = get_effective_border_widths(border, style);
-            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
-            set_radii(style,
-                      border.radii[1].zw,
-                      border.widths.xw,
-                      adjusted_widths.xw);
-            set_edge_line(border.widths.xw,
-                          corners.bl_outer,
-                          vec2(1.0, -1.0));
-            edge_distances = vec4(p0.x + adjusted_widths.x,
-                                  p1.y - adjusted_widths.w,
-                                  p0.x + inv_adjusted_widths.x,
-                                  p1.y - border.widths.w + adjusted_widths.w);
-            color_delta = vec2(-1.0, 1.0);
-            vIsBorderRadiusLessThanBorderWidth = any(lessThan(border.radii[1].zw,
-                                                              border.widths.xw)) ? 1.0 : 0.0;
-            edge_mask = vec4(1.0, 0.0, 0.0, 1.0);
-            break;
-        }
-        default:
-            p0 = p1 = vec2(0.0);
-            color0 = color1 = vec4(1.0);
-            vClipCenter = vClipSign = vec2(0.0);
-            style = 0;
-            edge_distances = edge_mask = vec4(0.0);
-            color_delta = vec2(0.0);
-            vIsBorderRadiusLessThanBorderWidth = 0.0;
-    }
-
-    switch (style) {
-        case BORDER_STYLE_DOUBLE: {
-            vEdgeDistance = edge_distances;
-            vAlphaSelect = 0.0;
-            vSDFSelect = 0.0;
-            break;
-        }
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 1.0;
-            break;
-        case BORDER_STYLE_DOTTED:
-            // Disable normal clip radii for dotted corners, since
-            // all the clipping is handled by the clip mask.
-            vClipSign = vec2(0.0);
-            vEdgeDistance = vec4(0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 0.0;
-            break;
-        default: {
-            vEdgeDistance = vec4(0.0);
-            vAlphaSelect = 1.0;
-            vSDFSelect = 0.0;
-            break;
-        }
-    }
-
-    write_color(color0, color1, style, color_delta, prim.user_data1);
-
-    RectWithSize segment_rect;
-    segment_rect.p0 = p0;
-    segment_rect.size = p1 - p0;
-
-#ifdef WR_FEATURE_TRANSFORM
-    VertexInfo vi = write_transform_vertex(segment_rect,
-                                           prim.local_rect,
-                                           prim.local_clip_rect,
-                                           edge_mask,
-                                           prim.z,
-                                           prim.scroll_node,
-                                           prim.task,
-                                           true);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.scroll_node,
-                                 prim.task,
-                                 prim.local_rect);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    float alpha = 1.0;
-#ifdef WR_FEATURE_TRANSFORM
-    alpha = init_transform_fs(vLocalPos);
-#endif
-
-    alpha *= do_clip();
-
-    float aa_range = compute_aa_range(vLocalPos);
-
-    float distance_for_color;
-    float color_mix_factor;
-
-    // Only apply the clip AA if inside the clip region. This is
-    // necessary for correctness when the border width is greater
-    // than the border radius.
-    if (vIsBorderRadiusLessThanBorderWidth == 0.0 ||
-        all(lessThan(vLocalPos * vClipSign, vClipCenter * vClipSign))) {
-        vec2 p = vLocalPos - vClipCenter;
-
-        // The coordinate system is snapped to pixel boundaries. To sample the distance,
-        // however, we are interested in the center of the pixels which introduces an
-        // error of half a pixel towards the exterior of the curve (See issue #1750).
-        // This error is corrected by offsetting the distance by half a device pixel.
-        // This not entirely correct: it leaves an error that varries between
-        // 0 and (sqrt(2) - 1)/2 = 0.2 pixels but it is hardly noticeable and is better
-        // than the constant sqrt(2)/2 px error without the correction.
-        // To correct this exactly we would need to offset p by half a pixel in the
-        // direction of the center of the ellipse (a different offset for each corner).
-
-        // Get signed distance from the inner/outer clips.
-        float d0 = distance_to_ellipse(p, vRadii0.xy, aa_range);
-        float d1 = distance_to_ellipse(p, vRadii0.zw, aa_range);
-        float d2 = distance_to_ellipse(p, vRadii1.xy, aa_range);
-        float d3 = distance_to_ellipse(p, vRadii1.zw, aa_range);
-
-        // SDF subtract main radii
-        float d_main = max(d0, -d1);
-
-        // SDF subtract inner radii (double style borders)
-        float d_inner = max(d2, -d3);
-
-        // Select how to combine the SDF based on border style.
-        float d = mix(max(d_main, -d_inner), d_main, vSDFSelect);
-
-        // Only apply AA to fragments outside the signed distance field.
-        alpha = min(alpha, distance_aa(aa_range, d));
-
-        // Get the groove/ridge mix factor.
-        color_mix_factor = distance_aa(aa_range, d2);
-    } else {
-        // Handle the case where the fragment is outside the clip
-        // region in a corner. This occurs when border width is
-        // greater than border radius.
-
-        // Get linear distances along horizontal and vertical edges.
-        vec2 d0 = vClipSign.xx * (vLocalPos.xx - vEdgeDistance.xz);
-        vec2 d1 = vClipSign.yy * (vLocalPos.yy - vEdgeDistance.yw);
-        // Apply union to get the outer edge signed distance.
-        float da = min(d0.x, d1.x);
-        // Apply intersection to get the inner edge signed distance.
-        float db = max(-d0.y, -d1.y);
-        // Apply union to get both edges.
-        float d = min(da, db);
-        // Select fragment on/off based on signed distance.
-        // No AA here, since we know we're on a straight edge
-        // and the width is rounded to a whole CSS pixel.
-        alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
-
-        // Get the groove/ridge mix factor.
-        // TODO(gw): Support AA for groove/ridge border edge with transforms.
-        color_mix_factor = mix(0.0, 1.0, da > 0.0);
-    }
-
-    // Mix inner/outer color.
-    vec4 color0 = mix(vColor00, vColor01, color_mix_factor);
-    vec4 color1 = mix(vColor10, vColor11, color_mix_factor);
-
-    // Select color based on side of line. Get distance from the
-    // reference line, and then apply AA along the edge.
-    float ld = distance_to_line(vColorEdgeLine.xy, vColorEdgeLine.zw, vLocalPos);
-    float m = distance_aa(aa_range, -ld);
-    vec4 color = mix(color0, color1, m);
-
-    oFragColor = color * alpha;
-}
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ /dev/null
@@ -1,334 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include shared,prim_shared,shared_border
-
-flat varying vec4 vColor0;
-flat varying vec4 vColor1;
-flat varying vec2 vEdgeDistance;
-flat varying float vAxisSelect;
-flat varying float vAlphaSelect;
-flat varying vec4 vClipParams;
-flat varying float vClipSelect;
-
-varying vec2 vLocalPos;
-
-#ifdef WR_VERTEX_SHADER
-void write_edge_distance(float p0,
-                         float original_width,
-                         float adjusted_width,
-                         float style,
-                         float axis_select,
-                         float sign_adjust) {
-    switch (int(style)) {
-        case BORDER_STYLE_DOUBLE:
-            vEdgeDistance = vec2(p0 + adjusted_width,
-                                 p0 + original_width - adjusted_width);
-            break;
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            vEdgeDistance = vec2(p0 + adjusted_width, sign_adjust);
-            break;
-        default:
-            vEdgeDistance = vec2(0.0);
-            break;
-    }
-
-    vAxisSelect = axis_select;
-}
-
-void write_alpha_select(float style) {
-    switch (int(style)) {
-        case BORDER_STYLE_DOUBLE:
-            vAlphaSelect = 0.0;
-            break;
-        default:
-            vAlphaSelect = 1.0;
-            break;
-    }
-}
-
-// write_color function is duplicated to work around a Mali-T880 GPU driver program link error.
-// See https://github.com/servo/webrender/issues/1403 for more info.
-// TODO: convert back to a single function once the driver issues are resolved, if ever.
-void write_color0(vec4 color, float style, bool flip) {
-    vec2 modulate;
-
-    switch (int(style)) {
-        case BORDER_STYLE_GROOVE:
-        {
-            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
-            break;
-        }
-        case BORDER_STYLE_RIDGE:
-        {
-            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
-            break;
-        }
-        default:
-            modulate = vec2(1.0);
-            break;
-    }
-
-    vColor0 = vec4(min(color.rgb * modulate.x, vec3(color.a)), color.a);
-}
-
-void write_color1(vec4 color, float style, bool flip) {
-    vec2 modulate;
-
-    switch (int(style)) {
-        case BORDER_STYLE_GROOVE:
-        {
-            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
-            break;
-        }
-        case BORDER_STYLE_RIDGE:
-        {
-            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
-            break;
-        }
-        default:
-            modulate = vec2(1.0);
-            break;
-    }
-
-    vColor1 = vec4(min(color.rgb * modulate.y, vec3(color.a)), color.a);
-}
-
-void write_clip_params(float style,
-                       float border_width,
-                       float edge_length,
-                       float edge_offset,
-                       float center_line,
-                       bool start_corner_has_radius,
-                       bool end_corner_has_radius) {
-    // x = offset
-    // y = dash on + off length
-    // z = dash length
-    // w = center line of edge cross-axis (for dots only)
-    switch (int(style)) {
-        case BORDER_STYLE_DASHED: {
-            float desired_dash_length = border_width * 3.0;
-            // Consider half total length since there is an equal on/off for each dash.
-            float dash_count = ceil(0.5 * edge_length / desired_dash_length);
-            float dash_length = 0.5 * edge_length / dash_count;
-            vClipParams = vec4(edge_offset - 0.5 * dash_length,
-                               2.0 * dash_length,
-                               dash_length,
-                               0.0);
-            vClipSelect = 0.0;
-            break;
-        }
-        case BORDER_STYLE_DOTTED: {
-            float diameter = border_width;
-            float radius = 0.5 * diameter;
-
-            // If this edge connects a corner with a radius to a corner without a radius, we
-            // act as if we have space for one more dot. This will position the dots so that
-            // there is a half dot on one of the ends.
-            float full_edge_length = edge_length +
-                (float(start_corner_has_radius ^^ end_corner_has_radius) * diameter);
-
-            float dot_count = ceil(0.5 * full_edge_length / diameter);
-            float empty_space = full_edge_length - (dot_count * diameter);
-            float distance_between_centers = diameter + empty_space / dot_count;
-
-            // If the starting corner has a radius, we want to position the half dot right
-            // against that edge.
-            float starting_offset =
-                edge_offset + radius + (-diameter * float(start_corner_has_radius));
-
-            vClipParams = vec4(starting_offset,
-                               distance_between_centers,
-                               radius,
-                               center_line);
-
-            vClipSelect = 1.0;
-            break;
-        }
-        default:
-            vClipParams = vec4(1.0);
-            vClipSelect = 0.0;
-            break;
-    }
-}
-
-bool hasRadius(vec2 radius) {
-    return any(notEqual(radius, vec2(0.0)));
-}
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.specific_prim_address);
-    int sub_part = prim.user_data0;
-    BorderCorners corners = get_border_corners(border, prim.local_rect);
-    vec4 color = border.colors[sub_part];
-
-    // TODO(gw): Now that all border styles are supported, the
-    //           statement below can be tidied up quite a bit.
-
-    float style;
-    bool color_flip;
-
-    RectWithSize segment_rect;
-    vec4 edge_mask;
-
-    switch (sub_part) {
-        case 0: {
-            segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
-            segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
-            write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
-            style = border.style.x;
-            color_flip = false;
-            write_clip_params(border.style.x,
-                              border.widths.x,
-                              segment_rect.size.y,
-                              segment_rect.p0.y,
-                              segment_rect.p0.x + 0.5 * segment_rect.size.x,
-                              hasRadius(border.radii[0].xy),
-                              hasRadius(border.radii[1].zw));
-            edge_mask = vec4(1.0, 0.0, 1.0, 0.0);
-            break;
-        }
-        case 1: {
-            segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
-            segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
-            write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
-            style = border.style.y;
-            color_flip = false;
-            write_clip_params(border.style.y,
-                              border.widths.y,
-                              segment_rect.size.x,
-                              segment_rect.p0.x,
-                              segment_rect.p0.y + 0.5 * segment_rect.size.y,
-                              hasRadius(border.radii[0].xy),
-                              hasRadius(border.radii[0].zw));
-            edge_mask = vec4(0.0, 1.0, 0.0, 1.0);
-            break;
-        }
-        case 2: {
-            segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
-            segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
-            write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
-            style = border.style.z;
-            color_flip = true;
-            write_clip_params(border.style.z,
-                              border.widths.z,
-                              segment_rect.size.y,
-                              segment_rect.p0.y,
-                              segment_rect.p0.x + 0.5 * segment_rect.size.x,
-                              hasRadius(border.radii[0].zw),
-                              hasRadius(border.radii[1].xy));
-            edge_mask = vec4(1.0, 0.0, 1.0, 0.0);
-            break;
-        }
-        case 3: {
-            segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
-            segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
-            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
-            write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
-            style = border.style.w;
-            color_flip = true;
-            write_clip_params(border.style.w,
-                              border.widths.w,
-                              segment_rect.size.x,
-                              segment_rect.p0.x,
-                              segment_rect.p0.y + 0.5 * segment_rect.size.y,
-                              hasRadius(border.radii[1].zw),
-                              hasRadius(border.radii[1].xy));
-            edge_mask = vec4(0.0, 1.0, 0.0, 1.0);
-            break;
-        }
-        default:
-            segment_rect.p0 = segment_rect.size = vec2(0.0);
-            style = 0.0;
-            color_flip = false;
-            edge_mask = vec4(0.0);
-    }
-
-    write_alpha_select(style);
-    write_color0(color, style, color_flip);
-    write_color1(color, style, color_flip);
-
-#ifdef WR_FEATURE_TRANSFORM
-    VertexInfo vi = write_transform_vertex(segment_rect,
-                                           prim.local_rect,
-                                           prim.local_clip_rect,
-                                           edge_mask,
-                                           prim.z,
-                                           prim.scroll_node,
-                                           prim.task,
-                                           true);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.scroll_node,
-                                 prim.task,
-                                 prim.local_rect);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-void main(void) {
-    float alpha = 1.0;
-#ifdef WR_FEATURE_TRANSFORM
-    alpha = init_transform_fs(vLocalPos);
-#endif
-
-    alpha *= do_clip();
-
-    // Find the appropriate distance to apply the step over.
-    float aa_range = compute_aa_range(vLocalPos);
-
-    // Applies the math necessary to draw a style: double
-    // border. In the case of a solid border, the vertex
-    // shader sets interpolator values that make this have
-    // no effect.
-
-    // Select the x/y coord, depending on which axis this edge is.
-    vec2 pos = mix(vLocalPos.xy, vLocalPos.yx, vAxisSelect);
-
-    // Get signed distance from each of the inner edges.
-    float d0 = pos.x - vEdgeDistance.x;
-    float d1 = vEdgeDistance.y - pos.x;
-
-    // SDF union to select both outer edges.
-    float d = min(d0, d1);
-
-    // Select fragment on/off based on signed distance.
-    // No AA here, since we know we're on a straight edge
-    // and the width is rounded to a whole CSS pixel.
-    alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
-
-    // Mix color based on first distance.
-    // TODO(gw): Support AA for groove/ridge border edge with transforms.
-    vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
-
-    // Apply dashing / dotting parameters.
-
-    // Get the main-axis position relative to closest dot or dash.
-    float x = mod(pos.y - vClipParams.x, vClipParams.y);
-
-    // Calculate dash alpha (on/off) based on dash length
-    float dash_alpha = step(x, vClipParams.z);
-
-    // Get the dot alpha
-    vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
-    float dot_distance = length(dot_relative_pos) - vClipParams.z;
-    float dot_alpha = distance_aa(aa_range, dot_distance);
-
-    // Select between dot/dash alpha based on clip mode.
-    alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
-
-    oFragColor = color * alpha;
-}
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/shared_border.glsl
+++ /dev/null
@@ -1,98 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// Border styles as defined in webrender_api/types.rs
-#define BORDER_STYLE_NONE         0
-#define BORDER_STYLE_SOLID        1
-#define BORDER_STYLE_DOUBLE       2
-#define BORDER_STYLE_DOTTED       3
-#define BORDER_STYLE_DASHED       4
-#define BORDER_STYLE_HIDDEN       5
-#define BORDER_STYLE_GROOVE       6
-#define BORDER_STYLE_RIDGE        7
-#define BORDER_STYLE_INSET        8
-#define BORDER_STYLE_OUTSET       9
-
-#ifdef WR_VERTEX_SHADER
-
-struct Border {
-    vec4 style;
-    vec4 widths;
-    vec4 colors[4];
-    vec4 radii[2];
-};
-
-struct BorderCorners {
-    vec2 tl_outer;
-    vec2 tl_inner;
-    vec2 tr_outer;
-    vec2 tr_inner;
-    vec2 br_outer;
-    vec2 br_inner;
-    vec2 bl_outer;
-    vec2 bl_inner;
-};
-
-vec4 get_effective_border_widths(Border border, int style) {
-    switch (style) {
-        case BORDER_STYLE_DOUBLE:
-            // Calculate the width of a border segment in a style: double
-            // border. Round to the nearest CSS pixel.
-
-            // The CSS spec doesn't define what width each of the segments
-            // in a style: double border should be. It only says that the
-            // sum of the segments should be equal to the total border
-            // width. We pick to make the segments (almost) equal thirds
-            // for now - we can adjust this if we find other browsers pick
-            // different values in some cases.
-            // SEE: https://drafts.csswg.org/css-backgrounds-3/#double
-            return max(floor(0.5 + border.widths / 3.0), 1.0);
-        case BORDER_STYLE_GROOVE:
-        case BORDER_STYLE_RIDGE:
-            return floor(0.5 + border.widths * 0.5);
-        default:
-            return border.widths;
-    }
-}
-
-Border fetch_border(int address) {
-    vec4 data[8] = fetch_from_resource_cache_8(address);
-    return Border(data[0], data[1],
-                  vec4[4](data[2], data[3], data[4], data[5]),
-                  vec4[2](data[6], data[7]));
-}
-
-BorderCorners get_border_corners(Border border, RectWithSize local_rect) {
-    vec2 tl_outer = local_rect.p0;
-    vec2 tl_inner = tl_outer + vec2(max(border.radii[0].x, border.widths.x),
-                                    max(border.radii[0].y, border.widths.y));
-
-    vec2 tr_outer = vec2(local_rect.p0.x + local_rect.size.x,
-                         local_rect.p0.y);
-    vec2 tr_inner = tr_outer + vec2(-max(border.radii[0].z, border.widths.z),
-                                    max(border.radii[0].w, border.widths.y));
-
-    vec2 br_outer = vec2(local_rect.p0.x + local_rect.size.x,
-                         local_rect.p0.y + local_rect.size.y);
-    vec2 br_inner = br_outer - vec2(max(border.radii[1].x, border.widths.z),
-                                    max(border.radii[1].y, border.widths.w));
-
-    vec2 bl_outer = vec2(local_rect.p0.x,
-                         local_rect.p0.y + local_rect.size.y);
-    vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
-                                    -max(border.radii[1].w, border.widths.w));
-
-    return BorderCorners(
-        tl_outer,
-        tl_inner,
-        tr_outer,
-        tr_inner,
-        br_outer,
-        br_inner,
-        bl_outer,
-        bl_inner
-    );
-}
-
-#endif
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,33 +1,32 @@
 /* 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, ClipMode, DeviceIntRect, DeviceIntSize};
 use api::{DeviceUintRect, DeviceUintPoint, ExternalImageType, FilterOp, ImageRendering, LayoutRect};
 use api::{DeviceIntPoint, YuvColorSpace, YuvFormat};
 use api::{LayoutToWorldTransform, WorldPixel};
-use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
-use gpu_cache::{GpuCache, GpuCacheAddress};
-use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex, ClipMaskBorderCornerDotDash};
+use gpu_cache::{GpuCache, GpuCacheHandle, GpuCacheAddress};
+use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, CompositePrimitiveInstance};
 use gpu_types::{PrimitiveInstance, RasterizationSpace, SimplePrimitiveInstance, ZBufferId};
 use gpu_types::ZBufferIdGenerator;
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, CachedGradient, DeferredResolve};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource, PictureIndex, PrimitiveIndex, PrimitiveKind};
 use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore, VisibleGradientTile};
-use prim_store::{BorderSource, CachedGradientIndex};
+use prim_store::{BorderSource};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::{BLOCKS_PER_UV_RECT, ShaderColorMode};
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use scene::FilterOpHelpers;
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
@@ -36,18 +35,16 @@ use util::{MatrixHelpers, TransformedRec
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum TransformBatchKind {
     TextRun(GlyphFormat),
-    BorderCorner,
-    BorderEdge,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
     Solid,
     Image(ImageBufferKind),
@@ -317,16 +314,34 @@ impl BatchList {
             BlendMode::SubpixelWithBgColor |
             BlendMode::SubpixelDualSource => {
                 self.alpha_batch_list
                     .get_suitable_batch(key, task_relative_bounding_rect)
             }
         }
     }
 
+    // Remove any batches that were added but didn't get any instances
+    // added to them.
+    fn remove_unused_batches(&mut self) {
+        if self.opaque_batch_list
+               .batches
+               .last()
+               .map_or(false, |batch| batch.instances.is_empty()) {
+            self.opaque_batch_list.batches.pop().unwrap();
+        }
+
+        if self.alpha_batch_list
+               .batches
+               .last()
+               .map_or(false, |batch| batch.instances.is_empty()) {
+            self.alpha_batch_list.batches.pop().unwrap();
+        }
+    }
+
     fn finalize(&mut self) {
         self.opaque_batch_list.finalize()
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PrimitiveBatch {
@@ -625,17 +640,16 @@ impl AlphaBatchBuilder {
         };
 
         let prim_cache_address = if is_multiple_primitives {
             GpuCacheAddress::invalid()
         } else {
             gpu_cache.get_address(&prim_metadata.gpu_location)
         };
 
-        let no_textures = BatchTextures::no_texture();
         let clip_task_address = prim_metadata
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
         let base_instance = SimplePrimitiveInstance::new(
             prim_cache_address,
             task_address,
             clip_task_address,
             clip_chain_rect_index,
@@ -1016,56 +1030,53 @@ impl AlphaBatchBuilder {
                                     task_address,
                                     z,
                                     user_data,
                                     tile.edge_flags
                                 );
                             }
                         }
                     }
-                    BrushKind::LinearGradient { gradient_index, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                    BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
                             visible_tiles,
-                            gradient_index,
+                            stops_handle,
                             BrushBatchKind::LinearGradient,
                             specified_blend_mode,
                             &task_relative_bounding_rect,
                             clip_chain_rect_index,
                             scroll_id,
                             task_address,
                             clip_task_address,
                             z,
-                            ctx,
                             gpu_cache,
                             &mut self.batch_list,
                         );
                     }
-                    BrushKind::RadialGradient { gradient_index, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
+                    BrushKind::RadialGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => {
                         add_gradient_tiles(
                             visible_tiles,
-                            gradient_index,
+                            stops_handle,
                             BrushBatchKind::RadialGradient,
                             specified_blend_mode,
                             &task_relative_bounding_rect,
                             clip_chain_rect_index,
                             scroll_id,
                             task_address,
                             clip_task_address,
                             z,
-                            ctx,
                             gpu_cache,
                             &mut self.batch_list,
                         );
                     }
                     _ => {
                         if let Some((batch_kind, textures, user_data)) = brush.get_batch_params(
                                 ctx.resource_cache,
                                 gpu_cache,
                                 deferred_resolves,
-                                ctx.cached_gradients,
                         ) {
                             self.add_brush_to_batch(
                                 brush,
                                 prim_metadata,
                                 batch_kind,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 textures,
@@ -1079,77 +1090,16 @@ impl AlphaBatchBuilder {
                                 z,
                                 render_tasks,
                                 user_data,
                             );
                         }
                     }
                 }
             }
-            PrimitiveKind::Border => {
-                let border_cpu =
-                    &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
-                // TODO(gw): Select correct blend mode for edges and corners!!
-
-                if border_cpu.corner_instances.iter().any(|&kind| kind != BorderCornerInstance::None) {
-                    let corner_kind = BatchKind::Transformable(
-                        transform_kind,
-                        TransformBatchKind::BorderCorner,
-                    );
-                    let corner_key = BatchKey::new(corner_kind, non_segmented_blend_mode, no_textures);
-                    let batch = self.batch_list
-                        .get_suitable_batch(corner_key, &task_relative_bounding_rect);
-
-                    for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate() {
-                        let sub_index = i as i32;
-                        match *instance_kind {
-                            BorderCornerInstance::None => {}
-                            BorderCornerInstance::Single => {
-                                batch.push(base_instance.build(
-                                    sub_index,
-                                    BorderCornerSide::Both as i32,
-                                    0,
-                                ));
-                            }
-                            BorderCornerInstance::Double => {
-                                batch.push(base_instance.build(
-                                    sub_index,
-                                    BorderCornerSide::First as i32,
-                                    0,
-                                ));
-                                batch.push(base_instance.build(
-                                    sub_index,
-                                    BorderCornerSide::Second as i32,
-                                    0,
-                                ));
-                            }
-                        }
-                    }
-                }
-
-                if border_cpu.edges.iter().any(|&kind| kind != BorderEdgeKind::None) {
-                    let edge_kind = BatchKind::Transformable(
-                        transform_kind,
-                        TransformBatchKind::BorderEdge,
-                    );
-                    let edge_key = BatchKey::new(edge_kind, non_segmented_blend_mode, no_textures);
-                    let batch = self.batch_list
-                        .get_suitable_batch(edge_key, &task_relative_bounding_rect);
-
-                    for (border_segment, instance_kind) in border_cpu.edges.iter().enumerate() {
-                        match *instance_kind {
-                            BorderEdgeKind::None => {},
-                            BorderEdgeKind::Solid |
-                            BorderEdgeKind::Clip => {
-                                batch.push(base_instance.build(border_segment as i32, 0, 0));
-                            }
-                        }
-                    }
-                }
-            }
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
 
                 let font = text_cpu.get_font(
                     ctx.device_pixel_scale,
                     Some(scroll_node.transform),
                 );
@@ -1374,45 +1324,45 @@ impl AlphaBatchBuilder {
                     blend_mode: non_segmented_blend_mode,
                     kind: BatchKind::Brush(batch_kind),
                     textures,
                 };
                 let batch = self.batch_list.get_suitable_batch(batch_key, task_relative_bounding_rect);
                 batch.push(PrimitiveInstance::from(base_instance));
             }
         }
+
+        self.batch_list.remove_unused_batches();
     }
 }
 
 fn add_gradient_tiles(
     visible_tiles: &[VisibleGradientTile],
-    gradient_index: CachedGradientIndex,
+    stops_handle: &GpuCacheHandle,
     kind: BrushBatchKind,
     blend_mode: BlendMode,
     task_relative_bounding_rect: &DeviceIntRect,
     clip_chain_rect_index: ClipChainRectIndex,
     scroll_id: ClipScrollNodeIndex,
     task_address: RenderTaskAddress,
     clip_task_address: RenderTaskAddress,
     z: ZBufferId,
-    ctx: &RenderTargetContext,
     gpu_cache: &GpuCache,
     batch_list: &mut BatchList,
 ) {
     batch_list.add_bounding_rect(task_relative_bounding_rect);
     let batch = batch_list.get_suitable_batch(
         BatchKey {
             blend_mode: blend_mode,
             kind: BatchKind::Brush(kind),
             textures: BatchTextures::no_texture(),
         },
         task_relative_bounding_rect
     );
 
-    let stops_handle = &ctx.cached_gradients[gradient_index.0].handle;
     let user_data = [stops_handle.as_int(gpu_cache), 0, 0];
 
     let base_instance = BrushInstance {
         picture_address: task_address,
         prim_address: GpuCacheAddress::invalid(),
         clip_chain_rect_index,
         scroll_id,
         clip_task_address,
@@ -1476,17 +1426,16 @@ impl BrushPrimitive {
         }
     }
 
     fn get_batch_params(
         &self,
         resource_cache: &ResourceCache,
         gpu_cache: &mut GpuCache,
         deferred_resolves: &mut Vec<DeferredResolve>,
-        cached_gradients: &[CachedGradient],
     ) -> Option<(BrushBatchKind, BatchTextures, [i32; 3])> {
         match self.kind {
             BrushKind::Image { request, ref source, .. } => {
                 let cache_item = match *source {
                     ImageSource::Default => {
                         resolve_image(
                             request,
                             resource_cache,
@@ -1570,30 +1519,28 @@ impl BrushPrimitive {
             }
             BrushKind::Clear => {
                 Some((
                     BrushBatchKind::Solid,
                     BatchTextures::no_texture(),
                     [0; 3],
                 ))
             }
-            BrushKind::RadialGradient { gradient_index, .. } => {
-                let stops_handle = &cached_gradients[gradient_index.0].handle;
+            BrushKind::RadialGradient { ref stops_handle, .. } => {
                 Some((
                     BrushBatchKind::RadialGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
                 ))
             }
-            BrushKind::LinearGradient { gradient_index, .. } => {
-                let stops_handle = &cached_gradients[gradient_index.0].handle;
+            BrushKind::LinearGradient { ref stops_handle, .. } => {
                 Some((
                     BrushBatchKind::LinearGradient,
                     BatchTextures::no_texture(),
                     [
                         stops_handle.as_int(gpu_cache),
                         0,
                         0,
                     ],
@@ -1663,17 +1610,16 @@ trait AlphaBatchHelpers {
         metadata: &PrimitiveMetadata,
     ) -> BlendMode;
 }
 
 impl AlphaBatchHelpers for PrimitiveStore {
     fn get_blend_mode(&self, metadata: &PrimitiveMetadata) -> BlendMode {
         match metadata.prim_kind {
             // Can only resolve the TextRun's blend mode once glyphs are fetched.
-            PrimitiveKind::Border |
             PrimitiveKind::TextRun => {
                 BlendMode::PremultipliedAlpha
             }
 
             PrimitiveKind::Brush => {
                 let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
                 match brush.kind {
                     BrushKind::Clear => {
@@ -1824,29 +1770,25 @@ fn make_polygon(
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<ClipMaskInstance>,
     /// Image draws apply the image masking.
     pub images: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
-    pub border_clears: Vec<ClipMaskBorderCornerDotDash>,
-    pub borders: Vec<ClipMaskBorderCornerDotDash>,
     pub box_shadows: FastHashMap<SourceTexture, Vec<ClipMaskInstance>>,
     pub line_decorations: Vec<ClipMaskInstance>,
 }
 
 impl ClipBatcher {
     pub fn new() -> Self {
         ClipBatcher {
             rectangles: Vec::new(),
             images: FastHashMap::default(),
-            border_clears: Vec::new(),
-            borders: Vec::new(),
             box_shadows: FastHashMap::default(),
             line_decorations: Vec::new(),
         }
     }
 
     pub fn add_clip_region(
         &mut self,
         task_address: RenderTaskAddress,
@@ -1948,40 +1890,16 @@ impl ClipBatcher {
                         }
                     }
                     ClipSource::RoundedRectangle(..) => {
                         self.rectangles.push(ClipMaskInstance {
                             clip_data_address: gpu_address,
                             ..instance
                         });
                     }
-                    ClipSource::BorderCorner(ref source) => {
-                        let instance = ClipMaskBorderCornerDotDash {
-                            clip_mask_instance: ClipMaskInstance {
-                                clip_data_address: gpu_address,
-                                segment: 0,
-                                ..instance
-                            },
-                            dot_dash_data: [0.; 8],
-                        };
-
-                        self.border_clears.push(instance);
-
-                        for data in source.dot_dash_data.iter() {
-                            self.borders.push(ClipMaskBorderCornerDotDash {
-                                clip_mask_instance: ClipMaskInstance {
-                                    // The shader understands segment=0 as the clear, so the
-                                    // segment here just needs to be non-zero.
-                                    segment: 1,
-                                    ..instance.clip_mask_instance
-                                },
-                                dot_dash_data: *data,
-                            })
-                        }
-                    }
                 }
             }
         }
     }
 }
 
 fn get_buffer_kind(texture: SourceTexture) -> ImageBufferKind {
     match texture {
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,47 +1,22 @@
 /* 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, BorderWidths, ColorF, LayoutPoint};
+use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF};
 use api::{ColorU, DeviceRect, DeviceSize, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale};
-use api::{DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
+use api::{DevicePixel, DeviceVector2D, DevicePoint, DeviceIntSize, LayoutRect, LayoutSize, NormalBorder};
 use app_units::Au;
-use clip::ClipSource;
 use ellipse::Ellipse;
 use display_list_flattener::DisplayListFlattener;
 use gpu_types::{BorderInstance, BorderSegment, BrushFlags};
-use gpu_cache::GpuDataRequest;
-use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegment, BrushSegmentDescriptor};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegment};
 use prim_store::{BorderSource, EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
-use util::{lerp, pack_as_float, RectHelpers};
-
-#[repr(u8)]
-#[derive(Debug, Copy, Clone, PartialEq)]
-pub enum BorderCornerInstance {
-    None,
-    Single, // Single instance needed - corner styles are same or similar.
-    Double, // Different corner styles. Draw two instances, one per style.
-}
-
-#[repr(C)]
-pub enum BorderCornerSide {
-    Both,
-    First,
-    Second,
-}
-
-#[repr(C)]
-enum BorderCorner {
-    TopLeft,
-    TopRight,
-    BottomLeft,
-    BottomRight,
-}
+use util::{lerp, RectHelpers};
 
 trait AuSizeConverter {
     fn to_au(&self) -> LayoutSizeAu;
 }
 
 impl AuSizeConverter for LayoutSize {
     fn to_au(&self) -> LayoutSizeAu {
         LayoutSizeAu::new(
@@ -123,215 +98,16 @@ pub struct BorderCacheKey {
     pub right: BorderSideAu,
     pub top: BorderSideAu,
     pub bottom: BorderSideAu,
     pub radius: BorderRadiusAu,
     pub widths: BorderWidthsAu,
     pub scale: Au,
 }
 
-#[derive(Clone, Debug, PartialEq)]
-pub enum BorderCornerKind {
-    None,
-    Solid,
-    Clip(BorderCornerInstance),
-    Mask(
-        BorderCornerClipData,
-        LayoutSize,
-        LayoutSize,
-        BorderCornerClipKind,
-    ),
-}
-
-impl BorderCornerKind {
-    fn new_mask(
-        kind: BorderCornerClipKind,
-        width0: f32,
-        width1: f32,
-        corner: BorderCorner,
-        radius: LayoutSize,
-        border_rect: LayoutRect,
-    ) -> BorderCornerKind {
-        let size = LayoutSize::new(width0.max(radius.width), width1.max(radius.height));
-        let (origin, clip_center) = match corner {
-            BorderCorner::TopLeft => {
-                let origin = border_rect.origin;
-                let clip_center = origin + size;
-                (origin, clip_center)
-            }
-            BorderCorner::TopRight => {
-                let origin = LayoutPoint::new(
-                    border_rect.origin.x + border_rect.size.width - size.width,
-                    border_rect.origin.y,
-                );
-                let clip_center = origin + LayoutSize::new(0.0, size.height);
-                (origin, clip_center)
-            }
-            BorderCorner::BottomRight => {
-                let origin = border_rect.origin + (border_rect.size - size);
-                let clip_center = origin;
-                (origin, clip_center)
-            }
-            BorderCorner::BottomLeft => {
-                let origin = LayoutPoint::new(
-                    border_rect.origin.x,
-                    border_rect.origin.y + border_rect.size.height - size.height,
-                );
-                let clip_center = origin + LayoutSize::new(size.width, 0.0);
-                (origin, clip_center)
-            }
-        };
-        let clip_data = BorderCornerClipData {
-            corner_rect: LayoutRect::new(origin, size),
-            clip_center,
-            corner: pack_as_float(corner as u32),
-            kind: pack_as_float(kind as u32),
-        };
-        BorderCornerKind::Mask(clip_data, radius, LayoutSize::new(width0, width1), kind)
-    }
-
-    fn get_radius(&self, original_radius: &LayoutSize) -> LayoutSize {
-        match *self {
-            BorderCornerKind::Solid => *original_radius,
-            BorderCornerKind::Clip(..) => *original_radius,
-            BorderCornerKind::Mask(_, ref radius, _, _) => *radius,
-            BorderCornerKind::None => *original_radius,
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum BorderEdgeKind {
-    None,
-    Solid,
-    Clip,
-}
-
-fn get_corner(
-    edge0: &BorderSide,
-    width0: f32,
-    edge1: &BorderSide,
-    width1: f32,
-    radius: &LayoutSize,
-    corner: BorderCorner,
-    border_rect: &LayoutRect,
-) -> BorderCornerKind {
-    // If both widths are zero, a corner isn't formed.
-    if width0 == 0.0 && width1 == 0.0 {
-        return BorderCornerKind::None;
-    }
-
-    // If both edges are transparent, no corner is formed.
-    if edge0.color.a == 0.0 && edge1.color.a == 0.0 {
-        return BorderCornerKind::None;
-    }
-
-    match (edge0.style, edge1.style) {
-        // If both edges are none or hidden, no corner is needed.
-        (BorderStyle::None, BorderStyle::None) |
-        (BorderStyle::None, BorderStyle::Hidden) |
-        (BorderStyle::Hidden, BorderStyle::None) |
-        (BorderStyle::Hidden, BorderStyle::Hidden) => {
-            BorderCornerKind::None
-        }
-
-        // If one of the edges is none or hidden, we just draw one style.
-        (BorderStyle::None, _) |
-        (_, BorderStyle::None) |
-        (BorderStyle::Hidden, _) |
-        (_, BorderStyle::Hidden) => {
-            BorderCornerKind::Clip(BorderCornerInstance::Single)
-        }
-
-        // If both borders are solid, we can draw them with a simple rectangle if
-        // both the colors match and there is no radius.
-        (BorderStyle::Solid, BorderStyle::Solid) => {
-            if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
-                BorderCornerKind::Solid
-            } else {
-                BorderCornerKind::Clip(BorderCornerInstance::Single)
-            }
-        }
-
-        // Inset / outset borders just modify the color of edges, so can be
-        // drawn with the normal border corner shader.
-        (BorderStyle::Outset, BorderStyle::Outset) |
-        (BorderStyle::Inset, BorderStyle::Inset) |
-        (BorderStyle::Double, BorderStyle::Double) |
-        (BorderStyle::Groove, BorderStyle::Groove) |
-        (BorderStyle::Ridge, BorderStyle::Ridge) => {
-            BorderCornerKind::Clip(BorderCornerInstance::Single)
-        }
-
-        // Dashed and dotted border corners get drawn into a clip mask.
-        (BorderStyle::Dashed, BorderStyle::Dashed) => BorderCornerKind::new_mask(
-            BorderCornerClipKind::Dash,
-            width0,
-            width1,
-            corner,
-            *radius,
-            *border_rect,
-        ),
-        (BorderStyle::Dotted, BorderStyle::Dotted) => {
-            let mut radius = *radius;
-            if radius.width < width0 {
-                radius.width = 0.0;
-            }
-            if radius.height < width1 {
-                radius.height = 0.0;
-            }
-            BorderCornerKind::new_mask(
-                BorderCornerClipKind::Dot,
-                width0,
-                width1,
-                corner,
-                radius,
-                *border_rect,
-             )
-        }
-
-        // Draw border transitions with dots and/or dashes as
-        // solid segments. The old border path didn't support
-        // this anyway, so we might as well start using the new
-        // border path here, since the dashing in the edges is
-        // much higher quality anyway.
-        (BorderStyle::Dotted, _) |
-        (_, BorderStyle::Dotted) |
-        (BorderStyle::Dashed, _) |
-        (_, BorderStyle::Dashed) => BorderCornerKind::Clip(BorderCornerInstance::Single),
-
-        // Everything else can be handled by drawing the corner twice,
-        // where the shader outputs zero alpha for the side it's not
-        // drawing. This is somewhat inefficient in terms of pixels
-        // written, but it's a fairly rare case, and we can optimize
-        // this case later.
-        _ => BorderCornerKind::Clip(BorderCornerInstance::Double),
-    }
-}
-
-fn get_edge(edge: &BorderSide, width: f32, height: f32) -> (BorderEdgeKind, f32) {
-    if width == 0.0 || height <= 0.0 {
-        return (BorderEdgeKind::None, 0.0);
-    }
-
-    match edge.style {
-        BorderStyle::None | BorderStyle::Hidden => (BorderEdgeKind::None, 0.0),
-
-        BorderStyle::Solid | BorderStyle::Inset | BorderStyle::Outset => {
-            (BorderEdgeKind::Solid, width)
-        }
-
-        BorderStyle::Double |
-        BorderStyle::Groove |
-        BorderStyle::Ridge |
-        BorderStyle::Dashed |
-        BorderStyle::Dotted => (BorderEdgeKind::Clip, width),
-    }
-}
-
 pub fn ensure_no_corner_overlap(
     radius: &mut BorderRadius,
     rect: &LayoutRect,
 ) {
     let mut ratio = 1.0;
     let top_left_radius = &mut radius.top_left;
     let top_right_radius = &mut radius.top_right;
     let bottom_right_radius = &mut radius.bottom_right;
@@ -368,354 +144,53 @@ pub fn ensure_no_corner_overlap(
         bottom_left_radius.height *= ratio;
 
         bottom_right_radius.width *= ratio;
         bottom_right_radius.height *= ratio;
     }
 }
 
 impl<'a> DisplayListFlattener<'a> {
-    fn add_normal_border_primitive(
-        &mut self,
-        info: &LayoutPrimitiveInfo,
-        border: &NormalBorder,
-        radius: &BorderRadius,
-        widths: &BorderWidths,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        corner_instances: [BorderCornerInstance; 4],
-        edges: [BorderEdgeKind; 4],
-        clip_sources: Vec<ClipSource>,
-    ) {
-        let left = &border.left;
-        let right = &border.right;
-        let top = &border.top;
-        let bottom = &border.bottom;
-
-        // These colors are used during inset/outset scaling.
-        let left_color = left.border_color(1.0, 2.0 / 3.0, 0.3, 0.7).premultiplied();
-        let top_color = top.border_color(1.0, 2.0 / 3.0, 0.3, 0.7).premultiplied();
-        let right_color = right.border_color(2.0 / 3.0, 1.0, 0.7, 0.3).premultiplied();
-        let bottom_color = bottom.border_color(2.0 / 3.0, 1.0, 0.7, 0.3).premultiplied();
-
-        let prim_cpu = BorderPrimitiveCpu {
-            corner_instances,
-            edges,
-
-            // TODO(gw): In the future, we will build these on demand
-            //           from the deserialized display list, rather
-            //           than creating it immediately.
-            gpu_blocks: [
-                [
-                    pack_as_float(left.style as u32),
-                    pack_as_float(top.style as u32),
-                    pack_as_float(right.style as u32),
-                    pack_as_float(bottom.style as u32),
-                ].into(),
-                [widths.left, widths.top, widths.right, widths.bottom].into(),
-                left_color.into(),
-                top_color.into(),
-                right_color.into(),
-                bottom_color.into(),
-                [
-                    radius.top_left.width,
-                    radius.top_left.height,
-                    radius.top_right.width,
-                    radius.top_right.height,
-                ].into(),
-                [
-                    radius.bottom_right.width,
-                    radius.bottom_right.height,
-                    radius.bottom_left.width,
-                    radius.bottom_left.height,
-                ].into(),
-            ],
-        };
-
-        self.add_primitive(
-            clip_and_scroll,
-            info,
-            clip_sources,
-            PrimitiveContainer::Border(prim_cpu),
-        );
-    }
-
-    // TODO(gw): This allows us to move border types over to the
-    // simplified shader model one at a time. Once all borders
-    // are converted, this can be removed, along with the complex
-    // border code path.
     pub fn add_normal_border(
         &mut self,
         info: &LayoutPrimitiveInfo,
         border: &NormalBorder,
         widths: &BorderWidths,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
-        // The border shader is quite expensive. For simple borders, we can just draw
-        // the border with a few rectangles. This generally gives better batching, and
-        // a GPU win in fragment shader time.
-        // More importantly, the software (OSMesa) implementation we run tests on is
-        // particularly slow at running our complex border shader, compared to the
-        // rectangle shader. This has the effect of making some of our tests time
-        // out more often on CI (the actual cause is simply too many Servo processes and
-        // threads being run on CI at once).
-
         let mut border = *border;
         ensure_no_corner_overlap(&mut border.radius, &info.rect);
 
-        let radius = &border.radius;
-        let left = &border.left;
-        let right = &border.right;
-        let top = &border.top;
-        let bottom = &border.bottom;
-
-        let brush_border_supported = [left, top, right, bottom].iter().all(|edge| {
-            match edge.style {
-                BorderStyle::Solid |
-                BorderStyle::Hidden |
-                BorderStyle::None |
-                BorderStyle::Double |
-                BorderStyle::Inset |
-                BorderStyle::Groove |
-                BorderStyle::Ridge |
-                BorderStyle::Outset => {
-                    true
-                }
-
-                BorderStyle::Dotted |
-                BorderStyle::Dashed => {
-                    false
-                }
-            }
-        });
-
-        if brush_border_supported {
-            let prim = BrushPrimitive::new(
-                BrushKind::Border {
-                    source: BorderSource::Border {
-                        border,
-                        widths: *widths,
-                        cache_key: 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::from_f32_px(0.0),
-                        },
-                        task_info: None,
-                        handle: None,
+        let prim = BrushPrimitive::new(
+            BrushKind::Border {
+                source: BorderSource::Border {
+                    border,
+                    widths: *widths,
+                    cache_key: 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::from_f32_px(0.0),
                     },
+                    task_info: None,
+                    handle: None,
                 },
-                None,
-            );
-
-            self.add_primitive(
-                clip_and_scroll,
-                info,
-                Vec::new(),
-                PrimitiveContainer::Brush(prim),
-            );
-            return;
-        }
-
-        let corners = [
-            get_corner(
-                left,
-                widths.left,
-                top,
-                widths.top,
-                &radius.top_left,
-                BorderCorner::TopLeft,
-                &info.rect,
-            ),
-            get_corner(
-                right,
-                widths.right,
-                top,
-                widths.top,
-                &radius.top_right,
-                BorderCorner::TopRight,
-                &info.rect,
-            ),
-            get_corner(
-                right,
-                widths.right,
-                bottom,
-                widths.bottom,
-                &radius.bottom_right,
-                BorderCorner::BottomRight,
-                &info.rect,
-            ),
-            get_corner(
-                left,
-                widths.left,
-                bottom,
-                widths.bottom,
-                &radius.bottom_left,
-                BorderCorner::BottomLeft,
-                &info.rect,
-            ),
-        ];
-
-        let (left_edge, left_len) = get_edge(left, widths.left,
-            info.rect.size.height - radius.top_left.height - radius.bottom_left.height);
-        let (top_edge, top_len) = get_edge(top, widths.top,
-            info.rect.size.width - radius.top_left.width - radius.top_right.width);
-        let (right_edge, right_len) = get_edge(right, widths.right,
-            info.rect.size.height - radius.top_right.height - radius.bottom_right.height);
-        let (bottom_edge, bottom_len) = get_edge(bottom, widths.bottom,
-            info.rect.size.width - radius.bottom_right.width - radius.bottom_left.width);
-
-        let edges = [left_edge, top_edge, right_edge, bottom_edge];
-
-        // Use a simple rectangle case when all edges and corners are either
-        // solid or none.
-        let all_corners_simple = corners.iter().all(|c| {
-            *c == BorderCornerKind::Solid || *c == BorderCornerKind::None
-        });
-        let all_edges_simple = edges.iter().all(|e| {
-            *e == BorderEdgeKind::Solid || *e == BorderEdgeKind::None
-        });
-
-        let has_no_curve = radius.is_zero();
+            },
+            None,
+        );
 
-        if has_no_curve && all_corners_simple && all_edges_simple {
-            let p0 = info.rect.origin;
-            let p1 = LayoutPoint::new(
-                info.rect.origin.x + left_len,
-                info.rect.origin.y + top_len,
-            );
-            let p2 = LayoutPoint::new(
-                info.rect.origin.x + info.rect.size.width - right_len,
-                info.rect.origin.y + info.rect.size.height - bottom_len,
-            );
-            let p3 = info.rect.bottom_right();
-
-            let segment = |x0, y0, x1, y1| BrushSegment::new(
-                LayoutRect::from_floats(x0, y0, x1, y1),
-                true,
-                EdgeAaSegmentMask::all(), // Note: this doesn't seem right, needs revision
-                [0.0; 4],
-                BrushFlags::empty(),
-            );
-
-            // Add a solid rectangle for each visible edge/corner combination.
-            if top_edge == BorderEdgeKind::Solid {
-                let descriptor = BrushSegmentDescriptor {
-                    segments: vec![
-                        segment(p0.x, p0.y, p1.x, p1.y),
-                        segment(p2.x, p0.y, p3.x, p1.y),
-                        segment(p1.x, p0.y, p2.x, p1.y),
-                    ],
-                    clip_mask_kind: BrushClipMaskKind::Unknown,
-                };
-
-                self.add_solid_rectangle(
-                    clip_and_scroll,
-                    info,
-                    border.top.color,
-                    Some(descriptor),
-                    Vec::new(),
-                );
-            }
-
-            if left_edge == BorderEdgeKind::Solid {
-                let descriptor = BrushSegmentDescriptor {
-                    segments: vec![
-                        segment(p0.x, p1.y, p1.x, p2.y),
-                    ],
-                    clip_mask_kind: BrushClipMaskKind::Unknown,
-                };
-
-                self.add_solid_rectangle(
-                    clip_and_scroll,
-                    info,
-                    border.left.color,
-                    Some(descriptor),
-                    Vec::new(),
-                );
-            }
-
-            if right_edge == BorderEdgeKind::Solid {
-                let descriptor = BrushSegmentDescriptor {
-                    segments: vec![
-                        segment(p2.x, p1.y, p3.x, p2.y),
-                    ],
-                    clip_mask_kind: BrushClipMaskKind::Unknown,
-                };
-
-                self.add_solid_rectangle(
-                    clip_and_scroll,
-                    info,
-                    border.right.color,
-                    Some(descriptor),
-                    Vec::new(),
-                );
-            }
-
-            if bottom_edge == BorderEdgeKind::Solid {
-                let descriptor = BrushSegmentDescriptor {
-                    segments: vec![
-                        segment(p1.x, p2.y, p2.x, p3.y),
-                        segment(p2.x, p2.y, p3.x, p3.y),
-                        segment(p0.x, p2.y, p1.x, p3.y),
-                    ],
-                    clip_mask_kind: BrushClipMaskKind::Unknown,
-                };
-
-                self.add_solid_rectangle(
-                    clip_and_scroll,
-                    info,
-                    border.bottom.color,
-                    Some(descriptor),
-                    Vec::new(),
-                );
-            }
-        } else {
-            // Create clip masks for border corners, if required.
-            let mut extra_clips = Vec::new();
-            let mut corner_instances = [BorderCornerInstance::Single; 4];
-
-            let radius = &border.radius;
-            let radius = BorderRadius {
-                top_left: corners[0].get_radius(&radius.top_left),
-                top_right: corners[1].get_radius(&radius.top_right),
-                bottom_right: corners[2].get_radius(&radius.bottom_right),
-                bottom_left: corners[3].get_radius(&radius.bottom_left),
-            };
-
-            for (i, corner) in corners.iter().enumerate() {
-                match *corner {
-                    BorderCornerKind::Mask(corner_data, mut corner_radius, widths, kind) => {
-                        let clip_source =
-                            BorderCornerClipSource::new(corner_data, corner_radius, widths, kind);
-                        extra_clips.push(ClipSource::BorderCorner(clip_source));
-                    }
-                    BorderCornerKind::Clip(instance_kind) => {
-                        corner_instances[i] = instance_kind;
-                    }
-                    BorderCornerKind::Solid => {}
-                    BorderCornerKind::None => {
-                        corner_instances[i] = BorderCornerInstance::None;
-                    }
-                }
-            }
-
-            self.add_normal_border_primitive(
-                info,
-                &border,
-                &radius,
-                widths,
-                clip_and_scroll,
-                corner_instances,
-                edges,
-                extra_clips,
-            );
-        }
+        self.add_primitive(
+            clip_and_scroll,
+            info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(
         &self,
         scale_factor_0: f32,
         scale_factor_1: f32,
@@ -751,36 +226,34 @@ impl BorderSideHelpers for BorderSide {
         }
     }
 }
 
 /// The kind of border corner clip.
 #[repr(C)]
 #[derive(Copy, Debug, Clone, PartialEq)]
 pub enum BorderCornerClipKind {
-    Dash,
-    Dot,
+    Dash = 1,
+    Dot = 2,
 }
 
 /// The source data for a border corner clip mask.
 #[derive(Debug, Clone)]
 pub struct BorderCornerClipSource {
-    pub corner_data: BorderCornerClipData,
     pub max_clip_count: usize,
     kind: BorderCornerClipKind,
-    widths: LayoutSize,
-    ellipse: Ellipse,
-    pub dot_dash_data: Vec<[f32; 8]>,
+    widths: DeviceSize,
+    radius: DeviceSize,
+    ellipse: Ellipse<DevicePixel>,
 }
 
 impl BorderCornerClipSource {
     pub fn new(
-        corner_data: BorderCornerClipData,
-        corner_radius: LayoutSize,
-        widths: LayoutSize,
+        corner_radius: DeviceSize,
+        widths: DeviceSize,
         kind: BorderCornerClipKind,
     ) -> BorderCornerClipSource {
         // Work out a dash length (and therefore dash count)
         // based on the width of the border edges. The "correct"
         // dash length is not mentioned in the CSS borders
         // spec. The calculation below is similar, but not exactly
         // the same as what Gecko uses.
         // TODO(gw): Iterate on this to get it closer to what Gecko
@@ -797,17 +270,17 @@ impl BorderCornerClipSource {
                 // Get the ideal number of dashes for that arc length.
                 // This is scaled by 0.5 since there is an on/off length
                 // for each dash.
                 let desired_count = 0.5 * ellipse.total_arc_length / desired_dash_arc_length;
 
                 // Round that up to the nearest integer, so that the dash length
                 // doesn't exceed the ratio above. Add one extra dash to cover
                 // the last half-dash of the arc.
-                (ellipse, 1 + desired_count.ceil() as usize)
+                (ellipse, desired_count.ceil() as usize)
             }
             BorderCornerClipKind::Dot => {
                 let mut corner_radius = corner_radius;
                 if corner_radius.width < (widths.width / 2.0) {
                     corner_radius.width = 0.0;
                 }
                 if corner_radius.height < (widths.height / 2.0) {
                     corner_radius.height = 0.0;
@@ -827,80 +300,130 @@ impl BorderCornerClipSource {
                     let min_diameter = widths.width.min(widths.height);
 
                     // Get the number of circles (assuming spacing of one diameter
                     // between dots).
                     let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
 
                     // Add space for one extra dot since they are centered at the
                     // start of the arc.
-                    (ellipse, 1 + max_dot_count.ceil() as usize)
+                    (ellipse, max_dot_count.ceil() as usize)
                 }
             }
         };
 
         BorderCornerClipSource {
             kind,
-            corner_data,
             max_clip_count,
             ellipse,
             widths,
-            dot_dash_data: Vec::new(),
+            radius: corner_radius,
         }
     }
 
-    pub fn write(&mut self, mut request: GpuDataRequest) {
-        self.corner_data.write(&mut request);
-        assert_eq!(request.close(), 2);
+    // TODO(gw): The naming and structure of BorderCornerClipSource
+    //           don't really make sense. I've left it this way
+    //           for now in order to reduce the size of the
+    //           patch a bit. In the future, when we spent some
+    //           time working on dot/dash placement, we should
+    //           restructure this code to be more consistent
+    //           with how border rendering works now.
+    pub fn write(self, segment: BorderSegment) -> Vec<[f32; 8]> {
+        let mut dot_dash_data = Vec::new();
+
+        let outer_scale = match segment {
+            BorderSegment::TopLeft => DeviceVector2D::new(0.0, 0.0),
+            BorderSegment::TopRight => DeviceVector2D::new(1.0, 0.0),
+            BorderSegment::BottomRight => DeviceVector2D::new(1.0, 1.0),
+            BorderSegment::BottomLeft => DeviceVector2D::new(0.0, 1.0),
+            _ => unreachable!(),
+        };
+        let outer = DevicePoint::new(
+            outer_scale.x * self.radius.width,
+            outer_scale.y * self.radius.height,
+        );
+        let clip_sign = DeviceVector2D::new(
+            1.0 - 2.0 * outer_scale.x,
+            1.0 - 2.0 * outer_scale.y,
+        );
 
         match self.kind {
             BorderCornerClipKind::Dash => {
                 // Get the correct dash arc length.
                 let dash_arc_length =
-                    0.5 * self.ellipse.total_arc_length / (self.max_clip_count - 1) as f32;
-                self.dot_dash_data.clear();
-                let mut current_arc_length = -0.5 * dash_arc_length;
+                    0.5 * self.ellipse.total_arc_length / self.max_clip_count as f32;
+                // Start the first dash at one quarter the length of a single dash
+                // along the arc line. This is arbitrary but looks reasonable in
+                // most cases. We need to spend some time working on a more
+                // sophisticated dash placement algorithm that takes into account
+                // the offset of the dashes along edge segments.
+                let mut current_arc_length = 0.25 * dash_arc_length;
                 for _ in 0 .. self.max_clip_count {
                     let arc_length0 = current_arc_length;
                     current_arc_length += dash_arc_length;
 
                     let arc_length1 = current_arc_length;
                     current_arc_length += dash_arc_length;
 
                     let alpha = self.ellipse.find_angle_for_arc_length(arc_length0);
                     let beta =  self.ellipse.find_angle_for_arc_length(arc_length1);
 
                     let (point0, tangent0) =  self.ellipse.get_point_and_tangent(alpha);
                     let (point1, tangent1) =  self.ellipse.get_point_and_tangent(beta);
 
-                    self.dot_dash_data.push([
-                        point0.x, point0.y, tangent0.x, tangent0.y,
-                        point1.x, point1.y, tangent1.x, tangent1.y
+                    let point0 = DevicePoint::new(
+                        outer.x + clip_sign.x * (self.radius.width - point0.x),
+                        outer.y + clip_sign.y * (self.radius.height - point0.y),
+                    );
+
+                    let tangent0 = DeviceVector2D::new(
+                        -tangent0.x * clip_sign.x,
+                        -tangent0.y * clip_sign.y,
+                    );
+
+                    let point1 = DevicePoint::new(
+                        outer.x + clip_sign.x * (self.radius.width - point1.x),
+                        outer.y + clip_sign.y * (self.radius.height - point1.y),
+                    );
+
+                    let tangent1 = DeviceVector2D::new(
+                        -tangent1.x * clip_sign.x,
+                        -tangent1.y * clip_sign.y,
+                    );
+
+                    dot_dash_data.push([
+                        point0.x,
+                        point0.y,
+                        tangent0.x,
+                        tangent0.y,
+                        point1.x,
+                        point1.y,
+                        tangent1.x,
+                        tangent1.y,
                     ]);
                 }
             }
             BorderCornerClipKind::Dot if self.max_clip_count == 1 => {
                 let dot_diameter = lerp(self.widths.width, self.widths.height, 0.5);
-                self.dot_dash_data.clear();
-                self.dot_dash_data.push([
+                dot_dash_data.push([
                     self.widths.width / 2.0, self.widths.height / 2.0, 0.5 * dot_diameter, 0.,
                     0., 0., 0., 0.,
                 ]);
             }
             BorderCornerClipKind::Dot => {
                 let mut forward_dots = Vec::new();
                 let mut back_dots = Vec::new();
                 let mut leftover_arc_length = 0.0;
 
                 // Alternate between adding dots at the start and end of the
                 // ellipse arc. This ensures that we always end up with an exact
                 // half dot at each end of the arc, to match up with the edges.
-                forward_dots.push(DotInfo::new(0.0, self.widths.width));
+                forward_dots.push(DotInfo::new(self.widths.width, self.widths.width));
                 back_dots.push(DotInfo::new(
-                    self.ellipse.total_arc_length,
+                    self.ellipse.total_arc_length - self.widths.height,
                     self.widths.height,
                 ));
 
                 for dot_index in 0 .. self.max_clip_count {
                     let prev_forward_pos = *forward_dots.last().unwrap();
                     let prev_back_pos = *back_dots.last().unwrap();
 
                     // Select which end of the arc to place a dot from.
@@ -942,69 +465,46 @@ impl BorderCornerClipSource {
 
                 // Now step through the dots, and distribute any extra
                 // leftover space on the arc between them evenly. Once
                 // the final arc position is determined, generate the correct
                 // arc positions and angles that get passed to the clip shader.
                 let number_of_dots = forward_dots.len() + back_dots.len();
                 let extra_space_per_dot = leftover_arc_length / (number_of_dots - 1) as f32;
 
-                self.dot_dash_data.clear();
-
-                let create_dot_data = |ellipse: &Ellipse, arc_length: f32, radius: f32| -> [f32; 8] {
+                let create_dot_data = |ellipse: &Ellipse<DevicePixel>, arc_length: f32, radius: f32| -> [f32; 8] {
                     // Represents the GPU data for drawing a single dot to a clip mask. The order
                     // these are specified must stay in sync with the way this data is read in the
                     // dot clip shader.
                     let theta = ellipse.find_angle_for_arc_length(arc_length);
                     let (center, _) = ellipse.get_point_and_tangent(theta);
-                    [center.x, center.y, radius, 0., 0., 0., 0., 0.,]
+
+                    let center = DevicePoint::new(
+                        outer.x + clip_sign.x * (self.radius.width - center.x),
+                        outer.y + clip_sign.y * (self.radius.height - center.y),
+                    );
+
+                    [center.x, center.y, radius, 0.0, 0.0, 0.0, 0.0, 0.0]
                 };
 
                 for (i, dot) in forward_dots.iter().enumerate() {
                     let extra_dist = i as f32 * extra_space_per_dot;
                     let dot_data = create_dot_data(&self.ellipse, dot.arc_pos + extra_dist, 0.5 * dot.diameter);
-                    self.dot_dash_data.push(dot_data);
+                    dot_dash_data.push(dot_data);
                 }
 
                 for (i, dot) in back_dots.iter().enumerate() {
                     let extra_dist = i as f32 * extra_space_per_dot;
                     let dot_data = create_dot_data(&self.ellipse, dot.arc_pos - extra_dist, 0.5 * dot.diameter);
-                    self.dot_dash_data.push(dot_data);
+                    dot_dash_data.push(dot_data);
                 }
             }
         }
-    }
-}
 
-/// Represents the common GPU data for writing a
-/// clip mask for a border corner.
-#[derive(Debug, Copy, Clone, PartialEq)]
-#[repr(C)]
-pub struct BorderCornerClipData {
-    /// Local space rect of the border corner.
-    corner_rect: LayoutRect,
-    /// Local space point that is the center of the
-    /// circle or ellipse that we are clipping against.
-    clip_center: LayoutPoint,
-    /// The shader needs to know which corner, to
-    /// be able to flip the dash tangents to the
-    /// right orientation.
-    corner: f32, // Of type BorderCorner enum
-    kind: f32, // Of type BorderCornerClipKind enum
-}
-
-impl BorderCornerClipData {
-    fn write(&self, request: &mut GpuDataRequest) {
-        request.push(self.corner_rect);
-        request.push([
-            self.clip_center.x,
-            self.clip_center.y,
-            self.corner,
-            self.kind,
-        ]);
+        dot_dash_data
     }
 }
 
 #[derive(Copy, Clone, Debug)]
 struct DotInfo {
     arc_pos: f32,
     diameter: f32,
 }
@@ -1024,16 +524,84 @@ pub struct BorderSegmentInfo {
 }
 
 #[derive(Debug)]
 pub struct BorderRenderTaskInfo {
     pub border_segments: Vec<BorderSegmentInfo>,
     pub size: DeviceIntSize,
 }
 
+// Information needed to place and draw a border edge.
+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,
+}
+
+impl EdgeInfo {
+    fn new(
+        local_offset: f32,
+        local_size: f32,
+        device_size: f32,
+    ) -> EdgeInfo {
+        EdgeInfo {
+            local_offset,
+            local_size,
+            device_size,
+        }
+    }
+}
+
+// Get the needed size in device pixels for an edge,
+// based on the border style of that edge. This is used
+// to determine how big the render task should be.
+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);
+    }
+
+    match style {
+        BorderStyle::Dashed => {
+            let dash_size = 3.0 * side_width;
+            let approx_dash_count = (avail_size - dash_size) / dash_size;
+            let dash_count = 1.0 + 2.0 * (approx_dash_count / 2.0).floor();
+            let used_size = dash_count * dash_size;
+            let extra_space = avail_size - used_size;
+            let device_size = 2.0 * dash_size * scale;
+            let offset = (extra_space * 0.5).round();
+            EdgeInfo::new(offset, used_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);
+            }
+            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)
+        }
+        _ => {
+            EdgeInfo::new(0.0, avail_size, 8.0)
+        }
+    }
+}
+
 impl BorderRenderTaskInfo {
     pub fn new(
         rect: &LayoutRect,
         border: &NormalBorder,
         widths: &BorderWidths,
         scale: LayoutToDeviceScale,
         brush_segments: &mut Vec<BrushSegment>,
     ) -> Self {
@@ -1078,102 +646,123 @@ impl BorderRenderTaskInfo {
             border.radius.bottom_right.width.max(widths.right),
             border.radius.bottom_right.height.max(widths.bottom),
         );
         let local_size_bl = LayoutSize::new(
             border.radius.bottom_left.width.max(widths.left),
             border.radius.bottom_left.height.max(widths.bottom),
         );
 
-        // TODO(gw): The inner and outer widths don't matter for simple
-        //           border types. Once we push dashing and dotted styles
-        //           through border brushes, we need to calculate an
-        //           appropriate length here.
-        let width_inner = 16.0;
-        let height_inner = 16.0;
+        let top_edge_info = get_edge_info(
+            border.top.style,
+            widths.top,
+            rect.size.width - local_size_tl.width - local_size_tr.width,
+            scale.0,
+        );
+        let bottom_edge_info = get_edge_info(
+            border.bottom.style,
+            widths.bottom,
+            rect.size.width - local_size_bl.width - local_size_br.width,
+            scale.0,
+        );
+        let inner_width = top_edge_info.device_size.max(bottom_edge_info.device_size).ceil();
+
+        let left_edge_info = get_edge_info(
+            border.left.style,
+            widths.left,
+            rect.size.height - local_size_tl.height - local_size_bl.height,
+            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) + width_inner + dp_size_tr.width.max(dp_size_br.width),
-            dp_size_tl.height.max(dp_size_tr.height) + height_inner + dp_size_bl.height.max(dp_size_br.height),
+            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),
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x,
-                rect.origin.y + local_size_tl.height,
+                rect.origin.y + local_size_tl.height + left_edge_info.local_offset,
                 rect.origin.x + widths.left,
-                rect.origin.y + rect.size.height - local_size_bl.height,
+                rect.origin.y + local_size_tl.height + left_edge_info.local_offset + left_edge_info.local_size,
             ),
             DeviceRect::from_floats(
                 0.0,
                 dp_size_tl.height,
                 dp_width_left,
-                size.height - dp_size_bl.height,
+                dp_size_tl.height + left_edge_info.device_size,
             ),
             &border.left,
             BorderSegment::Left,
             EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
             &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
             brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
-                rect.origin.x + local_size_tl.width,
+                rect.origin.x + local_size_tl.width + top_edge_info.local_offset,
                 rect.origin.y,
-                rect.origin.x + rect.size.width - local_size_tr.width,
+                rect.origin.x + local_size_tl.width + top_edge_info.local_offset + top_edge_info.local_size,
                 rect.origin.y + widths.top,
             ),
             DeviceRect::from_floats(
                 dp_size_tl.width,
                 0.0,
-                size.width - dp_size_tr.width,
+                dp_size_tl.width + top_edge_info.device_size,
                 dp_width_top,
             ),
             &border.top,
             BorderSegment::Top,
             EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
             &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
             brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
                 rect.origin.x + rect.size.width - widths.right,
-                rect.origin.y + local_size_tr.height,
+                rect.origin.y + local_size_tr.height + right_edge_info.local_offset,
                 rect.origin.x + rect.size.width,
-                rect.origin.y + rect.size.height - local_size_br.height,
+                rect.origin.y + local_size_tr.height + right_edge_info.local_offset + right_edge_info.local_size,
             ),
             DeviceRect::from_floats(
                 size.width - dp_width_right,
                 dp_size_tr.height,
                 size.width,
-                size.height - dp_size_br.height,
+                dp_size_tr.height + right_edge_info.device_size,
             ),
             &border.right,
             BorderSegment::Right,
             EdgeAaSegmentMask::RIGHT | EdgeAaSegmentMask::LEFT,
             &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_Y,
             brush_segments,
         );
 
         add_edge_segment(
             LayoutRect::from_floats(
-                rect.origin.x + local_size_bl.width,
+                rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset,
                 rect.origin.y + rect.size.height - widths.bottom,
-                rect.origin.x + rect.size.width - local_size_br.width,
+                rect.origin.x + local_size_bl.width + bottom_edge_info.local_offset + bottom_edge_info.local_size,
                 rect.origin.y + rect.size.height,
             ),
             DeviceRect::from_floats(
                 dp_size_bl.width,
                 size.height - dp_width_bottom,
-                size.width - dp_size_br.width,
+                dp_size_bl.width + bottom_edge_info.device_size,
                 size.height,
             ),
             &border.bottom,
             BorderSegment::Bottom,
             EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::TOP,
             &mut border_segments,
             BrushFlags::SEGMENT_RELATIVE | BrushFlags::SEGMENT_REPEAT_X,
             brush_segments,
@@ -1272,20 +861,17 @@ impl BorderRenderTaskInfo {
         );
 
         BorderRenderTaskInfo {
             border_segments,
             size: size.to_i32(),
         }
     }
 
-    pub fn build_instances(
-        &self,
-        border: &NormalBorder,
-    ) -> Vec<BorderInstance> {
+    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),
                 BorderSegment::Bottom => (&border.bottom, &border.bottom, true, true),
@@ -1368,31 +954,139 @@ fn add_segment(
     style1: BorderStyle,
     color0: ColorF,
     color1: ColorF,
     segment: BorderSegment,
     instances: &mut Vec<BorderInstance>,
     widths: DeviceSize,
     radius: DeviceSize,
 ) {
-    let flags = (segment as i32) |
-                ((style0 as i32) << 8) |
-                ((style1 as i32) << 16);
+    let base_flags = (segment as i32) |
+                     ((style0 as i32) << 8) |
+                     ((style1 as i32) << 16);
 
     let base_instance = BorderInstance {
         task_origin: DevicePoint::zero(),
         local_rect: task_rect,
-        flags,
+        flags: base_flags,
         color0: color0.premultiplied(),
         color1: color1.premultiplied(),
         widths,
         radius,
+        clip_params: [0.0; 8],
     };
 
-    instances.push(base_instance);
+    match segment {
+        BorderSegment::TopLeft |
+        BorderSegment::TopRight |
+        BorderSegment::BottomLeft |
+        BorderSegment::BottomRight => {
+            // TODO(gw): Similarly to the old border code, we don't correctly handle a a corner
+            //           that is dashed on one edge, and dotted on another. We can handle this
+            //           in the future by submitting two instances, each one with one side
+            //           color set to have an alpha of 0.
+            if (style0 == BorderStyle::Dotted && style1 == BorderStyle::Dashed) ||
+               (style0 == BorderStyle::Dashed && style0 == BorderStyle::Dotted) {
+                warn!("TODO: Handle a corner with dotted / dashed transition.");
+            }
+
+            let clip_kind = match style0 {
+                BorderStyle::Dashed => Some(BorderCornerClipKind::Dash),
+                BorderStyle::Dotted => Some(BorderCornerClipKind::Dot),
+                _ => None,
+            };
+
+            match clip_kind {
+                Some(clip_kind) => {
+                    let clip_source = BorderCornerClipSource::new(
+                        radius,
+                        widths,
+                        clip_kind,
+                    );
+
+                    // TODO(gw): Restructure the BorderCornerClipSource code
+                    //           so that we don't allocate a Vec here.
+                    let clip_list = clip_source.write(segment);
+
+                    for params in clip_list {
+                        instances.push(BorderInstance {
+                            flags: base_flags | ((clip_kind as i32) << 24),
+                            clip_params: params,
+                            ..base_instance
+                        });
+                    }
+                }
+                None => {
+                    instances.push(base_instance);
+                }
+            }
+        }
+        BorderSegment::Top |
+        BorderSegment::Bottom |
+        BorderSegment::Right |
+        BorderSegment::Left => {
+            let is_vertical = segment == BorderSegment::Left ||
+                              segment == BorderSegment::Right;
+
+            match style0 {
+                BorderStyle::Dashed => {
+                    let rect = if is_vertical {
+                        let half_dash_size = task_rect.size.height * 0.5;
+                        let y0 = task_rect.origin.y;
+                        let y1 = y0 + half_dash_size.round();
+
+                        DeviceRect::from_floats(
+                            task_rect.origin.x,
+                            y0,
+                            task_rect.origin.x + task_rect.size.width,
+                            y1,
+                        )
+                    } else {
+                        let half_dash_size = task_rect.size.width * 0.5;
+                        let x0 = task_rect.origin.x;
+                        let x1 = x0 + half_dash_size.round();
+
+                        DeviceRect::from_floats(
+                            x0,
+                            task_rect.origin.y,
+                            x1,
+                            task_rect.origin.y + task_rect.size.height,
+                        )
+                    };
+
+                    instances.push(BorderInstance {
+                        local_rect: rect,
+                        ..base_instance
+                    });
+                }
+                BorderStyle::Dotted => {
+                    let (x, y, r) = if is_vertical {
+                        (widths.width * 0.5,
+                         widths.width,
+                         widths.width * 0.5)
+                    } else {
+                        (widths.height,
+                         widths.height * 0.5,
+                         widths.height * 0.5)
+                    };
+
+                    instances.push(BorderInstance {
+                        flags: base_flags | ((BorderCornerClipKind::Dot as i32) << 24),
+                        clip_params: [
+                            x, y, r, 0.0, 0.0, 0.0, 0.0, 0.0,
+                        ],
+                        ..base_instance
+                    });
+                }
+                _ => {
+                    instances.push(base_instance);
+                }
+            }
+        }
+    }
 }
 
 fn add_corner_segment(
     image_rect: LayoutRect,
     task_rect: DeviceRect,
     side0: &BorderSide,
     side1: &BorderSide,
     widths: DeviceSize,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,16 +1,16 @@
 /* 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, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle};
-use border::{BorderCornerClipSource, ensure_no_corner_overlap};
+use border::{ensure_no_corner_overlap};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode, ClipScrollNodeIndex};
 use prim_store::{ClipData, ImageMaskData};
 use render_task::to_cache_size;
@@ -82,21 +82,16 @@ impl ClipRegion {
     }
 }
 
 #[derive(Debug)]
 pub enum ClipSource {
     Rectangle(LayoutRect, ClipMode),
     RoundedRectangle(LayoutRect, BorderRadius, ClipMode),
     Image(ImageMask),
-    /// TODO(gw): This currently only handles dashed style
-    /// clips, where the border style is dashed for both
-    /// adjacent border edges. Expand to handle dotted style
-    /// and different styles per edge.
-    BorderCorner(BorderCornerClipSource),
     BoxShadow(BoxShadowClipSource),
     LineDecoration(LineDecorationClipSource),
 }
 
 impl From<ClipRegion> for ClipSources {
     fn from(region: ClipRegion) -> ClipSources {
         let mut clips = Vec::new();
 
@@ -336,17 +331,16 @@ impl ClipSources {
                     can_calculate_outer_rect = true;
                     local_outer = local_outer.and_then(|r| r.intersection(rect));
 
                     let inner_rect = extract_inner_rect_safe(rect, radius);
                     local_inner = local_inner
                         .and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
                 }
                 ClipSource::BoxShadow(..) |
-                ClipSource::BorderCorner { .. } |
                 ClipSource::LineDecoration(..) => {
                     can_calculate_inner_rect = false;
                     break;
                 }
             }
         }
 
         let outer = if can_calculate_outer_rect {
@@ -395,19 +389,16 @@ impl ClipSources {
                     ClipSource::Rectangle(rect, mode) => {
                         let data = ClipData::uniform(rect, 0.0, mode);
                         data.write(&mut request);
                     }
                     ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
                         let data = ClipData::rounded_rect(rect, radius, mode);
                         data.write(&mut request);
                     }
-                    ClipSource::BorderCorner(ref mut source) => {
-                        source.write(request);
-                    }
                     ClipSource::LineDecoration(ref info) => {
                         request.push(info.rect);
                         request.push([
                             info.wavy_line_thickness,
                             pack_as_float(info.style as u32),
                             pack_as_float(info.orientation as u32),
                             0.0,
                         ]);
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -15,23 +15,24 @@ use api::{Shadow, SpecificDisplayItem, S
 use api::{TransformStyle, YuvColorSpace, YuvData};
 use app_units::Au;
 use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use euclid::{SideOffsets2D, vec2};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
+use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureCompositeMode;
-use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
-use prim_store::{CachedGradientIndex, EdgeAaSegmentMask, ImageSource};
+use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor};
+use prim_store::{EdgeAaSegmentMask, ImageSource};
 use prim_store::{BorderSource, BrushSegment, PictureIndex, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::{BuiltScene, SceneRequest};
 use std::{f32, mem, usize};
 use tiling::{CompositeOps, ScrollbarPrimitive};
@@ -184,31 +185,29 @@ pub struct DisplayListFlattener<'a> {
     pub hit_testing_runs: Vec<HitTestingRun>,
 
     /// The store which holds all complex clipping information.
     pub clip_store: ClipStore,
 
     /// The configuration to use for the FrameBuilder. We consult this in
     /// order to determine the default font.
     pub config: FrameBuilderConfig,
-
-    /// The gradients collecting during display list flattening.
-    pub cached_gradients: Vec<CachedGradient>,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         old_builder: FrameBuilder,
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
         output_pipelines: &FastHashSet<PipelineId>,
         frame_builder_config: &FrameBuilderConfig,
         new_scene: &mut Scene,
+        scene_id: u64,
     ) -> FrameBuilder {
         // We checked that the root pipeline is available on the render backend.
         let root_pipeline_id = scene.root_pipeline_id.unwrap();
         let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
 
         let root_epoch = scene.pipeline_epochs[&root_pipeline_id];
 
         let background_color = root_pipeline
@@ -219,17 +218,16 @@ impl<'a> DisplayListFlattener<'a> {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             pipeline_epochs: Vec::new(),
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
-            cached_gradients: recycle_vec(old_builder.cached_gradients),
             scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
             reference_frame_stack: Vec::new(),
             picture_stack: Vec::new(),
             shadow_stack: Vec::new(),
             sc_stack: Vec::new(),
             prim_store: old_builder.prim_store.recycle(),
             clip_store: old_builder.clip_store.recycle(),
         };
@@ -249,17 +247,18 @@ impl<'a> DisplayListFlattener<'a> {
         new_scene.pipeline_epochs.insert(root_pipeline_id, root_epoch);
         new_scene.pipeline_epochs.extend(flattener.pipeline_epochs.drain(..));
         new_scene.pipelines = scene.pipelines.clone();
 
         FrameBuilder::with_display_list_flattener(
             view.inner_rect,
             background_color,
             view.window_size,
-            flattener
+            scene_id,
+            flattener,
         )
     }
 
     fn get_complex_clips(
         &self,
         pipeline_id: PipelineId,
         complex_clips: ItemRange<ComplexClipRegion>,
     ) -> Vec<ComplexClipRegion> {
@@ -631,17 +630,16 @@ impl<'a> DisplayListFlattener<'a> {
             }
             SpecificDisplayItem::Gradient(ref info) => {
                 self.add_gradient(
                     clip_and_scroll,
                     &prim_info,
                     info.gradient.start_point,
                     info.gradient.end_point,
                     item.gradient_stops(),
-                    item.display_list().get(item.gradient_stops()).count(),
                     info.gradient.extend_mode,
                     info.tile_size,
                     info.tile_spacing,
                 );
             }
             SpecificDisplayItem::RadialGradient(ref info) => {
                 self.add_radial_gradient(
                     clip_and_scroll,
@@ -674,17 +672,16 @@ impl<'a> DisplayListFlattener<'a> {
                 );
             }
             SpecificDisplayItem::Border(ref info) => {
                 self.add_border(
                     clip_and_scroll,
                     &prim_info,
                     info,
                     item.gradient_stops(),
-                    item.display_list().get(item.gradient_stops()).count(),
                 );
             }
             SpecificDisplayItem::PushStackingContext(ref info) => {
                 let mut subtraversal = item.sub_iter();
                 self.flatten_stacking_context(
                     &mut subtraversal,
                     pipeline_id,
                     &item,
@@ -1489,17 +1486,16 @@ impl<'a> DisplayListFlattener<'a> {
     }
 
     pub fn add_border(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         border_item: &BorderDisplayItem,
         gradient_stops: ItemRange<GradientStop>,
-        gradient_stops_count: usize,
     ) {
         let rect = info.rect;
         let create_segments = |outset: SideOffsets2D<f32>| {
             // Calculate the modified rect as specific by border-image-outset
             let origin = LayoutPoint::new(rect.origin.x - outset.left, rect.origin.y - outset.top);
             let size = LayoutSize::new(
                 rect.size.width + outset.left + outset.right,
                 rect.size.height + outset.top + outset.bottom,
@@ -1734,17 +1730,16 @@ impl<'a> DisplayListFlattener<'a> {
                 info.rect = segment;
 
                 self.add_gradient(
                     clip_and_scroll,
                     &info,
                     border.gradient.start_point - segment_rel,
                     border.gradient.end_point - segment_rel,
                     gradient_stops,
-                    gradient_stops_count,
                     border.gradient.extend_mode,
                     segment.size,
                     LayoutSize::zero(),
                 );
             },
             BorderDetails::RadialGradient(ref border) => {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
@@ -1770,24 +1765,20 @@ impl<'a> DisplayListFlattener<'a> {
 
     pub fn add_gradient(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stops: ItemRange<GradientStop>,
-        stops_count: usize,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
     ) {
-        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
-        self.cached_gradients.push(CachedGradient::new());
-
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
         let info = LayoutPrimitiveInfo {
             rect: prim_rect,
             .. *info
         };
 
         // Try to ensure that if the gradient is specified in reverse, then so long as the stops
@@ -1806,22 +1797,21 @@ impl<'a> DisplayListFlattener<'a> {
             (end_point, start_point)
         } else {
             (start_point, end_point)
         };
 
         let prim = BrushPrimitive::new(
             BrushKind::LinearGradient {
                 stops_range: stops,
-                stops_count,
                 extend_mode,
                 reverse_stops,
                 start_point: sp,
                 end_point: ep,
-                gradient_index,
+                stops_handle: GpuCacheHandle::new(),
                 stretch_size,
                 tile_spacing,
                 visible_tiles: Vec::new(),
             },
             None,
         );
 
         let prim = PrimitiveContainer::Brush(prim);
@@ -1837,35 +1827,32 @@ impl<'a> DisplayListFlattener<'a> {
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
         stops: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         stretch_size: LayoutSize,
         mut tile_spacing: LayoutSize,
     ) {
-        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
-        self.cached_gradients.push(CachedGradient::new());
-
         let mut prim_rect = info.rect;
         simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
         let info = LayoutPrimitiveInfo {
             rect: prim_rect,
             .. *info
         };
 
         let prim = BrushPrimitive::new(
             BrushKind::RadialGradient {
                 stops_range: stops,
                 extend_mode,
                 center,
                 start_radius,
                 end_radius,
                 ratio_xy,
-                gradient_index,
+                stops_handle: GpuCacheHandle::new(),
                 stretch_size,
                 tile_spacing,
                 visible_tiles: Vec::new(),
             },
             None,
         );
 
         self.add_primitive(
@@ -2057,17 +2044,18 @@ pub fn build_scene(config: &FrameBuilder
     let frame_builder = DisplayListFlattener::create_frame_builder(
         FrameBuilder::empty(), // WIP, we're not really recycling anything here, clean this up.
         &request.scene,
         &mut clip_scroll_tree,
         request.font_instances,
         &request.view,
         &request.output_pipelines,
         config,
-        &mut new_scene
+        &mut new_scene,
+        request.scene_id,
     );
 
     BuiltScene {
         scene: new_scene,
         frame_builder,
         clip_scroll_tree,
         removed_pipelines: request.removed_pipelines,
     }
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.rs
@@ -1,27 +1,30 @@
 /* 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::{LayoutPoint, LayoutSize, LayoutVector2D};
+use api::{LayoutPoint, LayoutVector2D};
+use euclid::TypedSize2D;
 use std::f32::consts::FRAC_PI_2;
+#[cfg(test)]
+use api::LayoutSize;
 
 /// Number of steps to integrate arc length over.
 const STEP_COUNT: usize = 20;
 
 /// Represents an ellipse centred at a local space origin.
 #[derive(Debug, Clone)]
-pub struct Ellipse {
-    pub radius: LayoutSize,
+pub struct Ellipse<U> {
+    pub radius: TypedSize2D<f32, U>,
     pub total_arc_length: f32,
 }
 
-impl Ellipse {
-    pub fn new(radius: LayoutSize) -> Ellipse {
+impl<U> Ellipse<U> {
+    pub fn new(radius: TypedSize2D<f32, U>) -> Ellipse<U> {
         // Approximate the total length of the first quadrant of this ellipse.
         let total_arc_length = get_simpson_length(FRAC_PI_2, radius.width, radius.height);
 
         Ellipse {
             radius,
             total_arc_length,
         }
     }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -9,17 +9,17 @@ use clip::{ClipChain, ClipStore};
 use clip_scroll_node::{ClipScrollNode};
 use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
 use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, UvRectKind};
 use hit_test::{HitTester, HitTestingRun};
 use internal_types::{FastHashMap};
 use picture::PictureSurface;
-use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
+use prim_store::{PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::FrameId;
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use std::{mem, f32};
 use std::sync::Arc;
 use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext};
@@ -36,41 +36,41 @@ pub struct FrameBuilderConfig {
     pub dual_source_blending_is_enabled: bool,
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
+    scene_id: u64,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
-    pub cached_gradients: Vec<CachedGradient>,
     pub scrollbar_prims: Vec<ScrollbarPrimitive>,
 }
 
 pub struct FrameBuildingContext<'a> {
+    pub scene_id: u64,
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
     pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_rect: DeviceIntRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub node_data: &'a [ClipScrollNodeData],
 }
 
 pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub local_clip_rects: &'a mut Vec<LayoutRect>,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
-    pub cached_gradients: &'a mut [CachedGradient],
     pub special_render_passes: &'a mut SpecialRenderPasses,
 }
 
 pub struct PictureContext<'a> {
     pub pipeline_id: PipelineId,
     pub prim_runs: Vec<PrimitiveRun>,
     pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
     pub display_list: &'a BuiltDisplayList,
@@ -78,23 +78,25 @@ pub struct PictureContext<'a> {
     pub apply_local_clip_rect: bool,
     pub inflation_factor: f32,
     pub allow_subpixel_aa: bool,
 }
 
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
     pub has_non_root_coord_system: bool,
+    pub local_rect_changed: bool,
 }
 
 impl PictureState {
     pub fn new() -> PictureState {
         PictureState {
             tasks: Vec::new(),
             has_non_root_coord_system: false,
+            local_rect_changed: false,
         }
     }
 }
 
 pub struct PrimitiveRunContext<'a> {
     pub clip_chain: &'a ClipChain,
     pub scroll_node: &'a ClipScrollNode,
     pub clip_chain_rect_index: ClipChainRectIndex,
@@ -113,47 +115,48 @@ impl<'a> PrimitiveRunContext<'a> {
         }
     }
 }
 
 impl FrameBuilder {
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
-            cached_gradients: Vec::new(),
             scrollbar_prims: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
+            scene_id: 0,
             config: FrameBuilderConfig {
                 enable_scrollbars: false,
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
             },
         }
     }
 
     pub fn with_display_list_flattener(
         screen_rect: DeviceUintRect,
         background_color: Option<ColorF>,
         window_size: DeviceUintSize,
+        scene_id: u64,
         flattener: DisplayListFlattener,
     ) -> Self {
         FrameBuilder {
             hit_testing_runs: flattener.hit_testing_runs,
-            cached_gradients: flattener.cached_gradients,
             scrollbar_prims: flattener.scrollbar_prims,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
             screen_rect,
             background_color,
             window_size,
+            scene_id,
             config: flattener.config,
         }
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
@@ -180,33 +183,33 @@ impl FrameBuilder {
             &clip_scroll_tree.nodes[clip_scroll_tree.root_reference_frame_index().0];
 
         let display_list = &pipelines
             .get(&root_clip_scroll_node.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         let frame_context = FrameBuildingContext {
+            scene_id: self.scene_id,
             device_pixel_scale,
             scene_properties,
             pipelines,
             screen_rect: self.screen_rect.to_i32(),
             clip_scroll_tree,
             node_data,
         };
 
         let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             local_clip_rects,
             resource_cache,
             gpu_cache,
             special_render_passes,
-            cached_gradients: &mut self.cached_gradients,
         };
 
         let pic_context = PictureContext {
             pipeline_id: root_clip_scroll_node.pipeline_id,
             prim_runs: mem::replace(&mut self.prim_store.pictures[0].runs, Vec::new()),
             original_reference_frame_index: None,
             display_list,
             inv_world_transform: None,
@@ -372,17 +375,16 @@ impl FrameBuilder {
         for pass in &mut passes {
             let mut ctx = RenderTargetContext {
                 device_pixel_scale,
                 prim_store: &self.prim_store,
                 resource_cache,
                 clip_scroll_tree,
                 use_dual_source_blending,
                 node_data: &node_data,
-                cached_gradients: &self.cached_gradients,
             };
 
             pass.build(
                 &mut ctx,
                 gpu_cache,
                 &mut render_tasks,
                 &mut deferred_resolves,
                 &self.clip_store,
--- a/gfx/webrender/src/gpu_cache.rs
+++ b/gfx/webrender/src/gpu_cache.rs
@@ -490,21 +490,16 @@ impl<'a> GpuDataRequest<'a> {
 
     pub fn extend_from_slice(&mut self, blocks: &[GpuBlockData]) {
         self.texture.pending_blocks.extend_from_slice(blocks);
     }
 
     pub fn current_used_block_num(&self) -> usize {
         self.texture.pending_blocks.len() - self.start_index
     }
-
-    /// Consume the request and return the number of blocks written
-    pub fn close(self) -> usize {
-        self.texture.pending_blocks.len() - self.start_index
-    }
 }
 
 impl<'a> Drop for GpuDataRequest<'a> {
     fn drop(&mut self) {
         // Push the data to the texture pending updates list.
         let block_count = self.current_used_block_num();
         debug_assert!(block_count <= self.max_block_count);
 
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -75,17 +75,17 @@ pub enum BlurDirection {
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BlurInstance {
     pub task_address: RenderTaskAddress,
     pub src_task_address: RenderTaskAddress,
     pub blur_direction: BlurDirection,
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq)]
 #[repr(C)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BorderSegment {
     TopLeft,
     TopRight,
     BottomRight,
     BottomLeft,
@@ -102,16 +102,17 @@ pub enum BorderSegment {
 pub struct BorderInstance {
     pub task_origin: DevicePoint,
     pub local_rect: DeviceRect,
     pub color0: PremultipliedColorF,
     pub color1: PremultipliedColorF,
     pub flags: i32,
     pub widths: DeviceSize,
     pub radius: DeviceSize,
+    pub clip_params: [f32; 8],
 }
 
 /// A clipping primitive drawn into the clipping mask.
 /// Could be an image or a rectangle, which defines the
 /// way `address` is treated.
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -333,17 +333,16 @@ fn get_regions_for_clip_scroll_node(
     };
 
     clips.iter().map(|source| {
         match source.0 {
             ClipSource::Rectangle(ref rect, mode) => HitTestRegion::Rectangle(*rect, mode),
             ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
             ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect, ClipMode::Clip),
-            ClipSource::BorderCorner(_) |
             ClipSource::LineDecoration(_) |
             ClipSource::BoxShadow(_) => {
                 unreachable!("Didn't expect to hit test against BorderCorner / BoxShadow / LineDecoration");
             }
         }
     }).collect()
 }
 
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -127,26 +127,17 @@ impl TextureUpdateList {
     pub fn push(&mut self, update: TextureUpdate) {
         self.updates.push(update);
     }
 }
 
 /// Wraps a tiling::Frame, but conceptually could hold more information
 pub struct RenderedDocument {
     pub frame: tiling::Frame,
-}
-
-impl RenderedDocument {
-    pub fn new(
-        frame: tiling::Frame,
-    ) -> Self {
-        RenderedDocument {
-            frame,
-        }
-    }
+    pub is_new_scene: bool,
 }
 
 pub enum DebugOutput {
     FetchDocuments(String),
     FetchClipScrollTree(String),
     #[cfg(feature = "capture")]
     SaveCapture(CaptureConfig, Vec<ExternalCaptureImage>),
     #[cfg(feature = "replay")]
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -75,17 +75,21 @@ pub struct PictureCacheKey {
     //       we want the cache to remain valid as it
     //       is scrolled and/or translated by animation.
     //       This is valid while we have the restriction
     //       in place that only pictures that use the
     //       root coordinate system are cached - once
     //       we relax that, we'll need to consider some
     //       extra parameters, depending on transform.
 
-    // The unique identifier for this picture.
+    // This is a globally unique id of the scene this picture 
+    // is associated with, to avoid picture id collisions.
+    scene_id: u64,
+
+    // The unique (for the scene_id) identifier for this picture.
     // TODO(gw): Currently, these will not be
     //           shared across new display lists,
     //           so will only remain valid during
     //           scrolling. Next step will be to
     //           allow deep comparisons on pictures
     //           between display lists, allowing
     //           pictures that are the same to be
     //           cached across display lists!
@@ -369,16 +373,17 @@ impl PicturePrimitive {
                     );
 
                     // Request a render task that will cache the output in the
                     // texture cache.
                     let cache_item = frame_state.resource_cache.request_render_task(
                         RenderTaskCacheKey {
                             size: device_rect.size,
                             kind: RenderTaskCacheKeyKind::Picture(PictureCacheKey {
+                                scene_id: frame_context.scene_id,
                                 picture_id: self.id,
                                 unclipped_size: prim_screen_rect.unclipped.size,
                                 pic_relative_render_rect,
                             }),
                         },
                         frame_state.gpu_cache,
                         frame_state.render_tasks,
                         None,
@@ -462,16 +467,24 @@ impl PicturePrimitive {
                 );
 
                 self.secondary_render_task_id = Some(picture_task_id);
 
                 let render_task_id = frame_state.render_tasks.add(blur_render_task);
                 pic_state.tasks.push(render_task_id);
                 self.surface = Some(PictureSurface::RenderTask(render_task_id));
 
+                // If the local rect of the contents changed, force the cache handle
+                // to be invalidated so that the primitive data below will get
+                // uploaded to the GPU this frame. This can occur during property
+                // animation.
+                if pic_state.local_rect_changed {
+                    frame_state.gpu_cache.invalidate(&mut self.extra_gpu_data_handle);
+                }
+
                 if let Some(mut request) = frame_state.gpu_cache.request(&mut self.extra_gpu_data_handle) {
                     // TODO(gw): This is very hacky code below! It stores an extra
                     //           brush primitive below for the special case of a
                     //           drop-shadow where we need a different local
                     //           rect for the shadow. To tidy this up in future,
                     //           we could consider abstracting the code in prim_store.rs
                     //           that writes a brush primitive header.
 
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -4,17 +4,17 @@
 
 use api::{AlphaType, BorderRadius, BoxShadowClipMode, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode};
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
 use api::{PipelineId, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets};
 use api::{BorderWidths, LayoutToWorldScale, NormalBorder};
 use app_units::Au;
-use border::{BorderCacheKey, BorderCornerInstance, BorderRenderTaskInfo, BorderEdgeKind};
+use border::{BorderCacheKey, BorderRenderTaskInfo};
 use box_shadow::BLUR_SAMPLE_SCALE;
 use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey};
@@ -78,31 +78,16 @@ impl PrimitiveOpacity {
 
     pub fn from_alpha(alpha: f32) -> PrimitiveOpacity {
         PrimitiveOpacity {
             is_opaque: alpha == 1.0,
         }
     }
 }
 
-#[derive(Debug, Copy, Clone)]
-pub struct CachedGradientIndex(pub usize);
-
-pub struct CachedGradient {
-    pub handle: GpuCacheHandle,
-}
-
-impl CachedGradient {
-    pub fn new() -> CachedGradient {
-        CachedGradient {
-            handle: GpuCacheHandle::new(),
-        }
-    }
-}
-
 // Represents the local space rect of a list of
 // primitive runs. For most primitive runs, the
 // primitive runs are attached to the parent they
 // are declared in. However, when a primitive run
 // is part of a 3d rendering context, it may get
 // hoisted to a higher level in the picture tree.
 // When this happens, we need to also calculate the
 // local space rects in the original space. This
@@ -145,17 +130,16 @@ pub struct PrimitiveIndex(pub usize);
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct PictureIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 pub enum PrimitiveKind {
     TextRun,
-    Border,
     Brush,
 }
 
 impl GpuCacheHandle {
     pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 {
         gpu_cache.get_address(self).as_int()
     }
 }
@@ -292,31 +276,30 @@ pub enum BrushKind {
     },
     YuvImage {
         yuv_key: [ImageKey; 3],
         format: YuvFormat,
         color_space: YuvColorSpace,
         image_rendering: ImageRendering,
     },
     RadialGradient {
-        gradient_index: CachedGradientIndex,
+        stops_handle: GpuCacheHandle,
         stops_range: ItemRange<GradientStop>,
         extend_mode: ExtendMode,
         center: LayoutPoint,
         start_radius: f32,
         end_radius: f32,
         ratio_xy: f32,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         visible_tiles: Vec<VisibleGradientTile>,
     },
     LinearGradient {
-        gradient_index: CachedGradientIndex,
+        stops_handle: GpuCacheHandle,
         stops_range: ItemRange<GradientStop>,
-        stops_count: usize,
         extend_mode: ExtendMode,
         reverse_stops: bool,
         start_point: LayoutPoint,
         end_point: LayoutPoint,
         stretch_size: LayoutSize,
         tile_spacing: LayoutSize,
         visible_tiles: Vec<VisibleGradientTile>,
     },
@@ -552,29 +535,16 @@ pub enum ImageSource {
     // An image that is pre-rendered into the texture cache
     // via a render task.
     Cache {
         size: DeviceIntSize,
         handle: Option<RenderTaskCacheEntryHandle>,
     },
 }
 
-#[derive(Debug)]
-pub struct BorderPrimitiveCpu {
-    pub corner_instances: [BorderCornerInstance; 4],
-    pub edges: [BorderEdgeKind; 4],
-    pub gpu_blocks: [GpuBlockData; 8],
-}
-
-impl ToGpuBlocks for BorderPrimitiveCpu {
-    fn write_gpu_blocks(&self, mut request: GpuDataRequest) {
-        request.extend_from_slice(&self.gpu_blocks);
-    }
-}
-
 // The gradient entry index for the first color stop
 pub const GRADIENT_DATA_FIRST_STOP: usize = 0;
 // The gradient entry index for the last color stop
 pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1;
 
 // The start of the gradient data table
 pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1;
 // The exclusive bound of the gradient data table
@@ -1080,17 +1050,16 @@ impl ClipData {
             corner.write(request);
         }
     }
 }
 
 #[derive(Debug)]
 pub enum PrimitiveContainer {
     TextRun(TextRunPrimitiveCpu),
-    Border(BorderPrimitiveCpu),
     Brush(BrushPrimitive),
 }
 
 impl PrimitiveContainer {
     // Return true if the primary primitive is visible.
     // Used to trivially reject non-visible primitives.
     // TODO(gw): Currently, primitives other than those
     //           listed here are handled before the
@@ -1113,19 +1082,16 @@ impl PrimitiveContainer {
                     BrushKind::YuvImage { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Border { .. } |
                     BrushKind::LinearGradient { .. } => {
                         true
                     }
                 }
             }
-            PrimitiveContainer::Border(..) => {
-                true
-            }
         }
     }
 
     // Create a clone of this PrimitiveContainer, applying whatever
     // changes are necessary to the primitive to support rendering
     // it as part of the supplied shadow.
     pub fn create_shadow(&self, shadow: &Shadow) -> PrimitiveContainer {
         match *self {
@@ -1161,53 +1127,47 @@ impl PrimitiveContainer {
                     BrushKind::YuvImage { .. } |
                     BrushKind::Border { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::LinearGradient { .. } => {
                         panic!("bug: other brush kinds not expected here yet");
                     }
                 }
             }
-            PrimitiveContainer::Border(..) => {
-                panic!("bug: other primitive containers not expected here");
-            }
         }
     }
 }
 
 pub struct PrimitiveStore {
     /// CPU side information only.
     pub cpu_brushes: Vec<BrushPrimitive>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
-    pub cpu_borders: Vec<BorderPrimitiveCpu>,
 
     pub pictures: Vec<PicturePrimitive>,
     next_picture_id: u64,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_brushes: Vec::new(),
             cpu_text_runs: Vec::new(),
-            cpu_borders: Vec::new(),
 
             pictures: Vec::new(),
             next_picture_id: 0,
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_brushes: recycle_vec(self.cpu_brushes),
             cpu_text_runs: recycle_vec(self.cpu_text_runs),
-            cpu_borders: recycle_vec(self.cpu_borders),
 
             pictures: recycle_vec(self.pictures),
             next_picture_id: self.next_picture_id,
         }
     }
 
     pub fn add_image_picture(
         &mut self,
@@ -1292,27 +1252,16 @@ impl PrimitiveStore {
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     ..base_metadata
                 };
 
                 self.cpu_text_runs.push(text_cpu);
                 metadata
             }
-            PrimitiveContainer::Border(border_cpu) => {
-                let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::translucent(),
-                    prim_kind: PrimitiveKind::Border,
-                    cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
-                    ..base_metadata
-                };
-
-                self.cpu_borders.push(border_cpu);
-                metadata
-            }
         };
 
         self.cpu_metadata.push(metadata);
 
         PrimitiveIndex(prim_index)
     }
 
     // Internal method that retrieves the primitive index of a primitive
@@ -1360,18 +1309,17 @@ impl PrimitiveStore {
                     }
                     BrushKind::Border { .. } |
                     BrushKind::YuvImage { .. } |
                     BrushKind::LinearGradient { .. } |
                     BrushKind::RadialGradient { .. } |
                     BrushKind::Clear => {}
                 }
             }
-            PrimitiveKind::TextRun |
-            PrimitiveKind::Border => {}
+            PrimitiveKind::TextRun => {}
         }
 
         None
     }
 
     // Apply any optimizations to drawing this picture. Currently,
     // we just support collapsing pictures with an opacity filter
     // by pushing that opacity value into the color of a primitive
@@ -1410,18 +1358,17 @@ impl PrimitiveStore {
                         BrushKind::YuvImage { .. } |
                         BrushKind::Border { .. } |
                         BrushKind::LinearGradient { .. } |
                         BrushKind::RadialGradient { .. } => {
                             unreachable!("bug: invalid prim type for opacity collapse");
                         }
                     };
                 }
-                PrimitiveKind::TextRun |
-                PrimitiveKind::Border => {
+                PrimitiveKind::TextRun => {
                     unreachable!("bug: invalid prim type for opacity collapse");
                 }
             }
 
             // The opacity filter has been collapsed, so mark this picture
             // as a pass though. This means it will no longer allocate an
             // intermediate surface or incur an extra blend / blit. Instead,
             // the collapsed primitive will be drawn directly into the
@@ -1440,17 +1387,17 @@ impl PrimitiveStore {
 
     fn build_prim_segments_if_needed(
         &mut self,
         prim_index: PrimitiveIndex,
         pic_state: &mut PictureState,
         frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
     ) {
-        let metadata = &self.cpu_metadata[prim_index.0];
+        let metadata = &mut self.cpu_metadata[prim_index.0];
 
         if metadata.prim_kind != PrimitiveKind::Brush {
             return;
         }
 
         let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0];
 
         if let BrushKind::Border { ref mut source, .. } = brush.kind {
@@ -1508,16 +1455,20 @@ impl PrimitiveStore {
                     }
                 ));
 
                 if needs_update {
                     brush.segment_desc = Some(BrushSegmentDescriptor {
                         segments: new_segments,
                         clip_mask_kind: BrushClipMaskKind::Unknown,
                     });
+
+                    // The segments have changed, so force the GPU cache to
+                    // re-upload the primitive information.
+                    frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
                 }
             }
         }
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
@@ -1531,17 +1482,16 @@ impl PrimitiveStore {
         let mut is_tiled = false;
         let metadata = &mut self.cpu_metadata[prim_index.0];
         #[cfg(debug_assertions)]
         {
             metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         match metadata.prim_kind {
-            PrimitiveKind::Border => {}
             PrimitiveKind::TextRun => {
                 let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0];
                 // The transform only makes sense for screen space rasterization
                 let transform = Some(prim_run_context.scroll_node.world_content_transform.into());
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
                     transform,
                     pic_context.allow_subpixel_aa,
@@ -1801,30 +1751,30 @@ impl PrimitiveStore {
                             }
                             BorderSource::Border { .. } => {
                                 // Handled earlier since we need to update the segment
                                 // descriptor *before* update_clip_task() is called.
                             }
                         }
                     }
                     BrushKind::RadialGradient {
-                        gradient_index,
                         stops_range,
                         center,
                         start_radius,
                         end_radius,
                         ratio_xy,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
+                        ref mut stops_handle,
                         ref mut visible_tiles,
                         ..
                     } => {
                         build_gradient_stops_request(
-                            gradient_index,
+                            stops_handle,
                             stops_range,
                             false,
                             frame_state,
                             pic_context,
                         );
 
                         if tile_spacing != LayoutSize::zero() {
                             is_tiled = true;
@@ -1853,30 +1803,30 @@ impl PrimitiveStore {
                                         stretch_size.height,
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 },
                             );
                         }
                     }
                     BrushKind::LinearGradient {
-                        gradient_index,
                         stops_range,
                         reverse_stops,
                         start_point,
                         end_point,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
+                        ref mut stops_handle,
                         ref mut visible_tiles,
                         ..
                     } => {
 
                         build_gradient_stops_request(
-                            gradient_index,
+                            stops_handle,
                             stops_range,
                             reverse_stops,
                             frame_state,
                             pic_context,
                         );
 
                         if tile_spacing != LayoutSize::zero() {
                             is_tiled = true;
@@ -1943,20 +1893,16 @@ impl PrimitiveStore {
 
         // Mark this GPU resource as required for this frame.
         if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
             // has to match VECS_PER_BRUSH_PRIM
             request.push(metadata.local_rect);
             request.push(metadata.local_clip_rect);
 
             match metadata.prim_kind {
-                PrimitiveKind::Border => {
-                    let border = &self.cpu_borders[metadata.cpu_prim_index.0];
-                    border.write_gpu_blocks(request);
-                }
                 PrimitiveKind::TextRun => {
                     let text = &self.cpu_text_runs[metadata.cpu_prim_index.0];
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveKind::Brush => {
                     let brush = &self.cpu_brushes[metadata.cpu_prim_index.0];
                     brush.write_gpu_blocks(&mut request, metadata.local_rect);
 
@@ -2073,17 +2019,16 @@ impl PrimitiveStore {
                                 -0.5 * info.shadow_rect_alloc_size.width,
                                 -0.5 * info.shadow_rect_alloc_size.height,
                             ),
                             inner_clip_mode,
                         );
 
                         continue;
                     }
-                    ClipSource::BorderCorner(..) |
                     ClipSource::LineDecoration(..) |
                     ClipSource::Image(..) => {
                         rect_clips_only = false;
 
                         // TODO(gw): We can easily extend the segment builder
                         //           to support these clip sources in the
                         //           future, but they are rarely used.
                         clip_mask_kind = BrushClipMaskKind::Global;
@@ -2468,16 +2413,17 @@ impl PrimitiveStore {
 
                 let metadata = &mut self.cpu_metadata[prim_index.0];
 
                 let new_local_rect = pic.update_local_rect(result);
 
                 if new_local_rect != metadata.local_rect {
                     metadata.local_rect = new_local_rect;
                     frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+                    pic_state.local_rect_changed = true;
                 }
             }
         }
 
         let (local_rect, unclipped_device_rect) = {
             let metadata = &mut self.cpu_metadata[prim_index.0];
             if metadata.local_rect.size.width <= 0.0 ||
                metadata.local_rect.size.height <= 0.0 {
@@ -2667,23 +2613,22 @@ impl PrimitiveStore {
             }
         }
 
         result
     }
 }
 
 fn build_gradient_stops_request(
-    gradient_index: CachedGradientIndex,
+    stops_handle: &mut GpuCacheHandle,
     stops_range: ItemRange<GradientStop>,
     reverse_stops: bool,
     frame_state: &mut FrameBuildingState,
     pic_context: &PictureContext
 ) {
-    let stops_handle = &mut frame_state.cached_gradients[gradient_index.0].handle;
     if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
         let gradient_builder = GradientGpuBlockBuilder::new(
             stops_range,
             pic_context.display_list,
         );
         gradient_builder.build(
             reverse_stops,
             &mut request,
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -1209,8 +1209,55 @@ impl Profiler {
                 renderer_timers,
                 gpu_samplers,
                 screen_fraction,
                 debug_renderer,
             );
         }
     }
 }
+
+#[cfg(feature = "debug_renderer")]
+pub struct ChangeIndicator {
+    counter: u32,
+}
+
+#[cfg(feature = "debug_renderer")]
+impl ChangeIndicator {
+    pub fn new() -> Self {
+        ChangeIndicator {
+            counter: 0
+        }
+    }
+
+    pub fn changed(&mut self) {
+        self.counter = (self.counter + 1) % 15;
+    }
+
+    pub fn draw(
+        &self,
+        x: f32, y: f32,
+        color: ColorU,
+        debug_renderer: &mut DebugRenderer
+    ) {
+        let margin = 0.0;
+        let w = 10.0;
+        let h = 5.0;
+        let tx = self.counter as f32 * w;
+        debug_renderer.add_quad(
+            x - margin,
+            y - margin,
+            x + 15.0 * w + margin,
+            y + h + margin,
+            ColorU::new(0, 0, 0, 150),
+            ColorU::new(0, 0, 0, 150),
+        );
+
+        debug_renderer.add_quad(
+            x + tx,
+            y,
+            x + tx + w,
+            y + h,
+            color,
+            ColorU::new(25, 25, 25, 255),
+        );
+    }
+}
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -106,21 +106,16 @@ struct Document {
     // made available as output textures.
     output_pipelines: FastHashSet<PipelineId>,
     // A helper switch to prevent any frames rendering triggered by scrolling
     // messages between `SetDisplayList` and `GenerateFrame`.
     // If we allow them, then a reftest that scrolls a few layers before generating
     // the first frame would produce inconsistent rendering results, because
     // scroll events are not necessarily received in deterministic order.
     render_on_scroll: Option<bool>,
-    // A helper flag to prevent any hit-tests from happening between calls
-    // to build_scene and rendering the document. In between these two calls,
-    // hit-tests produce inconsistent results because the clip_scroll_tree
-    // is out of sync with the display list.
-    render_on_hittest: bool,
 
     /// A data structure to allow hit testing against rendered frames. This is updated
     /// every time we produce a fully rendered frame.
     hit_tester: Option<HitTester>,
 
     /// Properties that are resolved during frame building and can be changed at any time
     /// without requiring the scene to be re-built.
     dynamic_properties: SceneProperties,
@@ -158,30 +153,29 @@ impl Document {
                 device_pixel_ratio: default_device_pixel_ratio,
             },
             clip_scroll_tree: ClipScrollTree::new(),
             frame_id: FrameId(0),
             frame_builder_config,
             frame_builder: None,
             output_pipelines: FastHashSet::default(),
             render_on_scroll,
-            render_on_hittest: false,
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
         }
     }
 
     fn can_render(&self) -> bool { self.frame_builder.is_some() }
 
     fn has_pixels(&self) -> bool {
         !self.view.window_size.is_empty_or_negative()
     }
 
     // TODO: We will probably get rid of this soon and always forward to the scene building thread.
-    fn build_scene(&mut self, resource_cache: &mut ResourceCache) {
+    fn build_scene(&mut self, resource_cache: &mut ResourceCache, scene_id: u64) {
         let max_texture_size = resource_cache.max_texture_size();
 
         if self.view.window_size.width > max_texture_size ||
            self.view.window_size.height > max_texture_size {
             error!("ERROR: Invalid window dimensions {}x{}. Please call api.set_window_size()",
                 self.view.window_size.width,
                 self.view.window_size.height,
             );
@@ -194,30 +188,31 @@ impl Document {
             Some(root_pipeline_id) => root_pipeline_id,
             None => return,
         };
 
         if !self.pending.scene.pipelines.contains_key(&root_pipeline_id) {
             return;
         }
 
-        // The DisplayListFlattener will re-create the up-to-date current scene's pipeline epoch
+        // The DisplayListFlattener  re-create the up-to-date current scene's pipeline epoch
         // map and clip scroll tree from the information in the pending scene.
         self.current.scene.pipeline_epochs.clear();
         let old_scrolling_states = self.clip_scroll_tree.drain();
 
         let frame_builder = DisplayListFlattener::create_frame_builder(
             old_builder,
             &self.pending.scene,
             &mut self.clip_scroll_tree,
             resource_cache.get_font_instances(),
             &self.view,
             &self.output_pipelines,
             &self.frame_builder_config,
             &mut self.current.scene,
+            scene_id,
         );
 
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
 
         if !self.current.removed_pipelines.is_empty() {
             warn!("Built the scene several times without rendering it.");
         }
 
@@ -228,16 +223,17 @@ impl Document {
         self.frame_id.0 += 1;
     }
 
     fn forward_transaction_to_scene_builder(
         &mut self,
         transaction_msg: TransactionMsg,
         document_ops: &DocumentOps,
         document_id: DocumentId,
+        scene_id: u64,
         resource_cache: &ResourceCache,
         scene_tx: &Sender<SceneBuilderRequest>,
     ) {
         // Do as much of the error handling as possible here before dispatching to
         // the scene builder thread.
         let build_scene: bool = document_ops.build
             && self.pending.scene.root_pipeline_id.map(
                 |id| { self.pending.scene.pipelines.contains_key(&id) }
@@ -245,16 +241,17 @@ impl Document {
 
         let scene_request = if build_scene {
             Some(SceneRequest {
                 scene: self.pending.scene.clone(),
                 removed_pipelines: replace(&mut self.pending.removed_pipelines, Vec::new()),
                 view: self.view.clone(),
                 font_instances: resource_cache.get_font_instances(),
                 output_pipelines: self.output_pipelines.clone(),
+                scene_id,
             })
         } else {
             None
         };
 
         scene_tx.send(SceneBuilderRequest::Transaction {
             scene: scene_request,
             resource_updates: transaction_msg.resource_updates,
@@ -264,16 +261,17 @@ impl Document {
         }).unwrap();
     }
 
     fn render(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         resource_profile: &mut ResourceProfileCounters,
+        is_new_scene: bool,
     ) -> RenderedDocument {
         let accumulated_scale_factor = self.view.accumulated_scale_factor();
         let pan = self.view.pan.to_f32() / accumulated_scale_factor;
 
         let frame = {
             let frame_builder = self.frame_builder.as_mut().unwrap();
             let frame = frame_builder.build(
                 resource_cache,
@@ -287,17 +285,20 @@ impl Document {
                 &mut resource_profile.texture_cache,
                 &mut resource_profile.gpu_cache,
                 &self.dynamic_properties,
             );
             self.hit_tester = Some(frame_builder.create_hit_tester(&self.clip_scroll_tree));
             frame
         };
 
-        RenderedDocument::new(frame)
+        RenderedDocument {
+            frame,
+            is_new_scene,
+        }
     }
 
     pub fn updated_pipeline_info(&mut self) -> PipelineInfo {
         let removed_pipelines = replace(&mut self.current.removed_pipelines, Vec::new());
         PipelineInfo {
             epochs: self.current.scene.pipeline_epochs.clone(),
             removed_pipelines,
         }
@@ -392,16 +393,17 @@ static NEXT_NAMESPACE_ID: AtomicUsize = 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct PlainRenderBackend {
     default_device_pixel_ratio: f32,
     enable_render_on_scroll: bool,
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, DocumentView>,
     resources: PlainResources,
+    last_scene_id: u64,
 }
 
 /// The render backend is responsible for transforming high level display lists into
 /// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame.
 ///
 /// The render backend operates on its own thread.
 pub struct RenderBackend {
     api_rx: MsgReceiver<ApiMsg>,
@@ -419,16 +421,17 @@ pub struct RenderBackend {
 
     frame_config: FrameBuilderConfig,
     documents: FastHashMap<DocumentId, Document>,
 
     notifier: Box<RenderNotifier>,
     recorder: Option<Box<ApiRecordingReceiver>>,
     sampler: Option<Box<AsyncPropertySampler + Send>>,
 
+    last_scene_id: u64,
     enable_render_on_scroll: bool,
 }
 
 impl RenderBackend {
     pub fn new(
         api_rx: MsgReceiver<ApiMsg>,
         payload_rx: Receiver<Payload>,
         result_tx: Sender<ResultMsg>,
@@ -455,16 +458,17 @@ impl RenderBackend {
             default_device_pixel_ratio,
             resource_cache,
             gpu_cache: GpuCache::new(),
             frame_config,
             documents: FastHashMap::default(),
             notifier,
             recorder,
             sampler,
+            last_scene_id: 0,
             enable_render_on_scroll,
         }
     }
 
     fn process_scene_msg(
         &mut self,
         document_id: DocumentId,
         message: SceneMsg,
@@ -683,16 +687,22 @@ impl RenderBackend {
             }
         }
     }
 
     fn next_namespace_id(&self) -> IdNamespace {
         IdNamespace(NEXT_NAMESPACE_ID.fetch_add(1, Ordering::Relaxed) as u32)
     }
 
+    pub fn make_unique_scene_id(&mut self) -> u64 {
+        // 2^64 scenes ought to be enough for anybody!
+        self.last_scene_id += 1;
+        self.last_scene_id
+    }
+
     pub fn run(&mut self, mut profile_counters: BackendProfileCounters) {
         let mut frame_counter: u32 = 0;
         let mut keep_going = true;
 
         if let Some(ref sampler) = self.sampler {
             sampler.register();
         }
 
@@ -707,17 +717,16 @@ impl RenderBackend {
                         resource_updates,
                         frame_ops,
                         render,
                         result_tx,
                     } => {
                         if let Some(doc) = self.documents.get_mut(&document_id) {
                             if let Some(mut built_scene) = built_scene.take() {
                                 doc.new_async_scene_ready(built_scene);
-                                doc.render_on_hittest = true;
                             }
                             if let Some(tx) = result_tx {
                                 let (resume_tx, resume_rx) = channel();
                                 tx.send(SceneSwapResult::Complete(resume_tx)).unwrap();
                                 // Block until the post-swap hook has completed on
                                 // the scene builder thread. We need to do this before
                                 // we can sample from the sampler hook which might happen
                                 // in the update_document call below.
@@ -741,17 +750,18 @@ impl RenderBackend {
                             use_scene_builder_thread: false,
                         };
 
                         if !transaction_msg.is_empty() {
                             self.update_document(
                                 document_id,
                                 transaction_msg,
                                 &mut frame_counter,
-                                &mut profile_counters
+                                &mut profile_counters,
+                                DocumentOps::render(),
                             );
                         }
                     },
                     SceneBuilderResult::FlushComplete(tx) => {
                         tx.send(()).ok();
                     }
                     SceneBuilderResult::Stopped => {
                         panic!("We haven't sent a Stop yet, how did we get a Stopped back?");
@@ -940,78 +950,83 @@ impl RenderBackend {
             ApiMsg::ShutDown => {
                 return false;
             }
             ApiMsg::UpdateDocument(document_id, doc_msgs) => {
                 self.update_document(
                     document_id,
                     doc_msgs,
                     frame_counter,
-                    profile_counters
+                    profile_counters,
+                    DocumentOps::nop(),
                 )
             }
         }
 
         true
     }
 
     fn update_document(
         &mut self,
         document_id: DocumentId,
         mut transaction_msg: TransactionMsg,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
+        initial_op: DocumentOps,
     ) {
-        let mut op = DocumentOps::nop();
+        let mut op = initial_op;
 
         for scene_msg in transaction_msg.scene_ops.drain(..) {
             let _timer = profile_counters.total_time.timer();
             op.combine(
                 self.process_scene_msg(
                     document_id,
                     scene_msg,
                     *frame_counter,
                     &mut profile_counters.ipc,
                 )
             );
         }
 
         if transaction_msg.use_scene_builder_thread {
+            let scene_id = self.make_unique_scene_id();
             let doc = self.documents.get_mut(&document_id).unwrap();
+
             doc.forward_transaction_to_scene_builder(
                 transaction_msg,
                 &op,
                 document_id,
+                scene_id,
                 &self.resource_cache,
                 &self.scene_tx,
             );
 
             return;
         }
 
         self.resource_cache.update_resources(
             transaction_msg.resource_updates,
             &mut profile_counters.resources,
         );
 
         if op.build {
+            let scene_id = self.make_unique_scene_id();
             let doc = self.documents.get_mut(&document_id).unwrap();
             let _timer = profile_counters.total_time.timer();
             profile_scope!("build scene");
 
-            doc.build_scene(&mut self.resource_cache);
-            doc.render_on_hittest = true;
+            doc.build_scene(&mut self.resource_cache, scene_id);
         }
 
         // If we have a sampler, get more frame ops from it and add them
         // to the transaction. This is a hook to allow the WR user code to
         // fiddle with things after a potentially long scene build, but just
         // before rendering. This is useful for rendering with the latest
         // async transforms.
-        if transaction_msg.generate_frame {
+        if op.render || transaction_msg.generate_frame {
             if let Some(ref sampler) = self.sampler {
                 transaction_msg.frame_ops.append(&mut sampler.sample());
             }
         }
 
         for frame_msg in transaction_msg.frame_ops {
             let _timer = profile_counters.total_time.timer();
             op.combine(self.process_frame_msg(document_id, frame_msg));
@@ -1048,16 +1063,17 @@ impl RenderBackend {
             // borrow ck hack for profile_counters
             let (pending_update, rendered_document) = {
                 let _timer = profile_counters.total_time.timer();
 
                 let rendered_document = doc.render(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
                     &mut profile_counters.resources,
+                    op.build,
                 );
 
                 debug!("generated frame for document {:?} with {} passes",
                     document_id, rendered_document.frame.passes.len());
 
                 let msg = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
                 self.result_tx.send(msg).unwrap();
 
@@ -1072,17 +1088,16 @@ impl RenderBackend {
             let msg = ResultMsg::PublishDocument(
                 document_id,
                 rendered_document,
                 pending_update,
                 profile_counters.clone()
             );
             self.result_tx.send(msg).unwrap();
             profile_counters.reset();
-            doc.render_on_hittest = false;
         } else if op.render {
             // WR-internal optimization to avoid doing a bunch of render work if
             // there's no pixels. We still want to pretend to render and request
             // a composite to make sure that the callbacks (particularly the
             // new_frame_ready callback below) has the right flags.
             let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info());
             self.result_tx.send(msg).unwrap();
         }
@@ -1242,16 +1257,17 @@ impl RenderBackend {
                 let file_name = format!("scene-{}-{}", (id.0).0, id.1);
                 config.serialize(&doc.current.scene, file_name);
             }
             if config.bits.contains(CaptureBits::FRAME) {
                 let rendered_document = doc.render(
                     &mut self.resource_cache,
                     &mut self.gpu_cache,
                     &mut profile_counters.resources,
+                    true,
                 );
                 //TODO: write down doc's pipeline info?
                 // it has `pipeline_epoch_map`,
                 // which may capture necessary details for some cases.
                 let file_name = format!("frame-{}-{}", (id.0).0, id.1);
                 config.serialize(&rendered_document.frame, file_name);
             }
         }
@@ -1264,16 +1280,17 @@ impl RenderBackend {
             default_device_pixel_ratio: self.default_device_pixel_ratio,
             enable_render_on_scroll: self.enable_render_on_scroll,
             frame_config: self.frame_config.clone(),
             documents: self.documents
                 .iter()
                 .map(|(id, doc)| (*id, doc.view.clone()))
                 .collect(),
             resources,
+            last_scene_id: self.last_scene_id,
         };
 
         config.serialize(&backend, "backend");
 
         if config.bits.contains(CaptureBits::FRAME) {
             // After we rendered the frames, there are pending updates to both
             // GPU cache and resources. Instead of serializing them, we are going to make sure
             // they are applied on the `Renderer` side.
@@ -1323,16 +1340,17 @@ impl RenderBackend {
             None => GpuCache::new(),
         };
 
         self.documents.clear();
         self.default_device_pixel_ratio = backend.default_device_pixel_ratio;
         self.frame_config = backend.frame_config;
         self.enable_render_on_scroll = backend.enable_render_on_scroll;
 
+        let mut last_scene_id = backend.last_scene_id;
         for (id, view) in backend.documents {
             debug!("\tdocument {:?}", id);
             let scene_name = format!("scene-{}-{}", (id.0).0, id.1);
             let scene = CaptureConfig::deserialize::<Scene, _>(root, &scene_name)
                 .expect(&format!("Unable to open {}.ron", scene_name));
 
             let mut doc = Document {
                 current: SceneData {
@@ -1345,33 +1363,34 @@ impl RenderBackend {
                 },
                 view,
                 clip_scroll_tree: ClipScrollTree::new(),
                 frame_id: FrameId(0),
                 frame_builder_config: self.frame_config.clone(),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 render_on_scroll: None,
-                render_on_hittest: false,
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let render_doc = match CaptureConfig::deserialize::<Frame, _>(root, frame_name) {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
-                    RenderedDocument::new(frame)
+                    RenderedDocument { frame, is_new_scene: true }
                 }
                 None => {
-                    doc.build_scene(&mut self.resource_cache);
+                    last_scene_id += 1;
+                    doc.build_scene(&mut self.resource_cache, last_scene_id);
                     doc.render(
                         &mut self.resource_cache,
                         &mut self.gpu_cache,
                         &mut profile_counters.resources,
+                        true,
                     )
                 }
             };
 
             let msg_update = ResultMsg::UpdateGpuCache(self.gpu_cache.extract_updates());
             self.result_tx.send(msg_update).unwrap();
 
             let msg_publish = ResultMsg::PublishDocument(
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -448,18 +448,17 @@ impl RenderTask {
 
                                 root_task_id
                             }
                         ));
                     }
                     ClipSource::Rectangle(..) |
                     ClipSource::RoundedRectangle(..) |
                     ClipSource::Image(..) |
-                    ClipSource::LineDecoration(..) |
-                    ClipSource::BorderCorner(..) => {}
+                    ClipSource::LineDecoration(..) => {}
                 }
             }
         }
 
         RenderTask {
             children,
             location: RenderTaskLocation::Dynamic(None, Some(outer_rect.size)),
             kind: RenderTaskKind::CacheMask(CacheMaskTask {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -77,17 +77,17 @@ cfg_if! {
         use api::channel::MsgSender;
     }
 }
 
 cfg_if! {
     if #[cfg(feature = "debug_renderer")] {
         use api::ColorU;
         use debug_render::DebugRenderer;
-        use profiler::Profiler;
+        use profiler::{Profiler, ChangeIndicator};
         use query::GpuTimer;
     }
 }
 
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 /// Enabling this toggle would force the GPU cache scattered texture to
 /// be resized every frame, which enables GPU debuggers to see if this
 /// is performed correctly.
@@ -143,24 +143,16 @@ const GPU_TAG_SETUP_DATA: GpuProfileTag 
 const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag {
     label: "SplitComposite",
     color: debug_colors::DARKBLUE,
 };
 const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag {
     label: "TextRun",
     color: debug_colors::BLUE,
 };
-const GPU_TAG_PRIM_BORDER_CORNER: GpuProfileTag = GpuProfileTag {
-    label: "BorderCorner",
-    color: debug_colors::DARKSLATEGREY,
-};
-const GPU_TAG_PRIM_BORDER_EDGE: GpuProfileTag = GpuProfileTag {
-    label: "BorderEdge",
-    color: debug_colors::LAVENDER,
-};
 const GPU_TAG_BLUR: GpuProfileTag = GpuProfileTag {
     label: "Blur",
     color: debug_colors::VIOLET,
 };
 const GPU_TAG_BLIT: GpuProfileTag = GpuProfileTag {
     label: "Blit",
     color: debug_colors::LIME,
 };
@@ -178,26 +170,22 @@ const GPU_SAMPLER_TAG_TRANSPARENT: GpuPr
     color: debug_colors::BLACK,
 };
 
 impl TransformBatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
             TransformBatchKind::TextRun(..) => "TextRun",
-            TransformBatchKind::BorderCorner => "BorderCorner",
-            TransformBatchKind::BorderEdge => "BorderEdge",
         }
     }
 
     fn sampler_tag(&self) -> GpuProfileTag {
         match *self {
             TransformBatchKind::TextRun(..) => GPU_TAG_PRIM_TEXT_RUN,
-            TransformBatchKind::BorderCorner => GPU_TAG_PRIM_BORDER_CORNER,
-            TransformBatchKind::BorderEdge => GPU_TAG_PRIM_BORDER_EDGE,
         }
     }
 }
 
 impl BatchKind {
     #[cfg(feature = "debugger")]
     fn debug_name(&self) -> &'static str {
         match *self {
@@ -243,16 +231,18 @@ bitflags! {
         const RENDER_TARGET_DBG = 1 << 1;
         const TEXTURE_CACHE_DBG = 1 << 2;
         const GPU_TIME_QUERIES  = 1 << 3;
         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;
     }
 }
 
 fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option<bool> {
     if before & select != after & select {
         Some(after.contains(select))
     } else {
         None
@@ -433,16 +423,26 @@ pub(crate) mod desc {
                 count: 2,
                 kind: VertexAttributeKind::F32,
             },
             VertexAttribute {
                 name: "aRadii",
                 count: 2,
                 kind: VertexAttributeKind::F32,
             },
+            VertexAttribute {
+                name: "aClipParams1",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
+            VertexAttribute {
+                name: "aClipParams2",
+                count: 4,
+                kind: VertexAttributeKind::F32,
+            },
         ],
     };
 
     pub const CLIP: VertexDescriptor = VertexDescriptor {
         vertex_attributes: &[
             VertexAttribute {
                 name: "aPosition",
                 count: 2,
@@ -616,17 +616,16 @@ pub(crate) mod desc {
     };
 }
 
 #[derive(Debug, Copy, Clone)]
 pub(crate) enum VertexArrayKind {
     Primitive,
     Blur,
     Clip,
-    DashAndDot,
     VectorStencil,
     VectorCover,
     Border,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum GraphicsApi {
     OpenGL,
@@ -1370,16 +1369,21 @@ pub struct Renderer {
     enable_clear_scissor: bool,
     #[cfg(feature = "debug_renderer")]
     debug: LazyInitializedDebugRenderer,
     debug_flags: DebugFlags,
     backend_profile_counters: BackendProfileCounters,
     profile_counters: RendererProfileCounters,
     #[cfg(feature = "debug_renderer")]
     profiler: Profiler,
+    #[cfg(feature = "debug_renderer")]
+    new_frame_indicator: ChangeIndicator,
+    #[cfg(feature = "debug_renderer")]
+    new_scene_indicator: ChangeIndicator,
+
     last_time: u64,
 
     pub gpu_profile: GpuProfiler<GpuProfileTag>,
     vaos: RendererVAOs,
 
     node_data_texture: VertexDataTexture,
     local_clip_rects_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
@@ -1772,16 +1776,20 @@ impl Renderer {
             shaders,
             #[cfg(feature = "debug_renderer")]
             debug: LazyInitializedDebugRenderer::new(),
             debug_flags,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             #[cfg(feature = "debug_renderer")]
             profiler: Profiler::new(),
+            #[cfg(feature = "debug_renderer")]
+            new_frame_indicator: ChangeIndicator::new(),
+            #[cfg(feature = "debug_renderer")]
+            new_scene_indicator: ChangeIndicator::new(),
             max_texture_size: max_device_size,
             max_recorded_profiles: options.max_recorded_profiles,
             clear_color: options.clear_color,
             enable_clear_scissor: options.enable_clear_scissor,
             last_time: 0,
             gpu_profile,
             gpu_glyph_renderer,
             vaos: RendererVAOs {
@@ -1861,16 +1869,21 @@ impl Renderer {
                     self.pipeline_info.removed_pipelines.extend(pipeline_info.removed_pipelines.drain(..));
                 }
                 ResultMsg::PublishDocument(
                     document_id,
                     mut doc,
                     texture_update_list,
                     profile_counters,
                 ) => {
+                    if doc.is_new_scene {
+                        #[cfg(feature = "debug_renderer")]
+                        self.new_scene_indicator.changed();
+                    }
+
                     // Add a new document to the active set, expressed as a `Vec` in order
                     // to re-order based on `DocumentLayer` during rendering.
                     match self.active_documents.iter().position(|&(id, _)| id == document_id) {
                         Some(pos) => {
                             // If the document we are replacing must be drawn
                             // (in order to update the texture cache), issue
                             // a render just to off-screen targets.
                             if self.active_documents[pos].1.frame.must_be_drawn() {
@@ -1976,26 +1989,16 @@ impl Renderer {
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Zero Clears",
             target.zero_clears.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
-            "Clear",
-            target.clip_batcher.border_clears.len(),
-        );
-        debug_target.add(
-            debug_server::BatchKind::Clip,
-            "Borders",
-            target.clip_batcher.borders.len(),
-        );
-        debug_target.add(
-            debug_server::BatchKind::Clip,
             "BoxShadows",
             target.clip_batcher.box_shadows.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Clip,
             "LineDecorations",
             target.clip_batcher.line_decorations.len(),
         );
@@ -2146,16 +2149,22 @@ impl Renderer {
                 self.set_debug_flag(DebugFlags::RENDER_TARGET_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);
+            }
+            DebugCommand::EnableNewSceneIndicator(enable) => {
+                self.set_debug_flag(DebugFlags::NEW_SCENE_INDICATOR, enable);
+            }
             DebugCommand::EnableDualSourceBlending(_) => {
                 panic!("Should be handled by render backend");
             }
             DebugCommand::FetchDocuments |
             DebugCommand::FetchClipScrollTree => {}
             DebugCommand::FetchRenderTasks => {
                 let json = self.get_render_tasks_for_debugger();
                 self.debug_server.send(json);
@@ -2389,16 +2398,33 @@ impl Renderer {
                         &mut profile_timers,
                         &profile_samplers,
                         screen_fraction,
                         self.debug.get_mut(&mut self.device),
                         self.debug_flags.contains(DebugFlags::COMPACT_PROFILER),
                     );
                 }
             }
+
+            if self.debug_flags.contains(DebugFlags::NEW_FRAME_INDICATOR) {
+                self.new_frame_indicator.changed();
+                self.new_frame_indicator.draw(
+                    0.0, 0.0,
+                    ColorU::new(0, 110, 220, 255),
+                    self.debug.get_mut(&mut self.device)
+                );
+            }
+
+            if self.debug_flags.contains(DebugFlags::NEW_SCENE_INDICATOR) {
+                self.new_scene_indicator.draw(
+                    160.0, 0.0,
+                    ColorU::new(220, 30, 10, 255),
+                    self.debug.get_mut(&mut self.device)
+                );
+            }
         }
 
         if self.debug_flags.contains(DebugFlags::ECHO_DRIVER_MESSAGES) {
             self.device.echo_driver_messages();
         }
 
         self.backend_profile_counters.reset();
         self.profile_counters.reset();
@@ -2609,16 +2635,22 @@ impl Renderer {
     }
 
     pub(crate) fn draw_instanced_batch_with_previously_bound_textures<T>(
         &mut self,
         data: &[T],
         vertex_array_kind: VertexArrayKind,
         stats: &mut RendererStats,
     ) {
+        // If we end up with an empty draw call here, that means we have
+        // probably introduced unnecessary batch breaks during frame
+        // building - so we should be catching this earlier and removing
+        // the batch.
+        debug_assert!(!data.is_empty());
+
         let vao = get_vao(vertex_array_kind, &self.vaos, &self.gpu_glyph_renderer);
 
         self.device.bind_vao(vao);
 
         let batched = !self.debug_flags.contains(DebugFlags::DISABLE_BATCHING);
 
         if batched {
             self.device
@@ -3161,51 +3193,16 @@ impl Renderer {
         }
 
         self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheA8);
 
         // Draw the clip items into the tiled alpha mask.
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_CLIP);
 
-            // If we have border corner clips, the first step is to clear out the
-            // area in the clip mask. This allows drawing multiple invididual clip
-            // in regions below.
-            if !target.clip_batcher.border_clears.is_empty() {
-                let _gm2 = self.gpu_profile.start_marker("clip borders [clear]");
-                self.device.set_blend(false);
-                self.shaders.cs_clip_border
-                    .bind(&mut self.device, projection, &mut self.renderer_errors);
-                self.draw_instanced_batch(
-                    &target.clip_batcher.border_clears,
-                    VertexArrayKind::DashAndDot,
-                    &BatchTextures::no_texture(),
-                    stats,
-                );
-            }
-
-            // Draw any dots or dashes for border corners.
-            if !target.clip_batcher.borders.is_empty() {
-                let _gm2 = self.gpu_profile.start_marker("clip borders");
-                // We are masking in parts of the corner (dots or dashes) here.
-                // Blend mode is set to max to allow drawing multiple dots.
-                // The individual dots and dashes in a border never overlap, so using
-                // a max blend mode here is fine.
-                self.device.set_blend(true);
-                self.device.set_blend_mode_max();
-                self.shaders.cs_clip_border
-                    .bind(&mut self.device, projection, &mut self.renderer_errors);
-                self.draw_instanced_batch(
-                    &target.clip_batcher.borders,
-                    VertexArrayKind::DashAndDot,
-                    &BatchTextures::no_texture(),
-                    stats,
-                );
-            }
-
             // switch to multiplicative blending
             self.device.set_blend(true);
             self.device.set_blend_mode_multiply();
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip rectangles");
                 self.shaders.cs_clip_rectangle.bind(
@@ -4548,29 +4545,27 @@ impl Renderer {
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
                vaos: &'a RendererVAOs,
                gpu_glyph_renderer: &'a GpuGlyphRenderer)
                -> &'a VAO {
     match vertex_array_kind {
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
-        VertexArrayKind::DashAndDot => &vaos.dash_and_dot_vao,
         VertexArrayKind::VectorStencil => &gpu_glyph_renderer.vector_stencil_vao,
         VertexArrayKind::VectorCover => &gpu_glyph_renderer.vector_cover_vao,
         VertexArrayKind::Border => &vaos.border_vao,
     }
 }
 
 #[cfg(not(feature = "pathfinder"))]
 fn get_vao<'a>(vertex_array_kind: VertexArrayKind,
                vaos: &'a RendererVAOs,
                _: &'a GpuGlyphRenderer)
                -> &'a VAO {
     match vertex_array_kind {
         VertexArrayKind::Primitive => &vaos.prim_vao,
         VertexArrayKind::Clip => &vaos.clip_vao,
         VertexArrayKind::Blur => &vaos.blur_vao,
-        VertexArrayKind::DashAndDot => &vaos.dash_and_dot_vao,
         VertexArrayKind::VectorStencil | VertexArrayKind::VectorCover => unreachable!(),
         VertexArrayKind::Border => &vaos.border_vao,
     }
 }
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -52,16 +52,17 @@ pub enum SceneSwapResult {
 
 /// Contains the render backend data needed to build a scene.
 pub struct SceneRequest {
     pub scene: Scene,
     pub view: DocumentView,
     pub font_instances: FontInstanceMap,
     pub output_pipelines: FastHashSet<PipelineId>,
     pub removed_pipelines: Vec<PipelineId>,
+    pub scene_id: u64,
 }
 
 pub struct BuiltScene {
     pub scene: Scene,
     pub frame_builder: FrameBuilder,
     pub clip_scroll_tree: ClipScrollTree,
     pub removed_pipelines: Vec<PipelineId>,
 }
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -45,17 +45,16 @@ impl ImageBufferKind {
 
 pub const IMAGE_BUFFER_KINDS: [ImageBufferKind; 4] = [
     ImageBufferKind::Texture2D,
     ImageBufferKind::TextureRect,
     ImageBufferKind::TextureExternal,
     ImageBufferKind::Texture2DArray,
 ];
 
-const TRANSFORM_FEATURE: &str = "TRANSFORM";
 const ALPHA_FEATURE: &str = "ALPHA_PASS";
 const DITHERING_FEATURE: &str = "DITHERING";
 const DUAL_SOURCE_FEATURE: &str = "DUAL_SOURCE_BLENDING";
 
 pub(crate) enum ShaderKind {
     Primitive,
     Cache(VertexArrayKind),
     ClipCache,
@@ -256,63 +255,16 @@ impl BrushShader {
         self.opaque.deinit(device);
         self.alpha.deinit(device);
         if let Some(dual_source) = self.dual_source {
             dual_source.deinit(device);
         }
     }
 }
 
-struct PrimitiveShader {
-    simple: LazilyCompiledShader,
-    transform: LazilyCompiledShader,
-}
-
-impl PrimitiveShader {
-    fn new(
-        name: &'static str,
-        device: &mut Device,
-        features: &[&'static str],
-        precache: bool,
-    ) -> Result<Self, ShaderError> {
-        let simple = LazilyCompiledShader::new(
-            ShaderKind::Primitive,
-            name,
-            features,
-            device,
-            precache,
-        )?;
-
-        let mut transform_features = features.to_vec();
-        transform_features.push(TRANSFORM_FEATURE);
-
-        let transform = LazilyCompiledShader::new(
-            ShaderKind::Primitive,
-            name,
-            &transform_features,
-            device,
-            precache,
-        )?;
-
-        Ok(PrimitiveShader { simple, transform })
-    }
-
-    fn get(&mut self, transform_kind: TransformedRectKind) -> &mut LazilyCompiledShader {
-        match transform_kind {
-            TransformedRectKind::AxisAligned => &mut self.simple,
-            TransformedRectKind::Complex => &mut self.transform,
-        }
-    }
-
-    fn deinit(self, device: &mut Device) {
-        self.simple.deinit(device);
-        self.transform.deinit(device);
-    }
-}
-
 pub struct TextShader {
     simple: LazilyCompiledShader,
     transform: LazilyCompiledShader,
     glyph_transform: LazilyCompiledShader,
 }
 
 impl TextShader {
     fn new(
@@ -395,17 +347,16 @@ fn create_prim_shader(
     }
 
     debug!("PrimShader {}", name);
 
     let vertex_descriptor = match vertex_format {
         VertexArrayKind::Primitive => desc::PRIM_INSTANCES,
         VertexArrayKind::Blur => desc::BLUR,
         VertexArrayKind::Clip => desc::CLIP,
-        VertexArrayKind::DashAndDot => desc::BORDER_CORNER_DASH_AND_DOT,
         VertexArrayKind::VectorStencil => desc::VECTOR_STENCIL,
         VertexArrayKind::VectorCover => desc::VECTOR_COVER,
         VertexArrayKind::Border => desc::BORDER,
     };
 
     let program = device.create_program(name, &prefix, &vertex_descriptor);
 
     if let Ok(ref program) = program {
@@ -477,30 +428,27 @@ pub struct Shaders {
     brush_linear_gradient: BrushShader,
 
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     pub cs_clip_rectangle: LazilyCompiledShader,
     pub cs_clip_box_shadow: LazilyCompiledShader,
     pub cs_clip_image: LazilyCompiledShader,
-    pub cs_clip_border: LazilyCompiledShader,
     pub cs_clip_line: LazilyCompiledShader,
 
     // The are "primitive shaders". These shaders draw and blend
     // final results on screen. They are aware of tile boundaries.
     // Most draw directly to the framebuffer, but some use inputs
     // from the cache shaders to draw. Specifically, the box
     // shadow primitive shader stretches the box shadow cache
     // output, and the cache_image shader blits the results of
     // a cache shader (e.g. blur) to the screen.
     pub ps_text_run: TextShader,
     pub ps_text_run_dual_source: TextShader,
-    ps_border_corner: PrimitiveShader,
-    ps_border_edge: PrimitiveShader,
 
     ps_split_composite: LazilyCompiledShader,
 }
 
 impl Shaders {
     pub fn new(
         device: &mut Device,
         gl_type: GlType,
@@ -606,24 +554,16 @@ impl Shaders {
         let cs_clip_image = LazilyCompiledShader::new(
             ShaderKind::ClipCache,
             "cs_clip_image",
             &[],
             device,
             options.precache_shaders,
         )?;
 
-        let cs_clip_border = LazilyCompiledShader::new(
-            ShaderKind::ClipCache,
-            "cs_clip_border",
-            &[],
-            device,
-            options.precache_shaders,
-        )?;
-
         let ps_text_run = TextShader::new("ps_text_run",
             device,
             &[],
             options.precache_shaders,
         )?;
 
         let ps_text_run_dual_source = TextShader::new("ps_text_run",
             device,
@@ -694,30 +634,16 @@ impl Shaders {
                         );
                         brush_yuv_image[index] = Some(shader);
                         yuv_features.clear();
                     }
                 }
             }
         }
 
-        let ps_border_corner = PrimitiveShader::new(
-            "ps_border_corner",
-             device,
-             &[],
-             options.precache_shaders,
-        )?;
-
-        let ps_border_edge = PrimitiveShader::new(
-            "ps_border_edge",
-             device,
-             &[],
-             options.precache_shaders,
-        )?;
-
         let cs_border_segment = LazilyCompiledShader::new(
             ShaderKind::Cache(VertexArrayKind::Border),
             "cs_border_segment",
              &[],
              device,
              options.precache_shaders,
         )?;
 
@@ -741,23 +667,20 @@ impl Shaders {
             brush_image,
             brush_blend,
             brush_mix_blend,
             brush_yuv_image,
             brush_radial_gradient,
             brush_linear_gradient,
             cs_clip_rectangle,
             cs_clip_box_shadow,
-            cs_clip_border,
             cs_clip_image,
             cs_clip_line,
             ps_text_run,
             ps_text_run_dual_source,
-            ps_border_corner,
-            ps_border_edge,
             ps_split_composite,
         })
     }
 
     fn get_yuv_shader_index(
         buffer_kind: ImageBufferKind,
         format: YuvFormat,
         color_space: YuvColorSpace,
@@ -799,63 +722,53 @@ impl Shaders {
                         self.brush_yuv_image[shader_index]
                             .as_mut()
                             .expect("Unsupported YUV shader kind")
                     }
                 };
                 brush_shader.get(key.blend_mode)
             }
             BatchKind::Transformable(transform_kind, batch_kind) => {
-                let prim_shader = match batch_kind {
+                match batch_kind {
                     TransformBatchKind::TextRun(glyph_format) => {
                         let text_shader = match key.blend_mode {
                             BlendMode::SubpixelDualSource => {
                                 &mut self.ps_text_run_dual_source
                             }
                             _ => {
                                 &mut self.ps_text_run
                             }
                         };
                         return text_shader.get(glyph_format, transform_kind);
                     }
-                    TransformBatchKind::BorderCorner => {
-                        &mut self.ps_border_corner
-                    }
-                    TransformBatchKind::BorderEdge => {
-                        &mut self.ps_border_edge
-                    }
-                };
-                prim_shader.get(transform_kind)
+                }
             }
         }
     }
 
     pub fn deinit(self, device: &mut Device) {
         self.cs_blur_a8.deinit(device);
         self.cs_blur_rgba8.deinit(device);
         self.brush_solid.deinit(device);
         self.brush_blend.deinit(device);
         self.brush_mix_blend.deinit(device);
         self.brush_radial_gradient.deinit(device);
         self.brush_linear_gradient.deinit(device);
         self.cs_clip_rectangle.deinit(device);
         self.cs_clip_box_shadow.deinit(device);
         self.cs_clip_image.deinit(device);
-        self.cs_clip_border.deinit(device);
         self.cs_clip_line.deinit(device);
         self.ps_text_run.deinit(device);
         self.ps_text_run_dual_source.deinit(device);
         for shader in self.brush_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
         for shader in self.brush_yuv_image {
             if let Some(shader) = shader {
                 shader.deinit(device);
             }
         }
-        self.ps_border_corner.deinit(device);
-        self.ps_border_edge.deinit(device);
         self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -12,17 +12,17 @@ use device::{FrameId, Texture};
 #[cfg(feature = "pathfinder")]
 use euclid::{TypedPoint2D, TypedVector2D};
 use gpu_cache::{GpuCache};
 use gpu_types::{BorderInstance, BlurDirection, BlurInstance};
 use gpu_types::{ClipScrollNodeData, ZBufferIdGenerator};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 #[cfg(feature = "pathfinder")]
 use pathfinder_partitioner::mesh::Mesh;
-use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
+use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveStore};
 use prim_store::{BrushKind, DeferredResolve};
 use profiler::FrameProfileCounters;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, GlyphTask, RenderTaskLocation, RenderTaskTree};
 use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32, mem};
 use texture_allocator::GuillotineAllocator;
 #[cfg(feature = "pathfinder")]
@@ -44,17 +44,16 @@ pub struct RenderTargetIndex(pub usize);
 
 pub struct RenderTargetContext<'a, 'rc> {
     pub device_pixel_scale: DevicePixelScale,
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'rc mut ResourceCache,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub use_dual_source_blending: bool,
     pub node_data: &'a [ClipScrollNodeData],
-    pub cached_gradients: &'a [CachedGradient],
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct TextureAllocator {
     // TODO(gw): Replace this with a simpler allocator for
     // render target allocation - this use case doesn't need
     // to deal with coalescing etc that the general texture
--- a/gfx/webrender/tests/angle_shader_validation.rs
+++ b/gfx/webrender/tests/angle_shader_validation.rs
@@ -32,37 +32,29 @@ const SHADERS: &[Shader] = &[
         name: "cs_clip_image",
         features: CLIP_FEATURES,
     },
     Shader {
         name: "cs_clip_box_shadow",
         features: CLIP_FEATURES,
     },
     Shader {
-        name: "cs_clip_border",
-        features: CLIP_FEATURES,
-    },
-    Shader {
         name: "cs_clip_line",
         features: CLIP_FEATURES,
     },
     // Cache shaders
     Shader {
         name: "cs_blur",
         features: CACHE_FEATURES,
     },
-    // Prim shaders
     Shader {
-        name: "ps_border_corner",
-        features: PRIM_FEATURES,
+        name: "cs_border_segment",
+        features: CACHE_FEATURES,
     },
-    Shader {
-        name: "ps_border_edge",
-        features: PRIM_FEATURES,
-    },
+    // Prim shaders
     Shader {
         name: "ps_split_composite",
         features: PRIM_FEATURES,
     },
     Shader {
         name: "ps_text_run",
         features: PRIM_FEATURES,
     },
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -556,16 +556,20 @@ pub enum DebugCommand {
     /// Display intermediate render targets on screen.
     EnableRenderTargetDebug(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),
+    /// Show an indicator that moves every time a scene is built.
+    EnableNewSceneIndicator(bool),
     /// Fetch current documents and display lists.
     FetchDocuments,
     /// Fetch current passes and batches.
     FetchPasses,
     /// Fetch clip-scroll tree.
     FetchClipScrollTree,
     /// Fetch render tasks.
     FetchRenderTasks,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-8e697f8cb1f1aab2e5f6b9b903eb7191340b10c5
+aff9f409f3d6a3518c38c1f7755657f564c1083a
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -44,16 +44,17 @@ impl<'a> RawtestHarness<'a> {
         self.test_hit_testing();
         self.test_retained_blob_images_test();
         self.test_blob_update_test();
         self.test_blob_update_epoch_test();
         self.test_tile_decomposition();
         self.test_very_large_blob();
         self.test_offscreen_blob();
         self.test_save_restore();
+        self.test_blur_cache();
         self.test_capture();
         self.test_zero_height_window();
     }
 
     fn render_and_get_pixels(&mut self, window_rect: DeviceUintRect) -> Vec<u8> {
         self.rx.recv().unwrap();
         self.wrench.render();
         self.wrench.renderer.read_pixels_rgba8(window_rect)
@@ -715,16 +716,61 @@ impl<'a> RawtestHarness<'a> {
         };
 
         let first = do_test(false);
         let second = do_test(true);
 
         assert_eq!(first, second);
     }
 
+    // regression test for #2769
+    // "async scene building: cache collisions from reused picture ids"
+    fn test_blur_cache(&mut self) {
+        println!("\tblur cache...");
+        let window_size = self.window.get_inner_size();
+
+        let test_size = DeviceUintSize::new(400, 400);
+
+        let window_rect = DeviceUintRect::new(
+            DeviceUintPoint::new(0, window_size.height - test_size.height),
+            test_size,
+        );
+        let layout_size = LayoutSize::new(400., 400.);
+
+        let mut do_test = |shadow_is_red| {
+            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
+            let shadow_color = if shadow_is_red {
+                ColorF::new(1.0, 0.0, 0.0, 1.0)
+            } else {
+                ColorF::new(0.0, 1.0, 0.0, 1.0)
+            };
+
+            builder.push_shadow(&PrimitiveInfo::new(rect(100., 100., 100., 100.)),
+                Shadow {
+                    offset: LayoutVector2D::new(1.0, 1.0),
+                    blur_radius: 1.0,
+                    color: shadow_color,
+                });
+            builder.push_line(&PrimitiveInfo::new(rect(110., 110., 50., 2.)),
+                              0.0, LineOrientation::Horizontal,
+                              &ColorF::new(0.0, 0.0, 0.0, 1.0), LineStyle::Solid);
+            builder.pop_all_shadows();
+
+            let txn = Transaction::new();
+            self.submit_dl(&mut Epoch(0), layout_size, builder, &txn.resource_updates);
+
+            self.render_and_get_pixels(window_rect)
+        };
+
+        let first = do_test(false);
+        let second = do_test(true);
+
+        assert_ne!(first, second);
+    }
+
     fn test_capture(&mut self) {
         println!("\tcapture...");
         let path = "../captures/test";
         let layout_size = LayoutSize::new(400., 400.);
         let dim = self.window.get_inner_size();
         let window_rect = DeviceUintRect::new(
             point(0, dim.height - layout_size.height as u32),
             size(layout_size.width as u32, layout_size.height as u32),