Bug 1466549 - Update webrender to aff9f409f3d6a3518c38c1f7755657f564c1083a. r?Gankro draft
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 06 Jun 2018 07:34:22 -0400
changeset 804676 bd1ad9e581a6867fdcbebe2eaabbfc209df25fc0
parent 804634 cec4a3cecc29ff97860198969b6fdff24b9e93bb
child 804677 b0c7bfb9ec9aa6346a7f97ada8c3c442602d25b4
push id112433
push userkgupta@mozilla.com
push dateWed, 06 Jun 2018 11:35:31 +0000
reviewersGankro
bugs1466549
milestone62.0a1
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),