Bug 1355475 - Update webrender and webrender_traits to 07b6c6a1f93b5d8af1dd9ae825906dcf5c310810. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 18 Apr 2017 09:58:53 -0400
changeset 564395 a208c1cc0f00086177ec98a9b0ba4147a01cc20e
parent 564392 10965ecbe9c7d4695d49c4d1e61f340c5f79f8d7
child 564396 0442a7b8836cf9dabfcae29fa1245aad8b3619db
push id54581
push userkgupta@mozilla.com
push dateTue, 18 Apr 2017 14:05:01 +0000
reviewersjrmuizel
bugs1355475
milestone55.0a1
Bug 1355475 - Update webrender and webrender_traits to 07b6c6a1f93b5d8af1dd9ae825906dcf5c310810. r?jrmuizel MozReview-Commit-ID: DfBH3Q3BeqD
gfx/webrender/Cargo.toml
gfx/webrender/doc/CLIPPING.md
gfx/webrender/res/clip_shared.glsl
gfx/webrender/res/cs_clip_image.glsl
gfx/webrender/res/cs_clip_image.vs.glsl
gfx/webrender/res/cs_clip_rectangle.glsl
gfx/webrender/res/cs_clip_rectangle.vs.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_angle_gradient.vs.glsl
gfx/webrender/res/ps_border.fs.glsl
gfx/webrender/res/ps_border.glsl
gfx/webrender/res/ps_border.vs.glsl
gfx/webrender/res/ps_border_corner.fs.glsl
gfx/webrender/res/ps_border_corner.glsl
gfx/webrender/res/ps_border_corner.vs.glsl
gfx/webrender/res/ps_border_edge.fs.glsl
gfx/webrender/res/ps_border_edge.glsl
gfx/webrender/res/ps_border_edge.vs.glsl
gfx/webrender/res/ps_box_shadow.vs.glsl
gfx/webrender/res/ps_cache_image.vs.glsl
gfx/webrender/res/ps_gradient.glsl
gfx/webrender/res/ps_gradient.vs.glsl
gfx/webrender/res/ps_image.fs.glsl
gfx/webrender/res/ps_image.glsl
gfx/webrender/res/ps_image.vs.glsl
gfx/webrender/res/ps_radial_gradient.vs.glsl
gfx/webrender/res/ps_rectangle.glsl
gfx/webrender/res/ps_rectangle.vs.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/ps_text_run.vs.glsl
gfx/webrender/res/ps_yuv_image.fs.glsl
gfx/webrender/res/ps_yuv_image.glsl
gfx/webrender/res/ps_yuv_image.vs.glsl
gfx/webrender/src/CLIPPING.md
gfx/webrender/src/batch_builder.rs
gfx/webrender/src/border.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/mask_cache.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/resource_cache.rs
gfx/webrender/src/scene.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/image.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.31.0"
+version = "0.32.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib", "webgl"]
 freetype-lib = ["freetype/servo-freetype-sys"]
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/doc/CLIPPING.md
@@ -0,0 +1,93 @@
+# 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.
+
+```rust
+pub struct ClipRegion {
+    pub main: LayoutRect,
+    pub complex: ItemRange,
+    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.
+
+## Item Clips
+
+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
+
+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 clip display item has a `ClipRegion` as well as several other fields:
+
+```rust
+pub struct ClipDisplayItem {
+    pub id: ClipId,
+    pub parent_id: ClipId,
+}
+```
+
+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
+
+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:
+
+```rust
+let id = ClipId::new(my_internal_id, pipeline_id);
+let generated_id = define_clip(content_rect, clip, id);
+assert!(id == generated_id);
+```
+
+## 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))
--- a/gfx/webrender/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -29,28 +29,31 @@ CacheClipInstance fetch_clip_item(int in
     cci.render_task_index = aClipRenderTaskIndex;
     cci.layer_index = aClipLayerIndex;
     cci.data_index = aClipDataIndex;
     cci.segment_index = aClipSegmentIndex;
 
     return cci;
 }
 
+struct ClipVertexInfo {
+    vec3 local_pos;
+    vec2 screen_pos;
+    RectWithSize clipped_local_rect;
+};
+
 // The transformed vertex function that always covers the whole clip area,
 // which is the intersection of all clip instances of a given primitive
-TransformVertexInfo write_clip_tile_vertex(vec4 local_clip_rect,
-                                           Layer layer,
-                                           ClipArea area,
-                                           int segment_index) {
-    vec2 lp0_base = local_clip_rect.xy;
-    vec2 lp1_base = local_clip_rect.xy + local_clip_rect.zw;
+ClipVertexInfo write_clip_tile_vertex(RectWithSize local_clip_rect,
+                                      Layer layer,
+                                      ClipArea area,
+                                      int segment_index) {
 
-    vec2 lp0 = clamp_rect(lp0_base, layer.local_clip_rect);
-    vec2 lp1 = clamp_rect(lp1_base, layer.local_clip_rect);
-    vec4 clipped_local_rect = vec4(lp0, lp1 - lp0);
+    RectWithSize clipped_local_rect = intersect_rect(local_clip_rect,
+                                                     layer.local_clip_rect);
 
     vec2 outer_p0 = area.screen_origin_target_index.xy;
     vec2 outer_p1 = outer_p0 + area.task_bounds.zw - area.task_bounds.xy;
     vec2 inner_p0 = area.inner_rect.xy;
     vec2 inner_p1 = area.inner_rect.zw;
 
     vec2 p0, p1;
     switch (segment_index) {
@@ -80,12 +83,12 @@ TransformVertexInfo write_clip_tile_vert
 
     vec4 layer_pos = get_layer_pos(actual_pos / uDevicePixelRatio, layer);
 
     // compute the point position in side the layer, in CSS space
     vec2 vertex_pos = actual_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
 
     gl_Position = uTransform * vec4(vertex_pos, 0.0, 1);
 
-    return TransformVertexInfo(layer_pos.xyw, actual_pos, clipped_local_rect);
+    return ClipVertexInfo(layer_pos.xyw, actual_pos, clipped_local_rect);
 }
 
 #endif //WR_VERTEX_SHADER
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -1,10 +1,10 @@
 #line 1
 
 /* 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/. */
 
 varying vec3 vPos;
-flat varying vec4 vLocalRect;
+flat varying RectWithSize vLocalRect;
 flat varying vec4 vClipMaskUvRect;
 flat varying vec4 vClipMaskUvInnerRect;
--- a/gfx/webrender/res/cs_clip_image.vs.glsl
+++ b/gfx/webrender/res/cs_clip_image.vs.glsl
@@ -1,42 +1,38 @@
 #line 1
 /* 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/. */
 
 struct ImageMaskData {
-    vec4 uv_rect;
-    vec4 local_rect;
+    RectWithSize uv_rect;
+    RectWithSize local_rect;
 };
 
 ImageMaskData fetch_mask_data(int index) {
-    ImageMaskData info;
-
-    ivec2 uv = get_fetch_uv_2(index);
-
-    info.uv_rect = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
-    info.local_rect = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
-
-    return info;
+    vec4 data[2] = fetch_data_2(index);
+    return ImageMaskData(RectWithSize(data[0].xy, data[0].zw),
+                         RectWithSize(data[1].xy, data[1].zw));
 }
 
 void main(void) {
     CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
     ClipArea area = fetch_clip_area(cci.render_task_index);
     Layer layer = fetch_layer(cci.layer_index);
     ImageMaskData mask = fetch_mask_data(cci.data_index);
-    vec4 local_rect = mask.local_rect;
+    RectWithSize local_rect = mask.local_rect;
 
-    TransformVertexInfo vi = write_clip_tile_vertex(local_rect,
-                                                    layer,
-                                                    area,
-                                                    cci.segment_index);
+    ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
+                                               layer,
+                                               area,
+                                               cci.segment_index);
+
     vLocalRect = vi.clipped_local_rect;
     vPos = vi.local_pos;
 
-    vClipMaskUv = vec3((vPos.xy / vPos.z - local_rect.xy) / local_rect.zw, 0.0);
+    vClipMaskUv = vec3((vPos.xy / vPos.z - local_rect.p0) / local_rect.size, 0.0);
     vec2 texture_size = vec2(textureSize(sMask, 0));
-    vClipMaskUvRect = mask.uv_rect / texture_size.xyxy;
+    vClipMaskUvRect = vec4(mask.uv_rect.p0, mask.uv_rect.size) / texture_size.xyxy;
     // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
-    vec4 inner_rect = vec4(mask.uv_rect.xy, mask.uv_rect.xy + mask.uv_rect.zw);
+    vec4 inner_rect = vec4(mask.uv_rect.p0, mask.uv_rect.p0 + mask.uv_rect.size);
     vClipMaskUvInnerRect = (inner_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
 }
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -1,11 +1,11 @@
 #line 1
 
 /* 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/. */
 
 varying vec3 vPos;
-flat varying vec4 vLocalRect;
+flat varying RectWithSize vLocalRect;
 flat varying vec4 vClipRect;
 flat varying vec4 vClipRadius;
 flat varying float vClipMode;
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.vs.glsl
@@ -1,43 +1,31 @@
 #line 1
 /* 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/. */
 
 struct ClipRect {
-    vec4 rect;
+    RectWithSize rect;
     vec4 mode;
 };
 
 ClipRect fetch_clip_rect(int index) {
-    ClipRect rect;
-
-    ivec2 uv = get_fetch_uv_2(index);
-
-    rect.rect = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
-    rect.mode = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
-
-    return rect;
+    vec4 data[2] = fetch_data_2(index);
+    return ClipRect(RectWithSize(data[0].xy, data[0].zw), data[1]);
 }
 
 struct ClipCorner {
-    vec4 rect;
+    RectWithSize rect;
     vec4 outer_inner_radius;
 };
 
 ClipCorner fetch_clip_corner(int index) {
-    ClipCorner corner;
-
-    ivec2 uv = get_fetch_uv_2(index);
-
-    corner.rect = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
-    corner.outer_inner_radius = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
-
-    return corner;
+    vec4 data[2] = fetch_data_2(index);
+    return ClipCorner(RectWithSize(data[0].xy, data[0].zw), data[1]);
 }
 
 struct ClipData {
     ClipRect rect;
     ClipCorner top_left;
     ClipCorner top_right;
     ClipCorner bottom_left;
     ClipCorner bottom_right;
@@ -55,24 +43,24 @@ ClipData fetch_clip(int index) {
     return clip;
 }
 
 void main(void) {
     CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
     ClipArea area = fetch_clip_area(cci.render_task_index);
     Layer layer = fetch_layer(cci.layer_index);
     ClipData clip = fetch_clip(cci.data_index);
-    vec4 local_rect = clip.rect.rect;
+    RectWithSize local_rect = clip.rect.rect;
 
-    TransformVertexInfo vi = write_clip_tile_vertex(local_rect,
-                                                    layer,
-                                                    area,
-                                                    cci.segment_index);
+    ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
+                                               layer,
+                                               area,
+                                               cci.segment_index);
     vLocalRect = vi.clipped_local_rect;
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
-    vClipRect = vec4(local_rect.xy, local_rect.xy + local_rect.zw);
+    vClipRect = vec4(local_rect.p0, local_rect.p0 + local_rect.size);
     vClipRadius = vec4(clip.top_left.outer_inner_radius.x,
                        clip.top_right.outer_inner_radius.x,
                        clip.bottom_right.outer_inner_radius.x,
                        clip.bottom_left.outer_inner_radius.x);
 }
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -31,67 +31,16 @@
 #define UV_PIXEL         uint(1)
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
-flat varying vec4 vClipMaskUvBounds;
-varying vec3 vClipMaskUv;
-
-#ifdef WR_VERTEX_SHADER
-
-#define VECS_PER_LAYER             13
-#define VECS_PER_RENDER_TASK        3
-#define VECS_PER_PRIM_GEOM          2
-
-uniform sampler2D sLayers;
-uniform sampler2D sRenderTasks;
-uniform sampler2D sPrimGeometry;
-
-uniform sampler2D sData16;
-uniform sampler2D sData32;
-uniform sampler2D sData64;
-uniform sampler2D sData128;
-uniform sampler2D sResourceRects;
-
-// Instanced attributes
-in int aGlobalPrimId;
-in int aPrimitiveAddress;
-in int aTaskIndex;
-in int aClipTaskIndex;
-in int aLayerIndex;
-in int aElementIndex;
-in ivec2 aUserData;
-in int aZIndex;
-
-// get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
-// TODO: convert back to a function once the driver issues are resolved, if ever.
-// https://github.com/servo/webrender/pull/623
-// https://github.com/servo/servo/issues/13953
-#define get_fetch_uv(i, vpi)  ivec2(vpi * (i % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)), i / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))
-
-ivec2 get_fetch_uv_1(int index) {
-    return get_fetch_uv(index, 1);
-}
-
-ivec2 get_fetch_uv_2(int index) {
-    return get_fetch_uv(index, 2);
-}
-
-ivec2 get_fetch_uv_4(int index) {
-    return get_fetch_uv(index, 4);
-}
-
-ivec2 get_fetch_uv_8(int index) {
-    return get_fetch_uv(index, 8);
-}
-
 struct RectWithSize {
     vec2 p0;
     vec2 size;
 };
 
 struct RectWithEndpoint {
     vec2 p0;
     vec2 p1;
@@ -125,16 +74,100 @@ vec2 clamp_rect(vec2 point, RectWithEndp
 vec4 clamp_rect(vec4 points, RectWithSize rect) {
     return clamp(points, rect.p0.xyxy, rect.p0.xyxy + rect.size.xyxy);
 }
 
 vec4 clamp_rect(vec4 points, RectWithEndpoint rect) {
     return clamp(points, rect.p0.xyxy, rect.p1.xyxy);
 }
 
+RectWithSize intersect_rect(RectWithSize a, RectWithSize b) {
+    vec4 p = clamp_rect(vec4(a.p0, a.p0 + a.size), b);
+    return RectWithSize(p.xy, max(vec2(0.0), p.zw - p.xy));
+}
+
+RectWithEndpoint intersect_rect(RectWithEndpoint a, RectWithEndpoint b) {
+    vec4 p = clamp_rect(vec4(a.p0, a.p1), b);
+    return RectWithEndpoint(p.xy, max(p.xy, p.zw));
+}
+
+
+flat varying RectWithEndpoint vClipMaskUvBounds;
+varying vec3 vClipMaskUv;
+
+#ifdef WR_VERTEX_SHADER
+
+#define VECS_PER_LAYER             13
+#define VECS_PER_RENDER_TASK        3
+#define VECS_PER_PRIM_GEOM          2
+
+uniform sampler2D sLayers;
+uniform sampler2D sRenderTasks;
+uniform sampler2D sPrimGeometry;
+
+uniform sampler2D sData16;
+uniform sampler2D sData32;
+uniform sampler2D sData64;
+uniform sampler2D sData128;
+uniform sampler2D sResourceRects;
+
+// Instanced attributes
+in int aGlobalPrimId;
+in int aPrimitiveAddress;
+in int aTaskIndex;
+in int aClipTaskIndex;
+in int aLayerIndex;
+in int aElementIndex;
+in ivec2 aUserData;
+in int aZIndex;
+
+// get_fetch_uv is a macro to work around a macOS Intel driver parsing bug.
+// TODO: convert back to a function once the driver issues are resolved, if ever.
+// https://github.com/servo/webrender/pull/623
+// https://github.com/servo/servo/issues/13953
+#define get_fetch_uv(i, vpi)  ivec2(vpi * (i % (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi)), i / (WR_MAX_VERTEX_TEXTURE_WIDTH/vpi))
+
+vec4 fetch_data_1(int index) {
+    ivec2 uv = get_fetch_uv(index, 1);
+    return texelFetch(sData16, uv, 0);
+}
+
+vec4[2] fetch_data_2(int index) {
+    ivec2 uv = get_fetch_uv(index, 2);
+    return vec4[2](
+        texelFetchOffset(sData32, uv, 0, ivec2(0, 0)),
+        texelFetchOffset(sData32, uv, 0, ivec2(1, 0))
+    );
+}
+
+vec4[4] fetch_data_4(int index) {
+    ivec2 uv = get_fetch_uv(index, 4);
+    return vec4[4](
+        texelFetchOffset(sData64, uv, 0, ivec2(0, 0)),
+        texelFetchOffset(sData64, uv, 0, ivec2(1, 0)),
+        texelFetchOffset(sData64, uv, 0, ivec2(2, 0)),
+        texelFetchOffset(sData64, uv, 0, ivec2(3, 0))
+    );
+}
+
+vec4[8] fetch_data_8(int index) {
+    ivec2 uv = get_fetch_uv(index, 8);
+    return vec4[8](
+        texelFetchOffset(sData128, uv, 0, ivec2(0, 0)),
+        texelFetchOffset(sData128, uv, 0, ivec2(1, 0)),
+        texelFetchOffset(sData128, uv, 0, ivec2(2, 0)),
+        texelFetchOffset(sData128, uv, 0, ivec2(3, 0)),
+        texelFetchOffset(sData128, uv, 0, ivec2(4, 0)),
+        texelFetchOffset(sData128, uv, 0, ivec2(5, 0)),
+        texelFetchOffset(sData128, uv, 0, ivec2(6, 0)),
+        texelFetchOffset(sData128, uv, 0, ivec2(7, 0))
+    );
+}
+
+
 struct Layer {
     mat4 transform;
     mat4 inv_transform;
     RectWithSize local_clip_rect;
     vec4 screen_vertices[4];
 };
 
 Layer fetch_layer(int index) {
@@ -231,81 +264,110 @@ ClipArea fetch_clip_area(int index) {
 
 struct Gradient {
     vec4 start_end_point;
     vec4 tile_size_repeat;
     vec4 extend_mode;
 };
 
 Gradient fetch_gradient(int index) {
-    Gradient gradient;
-
-    ivec2 uv = get_fetch_uv_4(index);
-
-    gradient.start_end_point = texelFetchOffset(sData64, uv, 0, ivec2(0, 0));
-    gradient.tile_size_repeat = texelFetchOffset(sData64, uv, 0, ivec2(1, 0));
-    gradient.extend_mode = texelFetchOffset(sData64, uv, 0, ivec2(2, 0));
-
-    return gradient;
+    vec4 data[4] = fetch_data_4(index);
+    return Gradient(data[0], data[1], data[2]);
 }
 
 struct GradientStop {
     vec4 color;
     vec4 offset;
 };
 
 GradientStop fetch_gradient_stop(int index) {
-    GradientStop stop;
-
-    ivec2 uv = get_fetch_uv_2(index);
-
-    stop.color = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
-    stop.offset = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
-
-    return stop;
+    vec4 data[2] = fetch_data_2(index);
+    return GradientStop(data[0], data[1]);
 }
 
 struct RadialGradient {
     vec4 start_end_center;
     vec4 start_end_radius_ratio_xy_extend_mode;
     vec4 tile_size_repeat;
 };
 
 RadialGradient fetch_radial_gradient(int index) {
-    RadialGradient gradient;
+    vec4 data[4] = fetch_data_4(index);
+    return RadialGradient(data[0], data[1], data[2]);
+}
+
+struct Border {
+    vec4 style;
+    vec4 widths;
+    vec4 colors[4];
+    vec4 radii[2];
+};
 
-    ivec2 uv = get_fetch_uv_4(index);
+Border fetch_border(int index) {
+    vec4 data[8] = fetch_data_8(index);
+    return Border(data[0], data[1],
+                  vec4[4](data[2], data[3], data[4], data[5]),
+                  vec4[2](data[6], data[7]));
+}
+
+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;
+};
 
-    gradient.start_end_center = texelFetchOffset(sData64, uv, 0, ivec2(0, 0));
-    gradient.start_end_radius_ratio_xy_extend_mode = texelFetchOffset(sData64, uv, 0, ivec2(1, 0));
-    gradient.tile_size_repeat = texelFetchOffset(sData64, uv, 0, ivec2(2, 0));
+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));
 
-    return gradient;
+    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
+    );
 }
 
 struct Glyph {
     vec4 offset;
 };
 
 Glyph fetch_glyph(int index) {
-    Glyph glyph;
-
-    ivec2 uv = get_fetch_uv_1(index);
-
-    glyph.offset = texelFetchOffset(sData16, uv, 0, ivec2(0, 0));
-
-    return glyph;
+    vec4 data = fetch_data_1(index);
+    return Glyph(data);
 }
 
 RectWithSize fetch_instance_geometry(int index) {
-    ivec2 uv = get_fetch_uv_1(index);
-
-    vec4 rect = texelFetchOffset(sData16, uv, 0, ivec2(0, 0));
-
-    return RectWithSize(rect.xy, rect.zw);
+    vec4 data = fetch_data_1(index);
+    return RectWithSize(data.xy, data.zw);
 }
 
 struct PrimitiveGeometry {
     RectWithSize local_rect;
     RectWithSize local_clip_rect;
 };
 
 PrimitiveGeometry fetch_prim_geometry(int index) {
@@ -449,33 +511,31 @@ vec4 get_layer_pos(vec2 pos, Layer layer
     vec3 b = layer.screen_vertices[3].xyz / layer.screen_vertices[3].w;
     vec3 c = layer.screen_vertices[2].xyz / layer.screen_vertices[2].w;
     // get the normal to the layer plane
     vec3 n = normalize(cross(b-a, c-a));
     return untransform(pos, n, a, layer.inv_transform);
 }
 
 struct VertexInfo {
-    RectWithEndpoint local_rect;
     vec2 local_pos;
     vec2 screen_pos;
 };
 
 VertexInfo write_vertex(RectWithSize instance_rect,
                         RectWithSize local_clip_rect,
                         float z,
                         Layer layer,
-                        AlphaBatchTask task) {
-    RectWithEndpoint local_rect = to_rect_with_endpoint(instance_rect);
-
+                        AlphaBatchTask task,
+                        vec2 snap_ref) {
     // Select the corner of the local rect that we are processing.
-    vec2 local_pos = mix(local_rect.p0, local_rect.p1, aPosition.xy);
+    vec2 local_pos = instance_rect.p0 + instance_rect.size * aPosition.xy;
 
     // xy = top left corner of the local rect, zw = position of current vertex.
-    vec4 local_p0_pos = vec4(local_rect.p0, local_pos);
+    vec4 local_p0_pos = vec4(snap_ref, local_pos);
 
     // Clamp to the two local clip rects.
     local_p0_pos = clamp_rect(local_p0_pos, local_clip_rect);
     local_p0_pos = clamp_rect(local_p0_pos, layer.local_clip_rect);
 
     // Transform the top corner and current vertex to world space.
     vec4 world_p0 = layer.transform * vec4(local_p0_pos.xy, 0.0, 1.0);
     world_p0.xyz /= world_p0.w;
@@ -491,26 +551,25 @@ VertexInfo write_vertex(RectWithSize ins
     // Apply offsets for the render task to get correct screen location.
     vec2 final_pos = device_p0_pos.zw -
                      snap_delta -
                      task.screen_space_origin +
                      task.render_target_origin;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
-    VertexInfo vi = VertexInfo(local_rect, local_p0_pos.zw, device_p0_pos.zw);
+    VertexInfo vi = VertexInfo(local_p0_pos.zw, device_p0_pos.zw);
     return vi;
 }
 
 #ifdef WR_FEATURE_TRANSFORM
 
 struct TransformVertexInfo {
     vec3 local_pos;
     vec2 screen_pos;
-    vec4 clipped_local_rect;
 };
 
 float cross2(vec2 v0, vec2 v1) {
     return v0.x * v1.y - v0.y * v1.x;
 }
 
 // Return intersection of line (p0,p1) and line (p2,p3)
 vec2 intersect_lines(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
@@ -526,17 +585,18 @@ vec2 intersect_lines(vec2 p0, vec2 p1, v
 
     return vec2(nx / d, ny / d);
 }
 
 TransformVertexInfo write_transform_vertex(RectWithSize instance_rect,
                                            RectWithSize local_clip_rect,
                                            float z,
                                            Layer layer,
-                                           AlphaBatchTask task) {
+                                           AlphaBatchTask task,
+                                           vec2 snap_ref) {
     RectWithEndpoint local_rect = to_rect_with_endpoint(instance_rect);
 
     vec2 current_local_pos, prev_local_pos, next_local_pos;
 
     // Select the current vertex and the previous/next vertices,
     // based on the vertex ID that is known based on the instance rect.
     switch (gl_VertexID) {
         case 0:
@@ -586,185 +646,156 @@ TransformVertexInfo write_transform_vert
 
     // Intersect those adjusted lines to find the actual vertex position.
     vec2 device_pos = intersect_lines(adjusted_prev_p0,
                                       adjusted_prev_p1,
                                       adjusted_next_p0,
                                       adjusted_next_p1);
 
     // Calculate the snap amount based on the first vertex as a reference point.
-    vec4 world_p0 = layer.transform * vec4(local_rect.p0, 0.0, 1.0);
+    vec4 world_p0 = layer.transform * vec4(snap_ref, 0.0, 1.0);
     vec2 device_p0 = uDevicePixelRatio * world_p0.xy / world_p0.w;
     vec2 snap_delta = device_p0 - floor(device_p0 + 0.5);
 
     // Apply offsets for the render task to get correct screen location.
     vec2 final_pos = device_pos -
                      snap_delta -
                      task.screen_space_origin +
                      task.render_target_origin;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
     vec4 layer_pos = get_layer_pos(device_pos / uDevicePixelRatio, layer);
 
-    return TransformVertexInfo(layer_pos.xyw, device_pos, vec4(instance_rect.p0, instance_rect.size));
+    return TransformVertexInfo(layer_pos.xyw, device_pos);
 }
 
 #endif //WR_FEATURE_TRANSFORM
 
 struct ResourceRect {
     vec4 uv_rect;
 };
 
 ResourceRect fetch_resource_rect(int index) {
     ResourceRect rect;
 
-    ivec2 uv = get_fetch_uv_1(index);
+    ivec2 uv = get_fetch_uv(index, 1);
 
     rect.uv_rect = texelFetchOffset(sResourceRects, uv, 0, ivec2(0, 0));
 
     return rect;
 }
 
 struct Rectangle {
     vec4 color;
 };
 
 Rectangle fetch_rectangle(int index) {
-    Rectangle rect;
-
-    ivec2 uv = get_fetch_uv_1(index);
-
-    rect.color = texelFetchOffset(sData16, uv, 0, ivec2(0, 0));
-
-    return rect;
+    vec4 data = fetch_data_1(index);
+    return Rectangle(data);
 }
 
 struct TextRun {
     vec4 color;
 };
 
 TextRun fetch_text_run(int index) {
-    TextRun text;
-
-    ivec2 uv = get_fetch_uv_1(index);
-
-    text.color = texelFetchOffset(sData16, uv, 0, ivec2(0, 0));
-
-    return text;
+    vec4 data = fetch_data_1(index);
+    return TextRun(data);
 }
 
 struct Image {
     vec4 stretch_size_and_tile_spacing;  // Size of the actual image and amount of space between
                                          //     tiled instances of this image.
 };
 
 Image fetch_image(int index) {
-    Image image;
-
-    ivec2 uv = get_fetch_uv_1(index);
-
-    image.stretch_size_and_tile_spacing = texelFetchOffset(sData16, uv, 0, ivec2(0, 0));
-
-    return image;
+    vec4 data = fetch_data_1(index);
+    return Image(data);
 }
 
 // YUV color spaces
 #define YUV_REC601 1
 #define YUV_REC709 2
 
 struct YuvImage {
     vec4 y_st_rect;
     vec4 u_st_rect;
     vec4 v_st_rect;
     vec2 size;
     int color_space;
 };
 
 YuvImage fetch_yuv_image(int index) {
-    YuvImage image;
-
-    ivec2 uv = get_fetch_uv_1(index);
-
-    vec4 size_color_space = texelFetchOffset(sData16, uv, 0, ivec2(0, 0));
-    image.size = size_color_space.xy;
-    image.color_space = int(size_color_space.z);
-
-    return image;
+    vec4 data = fetch_data_1(index);
+    return YuvImage(vec4(0.0), vec4(0.0), vec4(0.0), data.xy, int(data.z));
 }
 
 struct BoxShadow {
     vec4 src_rect;
     vec4 bs_rect;
     vec4 color;
     vec4 border_radius_edge_size_blur_radius_inverted;
 };
 
 BoxShadow fetch_boxshadow(int index) {
-    BoxShadow bs;
-
-    ivec2 uv = get_fetch_uv_4(index);
-
-    bs.src_rect = texelFetchOffset(sData64, uv, 0, ivec2(0, 0));
-    bs.bs_rect = texelFetchOffset(sData64, uv, 0, ivec2(1, 0));
-    bs.color = texelFetchOffset(sData64, uv, 0, ivec2(2, 0));
-    bs.border_radius_edge_size_blur_radius_inverted = texelFetchOffset(sData64, uv, 0, ivec2(3, 0));
-
-    return bs;
+    vec4 data[4] = fetch_data_4(index);
+    return BoxShadow(data[0], data[1], data[2], data[3]);
 }
 
 void write_clip(vec2 global_pos, ClipArea area) {
     vec2 texture_size = vec2(textureSize(sCacheA8, 0).xy);
     vec2 uv = global_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
-    vClipMaskUvBounds = area.task_bounds / texture_size.xyxy;
+    vClipMaskUvBounds = RectWithEndpoint(area.task_bounds.xy / texture_size,
+                                         area.task_bounds.zw / texture_size);
     vClipMaskUv = vec3(uv / texture_size, area.screen_origin_target_index.z);
 }
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
 float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
     vec2 d = max(p0 - pos, pos - p1);
     return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
 }
 
-vec2 init_transform_fs(vec3 local_pos, vec4 local_rect, out float fragment_alpha) {
+vec2 init_transform_fs(vec3 local_pos, RectWithSize local_rect, out float fragment_alpha) {
     fragment_alpha = 1.0;
     vec2 pos = local_pos.xy / local_pos.z;
 
     // Because the local rect is placed on whole coordinates, but the interpolation
     // occurs at pixel centers, we need to offset the signed distance by that amount.
     // In the simple case of no zoom, and no transform, this is 0.5. However, we
     // need to scale this by the amount that the local rect is changing by per
     // fragment, based on the current zoom and transform.
     vec2 fw = fwidth(pos.xy);
     vec2 dxdy = 0.5 * fw;
 
     // Now get the actual signed distance. Inset the local rect by the offset amount
     // above to get correct distance values. This ensures that we only apply
     // anti-aliasing when the fragment has partial coverage.
     float d = signed_distance_rect(pos,
-                                   local_rect.xy + dxdy,
-                                   local_rect.xy + local_rect.zw - dxdy);
+                                   local_rect.p0 + dxdy,
+                                   local_rect.p0 + local_rect.size - dxdy);
 
     // Find the appropriate distance to apply the AA smoothstep over.
     float afwidth = 0.5 / length(fw);
 
     // Only apply AA to fragments outside the signed distance field.
     fragment_alpha = 1.0 - smoothstep(0.0, afwidth, d);
 
     return pos;
 }
 
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
-        vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
-        vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
+        vec4(vClipMaskUvBounds.p0, vClipMaskUv.xy),
+        vec4(vClipMaskUv.xy, vClipMaskUvBounds.p1));
     // check for the dummy bounds, which are given to the opaque objects
-    return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
+    return vClipMaskUvBounds.p0 == vClipMaskUvBounds.p1 ? 1.0:
         all(inside) ? textureLod(sCacheA8, vClipMaskUv, 0.0).r : 0.0;
 }
 
 vec4 dither(vec4 color) {
     const int matrix_mask = 7;
 
     ivec2 pos = ivec2(gl_FragCoord.xy) & ivec2(matrix_mask);
     float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
--- a/gfx/webrender/res/ps_angle_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.vs.glsl
@@ -6,17 +6,18 @@
 void main(void) {
     Primitive prim = load_primitive();
     Gradient gradient = fetch_gradient(prim.prim_index);
 
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
+                                 prim.task,
+                                 prim.local_rect.p0);
 
     vPos = vi.local_pos - prim.local_rect.p0;
 
     vec2 start_point = gradient.start_end_point.xy;
     vec2 end_point = gradient.start_end_point.zw;
     vec2 dir = end_point - start_point;
 
     vStartPoint = start_point;
--- a/gfx/webrender/res/ps_border.fs.glsl
+++ b/gfx/webrender/res/ps_border.fs.glsl
@@ -170,61 +170,52 @@ vec4 draw_dashed_edge(float position, fl
 
   if (mod(segment + 2.0, 2.0) == 0.0) {
     return vHorizontalColor * vec4(1.0, 1.0, 1.0, 1.0 - alpha);
   } else {
     return vHorizontalColor * vec4(1.0, 1.0, 1.0, alpha);
   }
 }
 
-void draw_dashed_or_dotted_border(vec2 local_pos, float distance_from_mix_line) {
+vec4 draw_dashed_or_dotted_border(vec2 local_pos, float distance_from_mix_line) {
   // This is the conversion factor for transformations and device pixel scaling.
   float pixels_per_fragment = length(fwidth(local_pos.xy));
 
   switch (vBorderPart) {
     // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
     case PST_TOP_LEFT:
     case PST_TOP_RIGHT:
     case PST_BOTTOM_LEFT:
     case PST_BOTTOM_RIGHT:
     {
-      oFragColor = get_fragment_color(distance_from_mix_line, pixels_per_fragment);
+      vec4 color = get_fragment_color(distance_from_mix_line, pixels_per_fragment);
       if (vRadii.x > 0.0) {
-        oFragColor *= vec4(1.0, 1.0, 1.0, alpha_for_solid_border_corner(local_pos,
-                                                                  vRadii.zw,
-                                                                  vRadii.xy,
-                                                                  pixels_per_fragment));
+        color.a *= alpha_for_solid_border_corner(local_pos,
+                                                 vRadii.zw,
+                                                 vRadii.xy,
+                                                 pixels_per_fragment);
       }
-
-      break;
+      return color;
     }
     case PST_BOTTOM:
     case PST_TOP: {
-      if (vBorderStyle == BORDER_STYLE_DASHED) {
-        oFragColor = draw_dashed_edge(vLocalPos.x - vPieceRect.x,
-                                      vPieceRect.w,
-                                      pixels_per_fragment);
-      } else {
-        oFragColor = draw_dotted_edge(local_pos.yx, vPieceRect.yxwz, pixels_per_fragment);
-      }
-      break;
+      return vBorderStyle == BORDER_STYLE_DASHED ?
+        draw_dashed_edge(vLocalPos.x - vPieceRect.x, vPieceRect.w, pixels_per_fragment) :
+        draw_dotted_edge(local_pos.yx, vPieceRect.yxwz, pixels_per_fragment);
     }
     case PST_LEFT:
     case PST_RIGHT:
     {
-      if (vBorderStyle == BORDER_STYLE_DASHED) {
-        oFragColor = draw_dashed_edge(vLocalPos.y - vPieceRect.y,
-                                      vPieceRect.z,
-                                      pixels_per_fragment);
-      } else {
-        oFragColor = draw_dotted_edge(local_pos.xy, vPieceRect.xyzw, pixels_per_fragment);
-      }
-      break;
+      return vBorderStyle == BORDER_STYLE_DASHED ?
+        draw_dashed_edge(vLocalPos.y - vPieceRect.y, vPieceRect.z, pixels_per_fragment) :
+        draw_dotted_edge(local_pos.xy, vPieceRect.xyzw, pixels_per_fragment);
     }
   }
+
+  return vec4(0.0);
 }
 
 vec4 draw_double_edge(float pos,
                       float len,
                       float distance_from_mix_line,
                       float pixels_per_fragment) {
   float total_border_width = len;
   float one_third_width = total_border_width / 3.0;
@@ -292,187 +283,164 @@ vec4 draw_double_edge_corner(vec2 local_
                                                      distance_from_mix_line >= 0.0;
   if (is_vertical) {
     return draw_double_edge_vertical(local_pos, distance_from_mix_line, pixels_per_fragment);
   } else {
     return draw_double_edge_horizontal(local_pos, distance_from_mix_line, pixels_per_fragment);
   }
 }
 
-void draw_double_border(float distance_from_mix_line, vec2 local_pos) {
+vec4 draw_double_border(float distance_from_mix_line, vec2 local_pos) {
   float pixels_per_fragment = length(fwidth(local_pos.xy));
   switch (vBorderPart) {
     // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
     case PST_TOP_LEFT:
     case PST_TOP_RIGHT:
     case PST_BOTTOM_LEFT:
     case PST_BOTTOM_RIGHT:
-    {
-      oFragColor = draw_double_edge_corner(local_pos, distance_from_mix_line, pixels_per_fragment);
-      break;
-    }
+      return draw_double_edge_corner(local_pos, distance_from_mix_line, pixels_per_fragment);
     case PST_BOTTOM:
     case PST_TOP:
-    {
-      oFragColor = draw_double_edge_horizontal(local_pos,
-                                               distance_from_mix_line,
-                                               pixels_per_fragment);
-      break;
-    }
+      return draw_double_edge_horizontal(local_pos,
+                                         distance_from_mix_line,
+                                         pixels_per_fragment);
     case PST_LEFT:
     case PST_RIGHT:
-    {
-      oFragColor = draw_double_edge_vertical(local_pos,
-                                             distance_from_mix_line,
-                                             pixels_per_fragment);
-      break;
-    }
+      return draw_double_edge_vertical(local_pos,
+                                       distance_from_mix_line,
+                                       pixels_per_fragment);
   }
+  return vec4(0.0);
 }
 
-void draw_solid_border(float distanceFromMixLine, vec2 localPos) {
+vec4 draw_solid_border(float distanceFromMixLine, vec2 localPos) {
   switch (vBorderPart) {
     case PST_TOP_LEFT:
     case PST_TOP_RIGHT:
     case PST_BOTTOM_LEFT:
     case PST_BOTTOM_RIGHT: {
       // This is the conversion factor for transformations and device pixel scaling.
       float pixelsPerFragment = length(fwidth(localPos.xy));
-      oFragColor = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
+      vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
 
       if (vRadii.x > 0.0) {
-        float alpha = alpha_for_solid_border_corner(localPos, vRadii.zw, vRadii.xy, pixelsPerFragment);
-        oFragColor *= vec4(1.0, 1.0, 1.0, alpha);
+        color.a *= alpha_for_solid_border_corner(localPos, vRadii.zw, vRadii.xy, pixelsPerFragment);
       }
 
-      break;
+      return color;
     }
     default:
-      oFragColor = vHorizontalColor;
       discard_pixels_in_rounded_borders(localPos);
-      break;
+      return vHorizontalColor;
   }
 }
 
 vec4 draw_mixed_edge(float distance, float border_len, vec4 color, vec2 brightness_mod) {
   float modulator = distance / border_len > 0.5 ? brightness_mod.x : brightness_mod.y;
   return vec4(color.xyz * modulator, color.a);
 }
 
-void draw_mixed_border(float distanceFromMixLine, float distanceFromMiddle, vec2 localPos, vec2 brightness_mod) {
+vec4 draw_mixed_border(float distanceFromMixLine, float distanceFromMiddle, vec2 localPos, vec2 brightness_mod) {
   switch (vBorderPart) {
     case PST_TOP_LEFT:
     case PST_TOP_RIGHT:
     case PST_BOTTOM_LEFT:
     case PST_BOTTOM_RIGHT: {
       // This is the conversion factor for transformations and device pixel scaling.
       float pixelsPerFragment = length(fwidth(localPos.xy));
       vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
 
       if (vRadii.x > 0.0) {
         float distance = distance(vRefPoint, localPos) - vRadii.z;
         float length = vRadii.x - vRadii.z;
         if (distanceFromMiddle < 0.0) {
           distance = length - distance;
         }
 
-        oFragColor = 0.0 <= distance && distance <= length ?
-          draw_mixed_edge(distance, length, color, brightness_mod) : vec4(0.0, 0.0, 0.0, 0.0);
-        break;
+        return 0.0 <= distance && distance <= length ?
+          draw_mixed_edge(distance, length, color, brightness_mod) :
+          vec4(0.0, 0.0, 0.0, 0.0);
       }
 
       bool is_vertical = (vBorderPart == PST_TOP_LEFT) ? distanceFromMixLine < 0.0 :
                                                          distanceFromMixLine >= 0.0;
       float distance = is_vertical ? abs(localPos.x - vRefPoint.x) : abs(localPos.y - vRefPoint.y);
       float length = is_vertical ? abs(vPieceRect.z) : abs(vPieceRect.w);
       if (distanceFromMiddle > 0.0) {
           distance = length - distance;
       }
 
-      oFragColor = 0.0 <= distance && distance <= length ?
-        draw_mixed_edge(distance, length, color, brightness_mod) : vec4(0.0, 0.0, 0.0, 0.0);
-      break;
+      return 0.0 <= distance && distance <= length ?
+        draw_mixed_edge(distance, length, color, brightness_mod) :
+        vec4(0.0, 0.0, 0.0, 0.0);
     }
     case PST_BOTTOM:
-    case PST_TOP: {
-      oFragColor = draw_mixed_edge(localPos.y - vPieceRect.y, vPieceRect.w, vVerticalColor, brightness_mod);
-      break;
-    }
+    case PST_TOP:
+      return draw_mixed_edge(localPos.y - vPieceRect.y, vPieceRect.w, vVerticalColor, brightness_mod);
     case PST_LEFT:
-    case PST_RIGHT: {
-      oFragColor = draw_mixed_edge(localPos.x - vPieceRect.x, vPieceRect.z, vHorizontalColor, brightness_mod);
+    case PST_RIGHT:
+      return draw_mixed_edge(localPos.x - vPieceRect.x, vPieceRect.z, vHorizontalColor, brightness_mod);
+  }
+  return vec4(0.0);
+}
+
+vec4 draw_complete_border(vec2 local_pos, float distance_from_mix_line, float distance_from_middle) {
+  vec2 brightness_mod = vec2(0.7, 1.3);
+
+  // Note: we can't pass-through in the following cases,
+  // because Angle doesn't support it and fails to compile the shaders.
+  switch (vBorderStyle) {
+    case BORDER_STYLE_DASHED:
+      return draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
+    case BORDER_STYLE_DOTTED:
+      return draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
+    case BORDER_STYLE_DOUBLE:
+      return draw_double_border(distance_from_mix_line, local_pos);
+    case BORDER_STYLE_OUTSET:
+      return draw_solid_border(distance_from_mix_line, local_pos);
+    case BORDER_STYLE_INSET:
+      return draw_solid_border(distance_from_mix_line, local_pos);
+    case BORDER_STYLE_SOLID:
+      return draw_solid_border(distance_from_mix_line, local_pos);
+    case BORDER_STYLE_NONE:
+      return draw_solid_border(distance_from_mix_line, local_pos);
+    case BORDER_STYLE_GROOVE:
+      return draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.yx);
+    case BORDER_STYLE_RIDGE:
+      return draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.xy);
+    case BORDER_STYLE_HIDDEN:
+    default:
       break;
-    }
   }
+
+  // Note: Workaround for Angle on Windows,
+  // because non-empty case statements must have break or return.
+  discard;
+  return vec4(0.0);
 }
 
 // TODO: Investigate performance of this shader and see
 //       if it's worthwhile splitting it / removing branches etc.
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 local_pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
 #else
+    float alpha = 1.0;
     vec2 local_pos = vLocalPos;
 #endif
 
 #ifdef WR_FEATURE_TRANSFORM
     // TODO(gw): Support other border styles for transformed elements.
     float distance_from_mix_line = (local_pos.x - vPieceRect.x) * vPieceRect.w -
                                    (local_pos.y - vPieceRect.y) * vPieceRect.z;
     distance_from_mix_line /= vPieceRectHypotenuseLength;
-    float distance_from_middle = (local_pos.x - vLocalRect.x) +
-                                 (local_pos.y - vLocalRect.y) -
-                                 0.5 * (vLocalRect.z + vLocalRect.w);
+    float distance_from_middle = (local_pos.x - vBorderRect.p0.x) +
+                                 (local_pos.y - vBorderRect.p0.y) -
+                                 0.5 * (vBorderRect.size.x + vBorderRect.size.y);
 #else
     float distance_from_mix_line = vDistanceFromMixLine;
     float distance_from_middle = vDistanceFromMiddle;
 #endif
 
-    vec2 brightness_mod = vec2(0.7, 1.3);
-    bool needs_discard = false;
-
-    // Note: we can't pass-through in the following cases,
-    // because Angle doesn't support it and fails to compile the shaders.
-    switch (vBorderStyle) {
-        case BORDER_STYLE_DASHED:
-          draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
-          break;
-        case BORDER_STYLE_DOTTED:
-          draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
-          break;
-        case BORDER_STYLE_DOUBLE:
-          draw_double_border(distance_from_mix_line, local_pos);
-          break;
-        case BORDER_STYLE_OUTSET:
-          draw_solid_border(distance_from_mix_line, local_pos);
-          break;
-        case BORDER_STYLE_INSET:
-          draw_solid_border(distance_from_mix_line, local_pos);
-          break;
-        case BORDER_STYLE_SOLID:
-          draw_solid_border(distance_from_mix_line, local_pos);
-          break;
-        case BORDER_STYLE_NONE:
-          draw_solid_border(distance_from_mix_line, local_pos);
-          break;
-        case BORDER_STYLE_GROOVE:
-          draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.yx);
-          break;
-        case BORDER_STYLE_RIDGE:
-          draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.xy);
-          break;
-        case BORDER_STYLE_HIDDEN:
-        default:
-          needs_discard = true;
-          break;
-    }
-
-    // Note: Workaround for Angle on Windows,
-    // because non-empty case statements must have break or return.
-    if (needs_discard) {
-      discard;
-    }
-
-#ifdef WR_FEATURE_TRANSFORM
-    oFragColor *= vec4(1.0, 1.0, 1.0, alpha);
-#endif
+    oFragColor = draw_complete_border(local_pos, distance_from_mix_line, distance_from_middle);
+    oFragColor.a *= min(alpha, do_clip());
 }
--- a/gfx/webrender/res/ps_border.glsl
+++ b/gfx/webrender/res/ps_border.glsl
@@ -1,31 +1,32 @@
 #line 1
 
 /* 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/. */
 
 // These are not changing.
-flat varying vec4 vVerticalColor;     // The vertical color, e.g. top/bottom
-flat varying vec4 vHorizontalColor;   // The horizontal color e.g. left/right
-flat varying vec4 vRadii;             // The border radius from CSS border-radius
-flat varying vec4 vLocalRect; // The rect of the border (x, y, w, h) in local space.
+flat varying vec4 vVerticalColor;      // The vertical color, e.g. top/bottom
+flat varying vec4 vHorizontalColor;    // The horizontal color e.g. left/right
+flat varying vec4 vRadii;              // The border radius from CSS border-radius
+flat varying RectWithSize vBorderRect; // The rect of the border in local space.
 
 // for corners, this is the beginning of the corner.
 // For the lines, this is the top left of the line.
 flat varying vec2 vRefPoint;
 flat varying int vBorderStyle;
 flat varying int vBorderPart; // Which part of the border we're drawing.
 
 flat varying vec4 vPieceRect;
 
 // These are in device space
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;     // The clamped position in local space.
+flat varying RectWithSize vLocalRect;
 flat varying float vPieceRectHypotenuseLength;
 #else
 varying vec2 vLocalPos;     // The clamped position in local space.
 
 // These two are interpolated
 varying float vDistanceFromMixLine;  // This is the distance from the line where two colors
                                      // meet in border corners.
 varying float vDistanceFromMiddle;   // This is the distance from the line between the top
--- a/gfx/webrender/res/ps_border.vs.glsl
+++ b/gfx/webrender/res/ps_border.vs.glsl
@@ -1,58 +1,35 @@
 #line 1
 /* 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/. */
 
-struct Border {
-    vec4 style;
-    vec4 widths;
-    vec4 colors[4];
-    vec4 radii[2];
-};
-
-Border fetch_border(int index) {
-    Border border;
-
-    ivec2 uv = get_fetch_uv_8(index);
-
-    border.style = texelFetchOffset(sData128, uv, 0, ivec2(0, 0));
-    border.widths = texelFetchOffset(sData128, uv, 0, ivec2(1, 0));
-    border.colors[0] = texelFetchOffset(sData128, uv, 0, ivec2(2, 0));
-    border.colors[1] = texelFetchOffset(sData128, uv, 0, ivec2(3, 0));
-    border.colors[2] = texelFetchOffset(sData128, uv, 0, ivec2(4, 0));
-    border.colors[3] = texelFetchOffset(sData128, uv, 0, ivec2(5, 0));
-    border.radii[0] = texelFetchOffset(sData128, uv, 0, ivec2(6, 0));
-    border.radii[1] = texelFetchOffset(sData128, uv, 0, ivec2(7, 0));
-
-    return border;
-}
-
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
+    vBorderRect = prim.local_rect;
 
-    vec2 tl_outer = prim.local_rect.p0;
+    vec2 tl_outer = vBorderRect.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(prim.local_rect.p0.x + prim.local_rect.size.x,
-                         prim.local_rect.p0.y);
+    vec2 tr_outer = vec2(vBorderRect.p0.x + vBorderRect.size.x,
+                         vBorderRect.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(prim.local_rect.p0.x + prim.local_rect.size.x,
-                         prim.local_rect.p0.y + prim.local_rect.size.y);
+    vec2 br_outer = vec2(vBorderRect.p0.x + vBorderRect.size.x,
+                         vBorderRect.p0.y + vBorderRect.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(prim.local_rect.p0.x,
-                         prim.local_rect.p0.y + prim.local_rect.size.y);
+    vec2 bl_outer = vec2(vBorderRect.p0.x,
+                         vBorderRect.p0.y + vBorderRect.size.y);
     vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
                                     -max(border.radii[1].w, border.widths.w));
 
     RectWithSize segment_rect;
     switch (sub_part) {
         case PST_TOP_LEFT:
             segment_rect.p0 = tl_outer;
             segment_rect.size = tl_inner - tl_outer;
@@ -123,32 +100,31 @@ void main(void) {
             break;
     }
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
-                                                    prim.task);
+                                                    prim.task,
+                                                    prim.local_rect.p0);
     vLocalPos = vi.local_pos;
-
-    // Local space
-    vLocalRect = vi.clipped_local_rect;
+    vLocalRect = segment_rect;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
+                                 prim.task,
+                                 prim.local_rect.p0);
     vLocalPos = vi.local_pos.xy;
+#endif
 
-    // Local space
-    vLocalRect = vec4(prim.local_rect.p0, prim.local_rect.size);
-#endif
+    write_clip(vi.screen_pos, prim.clip_area);
 
     float x0, y0, x1, y1;
     switch (sub_part) {
         // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
         case PST_TOP_LEFT:
             x0 = segment_rect.p0.x;
             y0 = segment_rect.p0.y;
             // These are width / heights
@@ -204,13 +180,13 @@ void main(void) {
     // to properly mix border colors. For transformed borders, we calculate this distance
     // in the fragment shader itself. For non-transformed borders, we can use the
     // interpolator.
 #ifdef WR_FEATURE_TRANSFORM
     vPieceRectHypotenuseLength = sqrt(pow(width, 2.0) + pow(height, 2.0));
 #else
     vDistanceFromMixLine = (vi.local_pos.x - x0) * height -
                            (vi.local_pos.y - y0) * width;
-    vDistanceFromMiddle = (vi.local_pos.x - vLocalRect.x) +
-                          (vi.local_pos.y - vLocalRect.y) -
-                          0.5 * (vLocalRect.z + vLocalRect.w);
+    vDistanceFromMiddle = (vi.local_pos.x - vBorderRect.p0.x) +
+                          (vi.local_pos.y - vBorderRect.p0.y) -
+                          0.5 * (vBorderRect.size.x + vBorderRect.size.y);
 #endif
 }
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_border_corner.fs.glsl
@@ -0,0 +1,122 @@
+#line 1
+/* 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/. */
+
+//
+// Signed distance to an ellipse.
+// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
+// Note that this fails for exact circles.
+//
+float sdEllipse( vec2 p, in vec2 ab ) {
+    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
+    float l = ab.y*ab.y - ab.x*ab.x;
+
+    float m = ab.x*p.x/l;
+    float n = ab.y*p.y/l;
+    float m2 = m*m;
+    float n2 = n*n;
+
+    float c = (m2 + n2 - 1.0)/3.0;
+    float c3 = c*c*c;
+
+    float q = c3 + m2*n2*2.0;
+    float d = c3 + m2*n2;
+    float g = m + m*n2;
+
+    float co;
+
+    if( d<0.0 )
+    {
+        float p = acos(q/c3)/3.0;
+        float s = cos(p);
+        float t = sin(p)*sqrt(3.0);
+        float rx = sqrt( -c*(s + t + 2.0) + m2 );
+        float ry = sqrt( -c*(s - t + 2.0) + m2 );
+        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
+    }
+    else
+    {
+        float h = 2.0*m*n*sqrt( d );
+        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
+        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
+        float rx = -s - u - c*4.0 + 2.0*m2;
+        float ry = (s - u)*sqrt(3.0);
+        float rm = sqrt( rx*rx + ry*ry );
+        float p = ry/sqrt(rm-rx);
+        co = (p + 2.0*g/rm - m)/2.0;
+    }
+
+    float si = sqrt( 1.0 - co*co );
+
+    vec2 r = vec2( ab.x*co, ab.y*si );
+
+    return length(r - p ) * sign(p.y-r.y);
+}
+
+float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
+    vec2 dir_to_p0 = p0 - p;
+    return dot(normalize(perp_dir), dir_to_p0);
+}
+
+void main(void) {
+    float alpha = 1.0;
+#ifdef WR_FEATURE_TRANSFORM
+    alpha = 0.0;
+    vec2 local_pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
+#else
+    vec2 local_pos = vLocalPos;
+#endif
+
+    alpha = min(alpha, do_clip());
+
+    // Find the appropriate distance to apply the AA smoothstep over.
+    vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
+
+    // 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 (all(lessThan(local_pos * vClipSign, vClipCenter * vClipSign))) {
+        float d0, d1;
+
+        // sdEllipse fails on exact circles, so handle equal
+        // radii here. The branch coherency should make this
+        // a performance win for the circle case too.
+        if (vOuterRadii.x == vOuterRadii.y) {
+            // Get the distances to the inner and outer radii.
+            d0 = distance(vClipCenter, local_pos) - vOuterRadii.x;
+        } else {
+            // Get the distance to the outer ellipse.
+            vec2 p = local_pos - vClipCenter;
+            d0 = sdEllipse(p, vOuterRadii);
+        }
+
+        if (vInnerRadii.x == vInnerRadii.y) {
+            d1 = distance(vClipCenter, local_pos) - vInnerRadii.x;
+        } else {
+            // Get distance to inner ellipse. Skip if the inner
+            // radius is <= 0.0 to avoid FP errors.
+            if (vInnerRadii.x <= 0.0 || vInnerRadii.y <= 0.0) {
+                d1 = -d0;
+            } else {
+                vec2 p = local_pos - vClipCenter;
+                d1 = sdEllipse(p, vInnerRadii);
+            }
+        }
+
+        // Signed distance field subtract
+        float d = max(d0, 0.5 * afwidth - d1);
+
+        // Only apply AA to fragments outside the signed distance field.
+        alpha = min(alpha, 1.0 - smoothstep(0.0, afwidth, d));
+    }
+
+    // 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, local_pos);
+    float m = smoothstep(-0.5 * afwidth, 0.5 * afwidth, ld);
+    vec4 color = mix(vColor0, vColor1, m);
+
+    oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_border_corner.glsl
@@ -0,0 +1,22 @@
+#line 1
+/* 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/. */
+
+// Edge color transition
+flat varying vec4 vColor0;
+flat varying vec4 vColor1;
+flat varying vec4 vColorEdgeLine;
+
+// Border radius
+flat varying vec2 vClipCenter;
+flat varying vec2 vOuterRadii;
+flat varying vec2 vInnerRadii;
+flat varying vec2 vClipSign;
+
+#ifdef WR_FEATURE_TRANSFORM
+flat varying RectWithSize vLocalRect;
+varying vec3 vLocalPos;
+#else
+varying vec2 vLocalPos;
+#endif
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_border_corner.vs.glsl
@@ -0,0 +1,112 @@
+#line 1
+/* 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/. */
+
+void set_radii(vec2 border_radius,
+               vec2 border_width,
+               vec2 outer_corner,
+               vec2 gradient_sign) {
+    if (border_radius.x > 0.0 && border_radius.y > 0.0) {
+        // Set inner/outer radius on valid border radius.
+        vOuterRadii = border_radius;
+    } else {
+        // No border radius - ensure clip has no effect.
+        vOuterRadii = vec2(2.0 * border_width);
+    }
+
+    if (border_radius.x > border_width.x && border_radius.y > border_width.y) {
+        vInnerRadii = max(vec2(0.0), border_radius - border_width);
+    } else {
+        vInnerRadii = vec2(0.0);
+    }
+
+    vec2 gradient = border_width * gradient_sign;
+    vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
+}
+
+void main(void) {
+    Primitive prim = load_primitive();
+    Border border = fetch_border(prim.prim_index);
+    int sub_part = prim.sub_index;
+    BorderCorners corners = get_border_corners(border, prim.local_rect);
+
+    RectWithSize segment_rect;
+    switch (sub_part) {
+        case 0: {
+            segment_rect.p0 = corners.tl_outer;
+            segment_rect.size = corners.tl_inner - corners.tl_outer;
+            vColor0 = border.colors[0];
+            vColor1 = border.colors[1];
+            vClipCenter = corners.tl_outer + border.radii[0].xy;
+            vClipSign = vec2(1.0);
+            set_radii(border.radii[0].xy,
+                      border.widths.xy,
+                      corners.tl_outer,
+                      vec2(1.0, 1.0));
+            break;
+        }
+        case 1: {
+            segment_rect.p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
+            segment_rect.size = vec2(corners.tr_outer.x - corners.tr_inner.x,
+                                     corners.tr_inner.y - corners.tr_outer.y);
+            vColor0 = border.colors[1];
+            vColor1 = border.colors[2];
+            vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
+            vClipSign = vec2(-1.0, 1.0);
+            set_radii(border.radii[0].zw,
+                      border.widths.zy,
+                      corners.tr_outer,
+                      vec2(-1.0, 1.0));
+            break;
+        }
+        case 2: {
+            segment_rect.p0 = corners.br_inner;
+            segment_rect.size = corners.br_outer - corners.br_inner;
+            vColor0 = border.colors[2];
+            vColor1 = border.colors[3];
+            vClipCenter = corners.br_outer - border.radii[1].xy;
+            vClipSign = vec2(-1.0, -1.0);
+            set_radii(border.radii[1].xy,
+                      border.widths.zw,
+                      corners.br_outer,
+                      vec2(-1.0, -1.0));
+            break;
+        }
+        case 3: {
+            segment_rect.p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
+            segment_rect.size = vec2(corners.bl_inner.x - corners.bl_outer.x,
+                                     corners.bl_outer.y - corners.bl_inner.y);
+            vColor0 = border.colors[3];
+            vColor1 = border.colors[0];
+            vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
+            vClipSign = vec2(1.0, -1.0);
+            set_radii(border.radii[1].zw,
+                      border.widths.xw,
+                      corners.bl_outer,
+                      vec2(1.0, -1.0));
+            break;
+        }
+    }
+
+#ifdef WR_FEATURE_TRANSFORM
+    TransformVertexInfo vi = write_transform_vertex(segment_rect,
+                                                    prim.local_clip_rect,
+                                                    prim.z,
+                                                    prim.layer,
+                                                    prim.task,
+                                                    prim.local_rect.p0);
+    vLocalPos = vi.local_pos;
+    vLocalRect = segment_rect;
+#else
+    VertexInfo vi = write_vertex(segment_rect,
+                                 prim.local_clip_rect,
+                                 prim.z,
+                                 prim.layer,
+                                 prim.task,
+                                 prim.local_rect.p0);
+    vLocalPos = vi.local_pos.xy;
+#endif
+
+    write_clip(vi.screen_pos, prim.clip_area);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_border_edge.fs.glsl
@@ -0,0 +1,15 @@
+/* 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/. */
+
+void main(void) {
+    float alpha = 1.0;
+#ifdef WR_FEATURE_TRANSFORM
+    alpha = 0.0;
+    init_transform_fs(vLocalPos, vLocalRect, alpha);
+#endif
+
+    alpha = min(alpha, do_clip());
+
+    oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -0,0 +1,10 @@
+/* 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/. */
+
+flat varying vec4 vColor;
+
+#ifdef WR_FEATURE_TRANSFORM
+varying vec3 vLocalPos;
+flat varying RectWithSize vLocalRect;
+#endif
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -0,0 +1,53 @@
+#line 1
+/* 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/. */
+
+void main(void) {
+    Primitive prim = load_primitive();
+    Border border = fetch_border(prim.prim_index);
+    int sub_part = prim.sub_index;
+    BorderCorners corners = get_border_corners(border, prim.local_rect);
+
+    vColor = border.colors[sub_part];
+
+    RectWithSize segment_rect;
+    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);
+            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);
+            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);
+            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);
+            break;
+    }
+
+#ifdef WR_FEATURE_TRANSFORM
+    TransformVertexInfo vi = write_transform_vertex(segment_rect,
+                                                    prim.local_clip_rect,
+                                                    prim.z,
+                                                    prim.layer,
+                                                    prim.task,
+                                                    prim.local_rect.p0);
+    vLocalPos = vi.local_pos;
+    vLocalRect = segment_rect;
+#else
+    VertexInfo vi = write_vertex(segment_rect,
+                                 prim.local_clip_rect,
+                                 prim.z,
+                                 prim.layer,
+                                 prim.task,
+                                 prim.local_rect.p0);
+#endif
+
+    write_clip(vi.screen_pos, prim.clip_area);
+}
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -7,17 +7,18 @@ void main(void) {
     Primitive prim = load_primitive();
     BoxShadow bs = fetch_boxshadow(prim.prim_index);
     RectWithSize segment_rect = fetch_instance_geometry(prim.sub_index);
 
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
+                                 prim.task,
+                                 prim.local_rect.p0);
 
     RenderTaskData child_task = fetch_render_task(prim.user_data.x);
     vUv.z = child_task.data1.x;
 
     // Constant offsets to inset from bilinear filtering border.
     vec2 patch_origin = child_task.data0.xy + vec2(1.0);
     vec2 patch_size_device_pixels = child_task.data0.zw - vec2(2.0);
     vec2 patch_size = patch_size_device_pixels / uDevicePixelRatio;
--- a/gfx/webrender/res/ps_cache_image.vs.glsl
+++ b/gfx/webrender/res/ps_cache_image.vs.glsl
@@ -8,17 +8,18 @@
 
 void main(void) {
     Primitive prim = load_primitive();
 
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
+                                 prim.task,
+                                 prim.local_rect.p0);
 
     RenderTaskData child_task = fetch_render_task(prim.user_data.x);
     vUv.z = child_task.data1.x;
 
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vec2 uv0 = child_task.data0.xy / texture_size;
     vec2 uv1 = (child_task.data0.xy + child_task.data0.zw) / texture_size;
 
--- a/gfx/webrender/res/ps_gradient.glsl
+++ b/gfx/webrender/res/ps_gradient.glsl
@@ -1,12 +1,12 @@
 /* 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/. */
 
 varying vec4 vColor;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying vec4 vLocalRect;
+flat varying RectWithSize vLocalRect;
 #else
 varying vec2 vPos;
 #endif
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_gradient.vs.glsl
@@ -56,26 +56,28 @@ void main(void) {
         adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
     }
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
-                                                    prim.task);
-    vLocalRect = vi.clipped_local_rect;
+                                                    prim.task,
+                                                    prim.local_rect.p0);
+    vLocalRect = segment_rect;
     vLocalPos = vi.local_pos;
     vec2 f = (vi.local_pos.xy - prim.local_rect.p0) / prim.local_rect.size;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
+                                 prim.task,
+                                 prim.local_rect.p0);
 
     vec2 f = (vi.local_pos - segment_rect.p0) / segment_rect.size;
     vPos = vi.local_pos;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     vColor = mix(adjusted_color_g0, adjusted_color_g1, dot(f, axis));
--- a/gfx/webrender/res/ps_image.fs.glsl
+++ b/gfx/webrender/res/ps_image.fs.glsl
@@ -6,18 +6,17 @@
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
 
     // We clamp the texture coordinate calculation here to the local rectangle boundaries,
     // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect =
-         clamp(pos, vLocalRect.xy, vLocalRect.xy + vLocalRect.zw) - vLocalRect.xy;
+    vec2 relative_pos_in_rect = clamp_rect(pos, vLocalRect) - vLocalRect.p0;
 #else
     float alpha = 1.0;
     vec2 relative_pos_in_rect = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We calculate the particular tile this fragment belongs to, taking into
--- a/gfx/webrender/res/ps_image.glsl
+++ b/gfx/webrender/res/ps_image.glsl
@@ -7,14 +7,14 @@
 // check GL_TEXTURE_RECTANGLE.
 flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
 flat varying vec2 vTextureSize;   // Size of the image in the texture atlas.
 flat varying vec2 vTileSpacing;   // Amount of space between tiled instances of this image.
 flat varying vec4 vStRect;        // Rectangle of valid texture rect.
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying vec4 vLocalRect;
+flat varying RectWithSize vLocalRect;
 flat varying vec2 vStretchSize;
 #else
 varying vec2 vLocalPos;
 flat varying vec2 vStretchSize;
 #endif
--- a/gfx/webrender/res/ps_image.vs.glsl
+++ b/gfx/webrender/res/ps_image.vs.glsl
@@ -8,26 +8,28 @@ void main(void) {
     Image image = fetch_image(prim.prim_index);
     ResourceRect res = fetch_resource_rect(prim.user_data.x);
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
-                                                    prim.task);
-    vLocalRect = vi.clipped_local_rect;
+                                                    prim.task,
+                                                    prim.local_rect.p0);
+    vLocalRect = prim.local_rect;
     vLocalPos = vi.local_pos;
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
-    vLocalPos = vi.local_pos - vi.local_rect.p0;
+                                 prim.task,
+                                 prim.local_rect.p0);
+    vLocalPos = vi.local_pos - prim.local_rect.p0;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     vTileSpacing = image.stretch_size_and_tile_spacing.zw;
     vStretchSize = image.stretch_size_and_tile_spacing.xy;
 
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
--- a/gfx/webrender/res/ps_radial_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.vs.glsl
@@ -6,17 +6,18 @@
 void main(void) {
     Primitive prim = load_primitive();
     RadialGradient gradient = fetch_radial_gradient(prim.prim_index);
 
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
+                                 prim.task,
+                                 prim.local_rect.p0);
 
     vPos = vi.local_pos - prim.local_rect.p0;
 
     vStartCenter = gradient.start_end_center.xy;
     vEndCenter = gradient.start_end_center.zw;
 
     vStartRadius = gradient.start_end_radius_ratio_xy_extend_mode.x;
     vEndRadius = gradient.start_end_radius_ratio_xy_extend_mode.y;
--- a/gfx/webrender/res/ps_rectangle.glsl
+++ b/gfx/webrender/res/ps_rectangle.glsl
@@ -1,10 +1,10 @@
 /* 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/. */
 
 varying vec4 vColor;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying vec4 vLocalRect;
+flat varying RectWithSize vLocalRect;
 #endif
--- a/gfx/webrender/res/ps_rectangle.vs.glsl
+++ b/gfx/webrender/res/ps_rectangle.vs.glsl
@@ -7,23 +7,25 @@ void main(void) {
     Primitive prim = load_primitive();
     Rectangle rect = fetch_rectangle(prim.prim_index);
     vColor = rect.color;
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
-                                                    prim.task);
-    vLocalRect = vi.clipped_local_rect;
+                                                    prim.task,
+                                                    prim.local_rect.p0);
+    vLocalRect = prim.local_rect;
     vLocalPos = vi.local_pos;
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
+                                 prim.task,
+                                 prim.local_rect.p0);
 #endif
 
 #ifdef WR_FEATURE_CLIP
     write_clip(vi.screen_pos, prim.clip_area);
 #endif
 }
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -3,10 +3,10 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 flat varying vec4 vColor;
 varying vec2 vUv;
 flat varying vec4 vUvBorder;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying vec4 vLocalRect;
+flat varying RectWithSize vLocalRect;
 #endif
--- a/gfx/webrender/res/ps_text_run.vs.glsl
+++ b/gfx/webrender/res/ps_text_run.vs.glsl
@@ -12,27 +12,29 @@ void main(void) {
     RectWithSize local_rect = RectWithSize(glyph.offset.xy,
                                            (res.uv_rect.zw - res.uv_rect.xy) / uDevicePixelRatio);
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
-                                                    prim.task);
-    vLocalRect = vi.clipped_local_rect;
+                                                    prim.task,
+                                                    local_rect.p0);
+    vLocalRect = local_rect;
     vLocalPos = vi.local_pos;
     vec2 f = (vi.local_pos.xy / vi.local_pos.z - local_rect.p0) / local_rect.size;
 #else
     VertexInfo vi = write_vertex(local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
-    vec2 f = (vi.local_pos - vi.local_rect.p0) / (vi.local_rect.p1 - vi.local_rect.p0);
+                                 prim.task,
+                                 local_rect.p0);
+    vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vec2 st0 = res.uv_rect.xy / texture_size;
     vec2 st1 = res.uv_rect.zw / texture_size;
 
--- a/gfx/webrender/res/ps_yuv_image.fs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.fs.glsl
@@ -5,18 +5,17 @@
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
 
     // We clamp the texture coordinate calculation here to the local rectangle boundaries,
     // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect =
-         clamp(pos, vLocalRect.xy, vLocalRect.xy + vLocalRect.zw) - vLocalRect.xy;
+    vec2 relative_pos_in_rect = clamp_rect(pos, vLocalRect) - vLocalRect.p0;
 #else
     float alpha = 1.0;;
     vec2 relative_pos_in_rect = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We clamp the texture coordinates to the half-pixel offset from the borders
--- a/gfx/webrender/res/ps_yuv_image.glsl
+++ b/gfx/webrender/res/ps_yuv_image.glsl
@@ -10,12 +10,12 @@ flat varying vec2 vTextureSizeUv;  // Si
 flat varying vec2 vStretchSize;
 flat varying vec2 vHalfTexelY;     // Normalized length of the half of a Y texel.
 flat varying vec2 vHalfTexelUv;    // Normalized length of the half of u and v texels.
 
 flat varying mat3 vYuvColorMatrix;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying vec4 vLocalRect;
+flat varying RectWithSize vLocalRect;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_yuv_image.vs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.vs.glsl
@@ -5,26 +5,28 @@
 
 void main(void) {
     Primitive prim = load_primitive();
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
-                                                    prim.task);
-    vLocalRect = vi.clipped_local_rect;
+                                                    prim.task,
+                                                    prim.local_rect.p0);
+    vLocalRect = prim.local_rect;
     vLocalPos = vi.local_pos;
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
-                                 prim.task);
-    vLocalPos = vi.local_pos - vi.local_rect.p0;
+                                 prim.task,
+                                 prim.local_rect.p0);
+    vLocalPos = vi.local_pos - prim.local_rect.p0;
 #endif
 
     YuvImage image = fetch_yuv_image(prim.prim_index);
     ResourceRect y_rect = fetch_resource_rect(prim.user_data.x);
     ResourceRect u_rect = fetch_resource_rect(prim.user_data.x + 1);
     ResourceRect v_rect = fetch_resource_rect(prim.user_data.x + 2);
 
     vec2 y_texture_size = vec2(textureSize(sColor0, 0));
deleted file mode 100644
--- a/gfx/webrender/src/CLIPPING.md
+++ /dev/null
@@ -1,98 +0,0 @@
-# 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.
-
-```rust
-pub struct ClipRegion {
-    pub main: LayoutRect,
-    pub complex: ItemRange,
-    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.
-
-## Item Clips
-
-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
-
-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 `scroll_layer_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 clip display item has a `ClipRegion` as well as several other fields:
-
-```rust
-pub struct ClipDisplayItem {
-    pub id: ScrollLayerId,
-    pub parent_id: ScrollLayerId,
-}
-```
-
-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
-
-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 `ScrollLayerId::new(...)`. If this argument is not
-provided `define_clip` will return a uniquely generated id. Thus, the following
-should always be true:
-
-```rust
-let id = ScrollLayerId::new(my_internal_id, pipeline_id);
-let generated_id = define_clip(content_rect, clip, id);
-assert!(id == generated_id);
-```
-
-## Pending changes
-
-1. Rename `ScrollLayerId` to `ClipId`. The current name is a holdover from the
-   previous design.  ([github issue](https://github.com/servo/webrender/issues/1089))
-
-2. 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))
-
-3. 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))
-
deleted file mode 100644
--- a/gfx/webrender/src/batch_builder.rs
+++ /dev/null
@@ -1,43 +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/. */
-
-use std::f32;
-use webrender_traits::{ColorF, BorderStyle};
-use webrender_traits::{BorderSide};
-
-//const BORDER_DASH_SIZE: f32 = 3.0;
-
-pub trait BorderSideHelpers {
-    fn border_color(&self,
-                    scale_factor_0: f32,
-                    scale_factor_1: f32,
-                    black_color_0: f32,
-                    black_color_1: f32) -> ColorF;
-}
-
-impl BorderSideHelpers for BorderSide {
-    fn border_color(&self,
-                    scale_factor_0: f32,
-                    scale_factor_1: f32,
-                    black_color_0: f32,
-                    black_color_1: f32) -> ColorF {
-        match self.style {
-            BorderStyle::Inset => {
-                if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 {
-                    self.color.scale_rgb(scale_factor_1)
-                } else {
-                    ColorF::new(black_color_0, black_color_0, black_color_0, self.color.a)
-                }
-            }
-            BorderStyle::Outset => {
-                if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 {
-                    self.color.scale_rgb(scale_factor_0)
-                } else {
-                    ColorF::new(black_color_1, black_color_1, black_color_1, self.color.a)
-                }
-            }
-            _ => self.color,
-        }
-    }
-}
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,29 +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 frame_builder::FrameBuilder;
+use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, PrimitiveContainer};
 use tiling::PrimitiveFlags;
-use webrender_traits::{BorderSide, BorderStyle, BorderWidths, NormalBorder};
-use webrender_traits::{ClipRegion, LayerPoint, LayerRect, LayerSize, ScrollLayerId};
+use util::pack_as_float;
+use webrender_traits::{BorderSide, BorderStyle, BorderWidths, ColorF, NormalBorder};
+use webrender_traits::{ClipId, ClipRegion, LayerPoint, LayerRect, LayerSize};
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderCornerKind {
     None,
     Solid,
-    Complex,
+    Clip,
+    Unhandled,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderEdgeKind {
     None,
     Solid,
-    Complex,
+    Unhandled,
 }
 
 pub trait NormalBorderHelpers {
     fn get_corner(&self,
                   edge0: &BorderSide,
                   width0: f32,
                   edge1: &BorderSide,
                   width1: f32,
@@ -57,30 +60,35 @@ impl NormalBorderHelpers for NormalBorde
             (BorderStyle::Hidden, _) | (_, BorderStyle::Hidden) => BorderCornerKind::None,
 
             // 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::Complex
+                    BorderCornerKind::Clip
                 }
             }
 
+            // Inset / outset borders just modtify the color of edges, so can be
+            // drawn with the normal border corner shader.
+            (BorderStyle::Outset, BorderStyle::Outset) |
+            (BorderStyle::Inset, BorderStyle::Inset) => BorderCornerKind::Clip,
+
             // Assume complex for these cases.
             // TODO(gw): There are some cases in here that can be handled with a fast path.
             // For example, with inset/outset borders, two of the four corners are solid.
-            (BorderStyle::Dotted, _) | (_, BorderStyle::Dotted) => BorderCornerKind::Complex,
-            (BorderStyle::Dashed, _) | (_, BorderStyle::Dashed) => BorderCornerKind::Complex,
-            (BorderStyle::Double, _) | (_, BorderStyle::Double) => BorderCornerKind::Complex,
-            (BorderStyle::Groove, _) | (_, BorderStyle::Groove) => BorderCornerKind::Complex,
-            (BorderStyle::Ridge, _) | (_, BorderStyle::Ridge) => BorderCornerKind::Complex,
-            (BorderStyle::Outset, _) | (_, BorderStyle::Outset) => BorderCornerKind::Complex,
-            (BorderStyle::Inset, _) | (_, BorderStyle::Inset) => BorderCornerKind::Complex,
+            (BorderStyle::Dotted, _) | (_, BorderStyle::Dotted) => BorderCornerKind::Unhandled,
+            (BorderStyle::Dashed, _) | (_, BorderStyle::Dashed) => BorderCornerKind::Unhandled,
+            (BorderStyle::Double, _) | (_, BorderStyle::Double) => BorderCornerKind::Unhandled,
+            (BorderStyle::Groove, _) | (_, BorderStyle::Groove) => BorderCornerKind::Unhandled,
+            (BorderStyle::Ridge, _) | (_, BorderStyle::Ridge) => BorderCornerKind::Unhandled,
+            (BorderStyle::Outset, _) | (_, BorderStyle::Outset) => BorderCornerKind::Unhandled,
+            (BorderStyle::Inset, _) | (_, BorderStyle::Inset) => BorderCornerKind::Unhandled,
         }
     }
 
     fn get_edge(&self,
                 edge: &BorderSide,
                 width: f32) -> (BorderEdgeKind, f32) {
         if width == 0.0 {
             return (BorderEdgeKind::None, 0.0);
@@ -93,110 +101,225 @@ impl NormalBorderHelpers for NormalBorde
             BorderStyle::Solid |
             BorderStyle::Inset |
             BorderStyle::Outset => (BorderEdgeKind::Solid, width),
 
             BorderStyle::Double |
             BorderStyle::Dotted |
             BorderStyle::Dashed |
             BorderStyle::Groove |
-            BorderStyle::Ridge => (BorderEdgeKind::Complex, width),
+            BorderStyle::Ridge => (BorderEdgeKind::Unhandled, width),
         }
     }
 }
 
 impl FrameBuilder {
+    fn add_normal_border_primitive(&mut self,
+                                   rect: &LayerRect,
+                                   border: &NormalBorder,
+                                   widths: &BorderWidths,
+                                   clip_id: ClipId,
+                                   clip_region: &ClipRegion,
+                                   use_new_border_path: bool) {
+        let radius = &border.radius;
+        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);
+        let top_color       = top.border_color(1.0, 2.0/3.0, 0.3, 0.7);
+        let right_color     = right.border_color(2.0/3.0, 1.0, 0.7, 0.3);
+        let bottom_color    = bottom.border_color(2.0/3.0, 1.0, 0.7, 0.3);
+
+        let prim_cpu = BorderPrimitiveCpu {
+            use_new_border_path: use_new_border_path,
+        };
+
+        let prim_gpu = BorderPrimitiveGpu {
+            colors: [ left_color, top_color, right_color, bottom_color ],
+            widths: [ widths.left,
+                      widths.top,
+                      widths.right,
+                      widths.bottom ],
+            style: [
+                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),
+            ],
+            radii: [
+                radius.top_left,
+                radius.top_right,
+                radius.bottom_right,
+                radius.bottom_left,
+            ],
+        };
+
+        self.add_primitive(clip_id,
+                           &rect,
+                           clip_region,
+                           &[],
+                           PrimitiveContainer::Border(prim_cpu, prim_gpu));
+    }
+
     // 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_simple_border(&mut self,
+    pub fn add_normal_border(&mut self,
                              rect: &LayerRect,
                              border: &NormalBorder,
                              widths: &BorderWidths,
-                             scroll_layer_id: ScrollLayerId,
-                             clip_region: &ClipRegion) -> bool {
+                             clip_id: ClipId,
+                             clip_region: &ClipRegion) {
         // 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 radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
-        // If any of the corners are complex, fall back to slow path for now.
-        let tl = border.get_corner(left, widths.left, top, widths.top, &radius.top_left);
-        let tr = border.get_corner(top, widths.top, right, widths.right, &radius.top_right);
-        let br = border.get_corner(right, widths.right, bottom, widths.bottom, &radius.bottom_right);
-        let bl = border.get_corner(bottom, widths.bottom, left, widths.left, &radius.bottom_left);
+        let corners = [
+            border.get_corner(left, widths.left, top, widths.top, &radius.top_left),
+            border.get_corner(top, widths.top, right, widths.right, &radius.top_right),
+            border.get_corner(right, widths.right, bottom, widths.bottom, &radius.bottom_right),
+            border.get_corner(bottom, widths.bottom, left, widths.left, &radius.bottom_left),
+        ];
 
-        if tl == BorderCornerKind::Complex ||
-           tr == BorderCornerKind::Complex ||
-           br == BorderCornerKind::Complex ||
-           bl == BorderCornerKind::Complex {
-            return false;
+        // If any of the corners are unhandled, fall back to slow path for now.
+        if corners.iter().any(|c| *c == BorderCornerKind::Unhandled) {
+            self.add_normal_border_primitive(rect,
+                                             border,
+                                             widths,
+                                             clip_id,
+                                             clip_region,
+                                             false);
+            return;
         }
 
-        // If any of the edges are complex, fall back to slow path for now.
         let (left_edge, left_len) = border.get_edge(left, widths.left);
         let (top_edge, top_len) = border.get_edge(top, widths.top);
         let (right_edge, right_len) = border.get_edge(right, widths.right);
         let (bottom_edge, bottom_len) = border.get_edge(bottom, widths.bottom);
 
-        if left_edge == BorderEdgeKind::Complex ||
-           top_edge == BorderEdgeKind::Complex ||
-           right_edge == BorderEdgeKind::Complex ||
-           bottom_edge == BorderEdgeKind::Complex {
-            return false;
+        let edges = [
+            left_edge,
+            top_edge,
+            right_edge,
+            bottom_edge,
+        ];
+
+        // If any of the edges are unhandled, fall back to slow path for now.
+        if edges.iter().any(|e| *e == BorderEdgeKind::Unhandled) {
+            self.add_normal_border_primitive(rect,
+                                             border,
+                                             widths,
+                                             clip_id,
+                                             clip_region,
+                                             false);
+            return;
         }
 
-        let p0 = rect.origin;
-        let p1 = rect.bottom_right();
-        let rect_width = rect.size.width;
-        let rect_height = rect.size.height;
+        // 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
+        });
+
+        if all_corners_simple && all_edges_simple {
+            let p0 = rect.origin;
+            let p1 = rect.bottom_right();
+            let rect_width = rect.size.width;
+            let rect_height = rect.size.height;
 
-        // Add a solid rectangle for each visible edge/corner combination.
-        if top_edge == BorderEdgeKind::Solid {
-            self.add_solid_rectangle(scroll_layer_id,
-                                     &LayerRect::new(p0,
-                                                     LayerSize::new(rect.size.width, top_len)),
-                                     clip_region,
-                                     &border.top.color,
-                                     PrimitiveFlags::None);
+            // Add a solid rectangle for each visible edge/corner combination.
+            if top_edge == BorderEdgeKind::Solid {
+                self.add_solid_rectangle(clip_id,
+                                         &LayerRect::new(p0,
+                                                         LayerSize::new(rect_width, top_len)),
+                                         clip_region,
+                                         &border.top.color,
+                                         PrimitiveFlags::None);
+            }
+            if left_edge == BorderEdgeKind::Solid {
+                self.add_solid_rectangle(clip_id,
+                                         &LayerRect::new(LayerPoint::new(p0.x, p0.y + top_len),
+                                                         LayerSize::new(left_len,
+                                                                        rect_height - top_len - bottom_len)),
+                                         clip_region,
+                                         &border.left.color,
+                                         PrimitiveFlags::None);
+            }
+            if right_edge == BorderEdgeKind::Solid {
+                self.add_solid_rectangle(clip_id,
+                                         &LayerRect::new(LayerPoint::new(p1.x - right_len,
+                                                                         p0.y + top_len),
+                                                         LayerSize::new(right_len,
+                                                                        rect_height - top_len - bottom_len)),
+                                         clip_region,
+                                         &border.right.color,
+                                         PrimitiveFlags::None);
+            }
+            if bottom_edge == BorderEdgeKind::Solid {
+                self.add_solid_rectangle(clip_id,
+                                         &LayerRect::new(LayerPoint::new(p0.x, p1.y - bottom_len),
+                                                         LayerSize::new(rect_width, bottom_len)),
+                                         clip_region,
+                                         &border.bottom.color,
+                                         PrimitiveFlags::None);
+            }
+        } else {
+            self.add_normal_border_primitive(rect,
+                                             border,
+                                             widths,
+                                             clip_id,
+                                             clip_region,
+                                             true);
         }
-        if left_edge == BorderEdgeKind::Solid {
-            self.add_solid_rectangle(scroll_layer_id,
-                                     &LayerRect::new(LayerPoint::new(p0.x, p0.y + top_len),
-                                                     LayerSize::new(left_len,
-                                                                    rect_height - top_len - bottom_len)),
-                                     clip_region,
-                                     &border.left.color,
-                                     PrimitiveFlags::None);
-        }
-        if right_edge == BorderEdgeKind::Solid {
-            self.add_solid_rectangle(scroll_layer_id,
-                                     &LayerRect::new(LayerPoint::new(p1.x - right_len,
-                                                                     p0.y + top_len),
-                                                     LayerSize::new(right_len,
-                                                                    rect_height - top_len - bottom_len)),
-                                     clip_region,
-                                     &border.right.color,
-                                     PrimitiveFlags::None);
-        }
-        if bottom_edge == BorderEdgeKind::Solid {
-            self.add_solid_rectangle(scroll_layer_id,
-                                     &LayerRect::new(LayerPoint::new(p0.x, p1.y - bottom_len),
-                                                     LayerSize::new(rect_width, bottom_len)),
-                                     clip_region,
-                                     &border.bottom.color,
-                                     PrimitiveFlags::None);
-        }
-
-        true
     }
 }
+
+pub trait BorderSideHelpers {
+    fn border_color(&self,
+                    scale_factor_0: f32,
+                    scale_factor_1: f32,
+                    black_color_0: f32,
+                    black_color_1: f32) -> ColorF;
+}
+
+impl BorderSideHelpers for BorderSide {
+    fn border_color(&self,
+                    scale_factor_0: f32,
+                    scale_factor_1: f32,
+                    black_color_0: f32,
+                    black_color_1: f32) -> ColorF {
+        match self.style {
+            BorderStyle::Inset => {
+                if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 {
+                    self.color.scale_rgb(scale_factor_1)
+                } else {
+                    ColorF::new(black_color_0, black_color_0, black_color_0, self.color.a)
+                }
+            }
+            BorderStyle::Outset => {
+                if self.color.r != 0.0 || self.color.g != 0.0 || self.color.b != 0.0 {
+                    self.color.scale_rgb(scale_factor_0)
+                } else {
+                    ColorF::new(black_color_1, black_color_1, black_color_1, self.color.a)
+                }
+            }
+            _ => self.color,
+        }
+    }
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -5,20 +5,20 @@
 use euclid::Point3D;
 use geometry::ray_intersects_rect;
 use mask_cache::{ClipSource, MaskCacheInfo, RegionMode};
 use prim_store::GpuBlock32;
 use renderer::VertexDataStore;
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::TransformedRect;
-use webrender_traits::{ClipRegion, LayerPixel, LayerPoint, LayerRect, LayerSize};
+use webrender_traits::{ClipId, ClipRegion, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use webrender_traits::{LayerToScrollTransform, LayerToWorldTransform, PipelineId};
-use webrender_traits::{ScrollEventPhase, ScrollLayerId, ScrollLayerRect, ScrollLocation};
-use webrender_traits::{WorldPoint, WorldPoint4D};
+use webrender_traits::{ScrollEventPhase, ScrollLayerRect, ScrollLocation, WorldPoint};
+use webrender_traits::WorldPoint4D;
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
 #[derive(Clone, Debug)]
@@ -101,28 +101,28 @@ pub struct ClipScrollNode {
 
     /// World transform for content transformed by this node.
     pub world_content_transform: LayerToWorldTransform,
 
     /// Pipeline that this layer belongs to
     pub pipeline_id: PipelineId,
 
     /// Parent layer. If this is None, we are the root node.
-    pub parent: Option<ScrollLayerId>,
+    pub parent: Option<ClipId>,
 
     /// Child layers
-    pub children: Vec<ScrollLayerId>,
+    pub children: Vec<ClipId>,
 
     /// Whether or not this node is a reference frame.
     pub node_type: NodeType,
 }
 
 impl ClipScrollNode {
     pub fn new(pipeline_id: PipelineId,
-               parent_id: ScrollLayerId,
+               parent_id: ClipId,
                content_rect: &LayerRect,
                clip_rect: &LayerRect,
                clip_info: ClipInfo)
                -> ClipScrollNode {
         // FIXME(mrobinson): We don't yet handle clipping rectangles that don't start at the origin
         // of the node.
         let local_viewport_rect = LayerRect::new(content_rect.origin, clip_rect.size);
         ClipScrollNode {
@@ -135,17 +135,17 @@ impl ClipScrollNode {
             world_content_transform: LayerToWorldTransform::identity(),
             parent: Some(parent_id),
             children: Vec::new(),
             pipeline_id: pipeline_id,
             node_type: NodeType::Clip(clip_info),
         }
     }
 
-    pub fn new_reference_frame(parent_id: Option<ScrollLayerId>,
+    pub fn new_reference_frame(parent_id: Option<ClipId>,
                                local_viewport_rect: &LayerRect,
                                content_size: LayerSize,
                                local_transform: &LayerToScrollTransform,
                                pipeline_id: PipelineId)
                                -> ClipScrollNode {
         ClipScrollNode {
             scrolling: ScrollingState::new(),
             content_size: content_size,
@@ -156,17 +156,17 @@ impl ClipScrollNode {
             world_content_transform: LayerToWorldTransform::identity(),
             parent: parent_id,
             children: Vec::new(),
             pipeline_id: pipeline_id,
             node_type: NodeType::ReferenceFrame(*local_transform),
         }
     }
 
-    pub fn add_child(&mut self, child: ScrollLayerId) {
+    pub fn add_child(&mut self, child: ClipId) {
         self.children.push(child);
     }
 
     pub fn finalize(&mut self, scrolling: &ScrollingState) {
         self.scrolling = *scrolling;
     }
 
     pub fn overscroll_amount(&self) -> LayerSize {
@@ -217,18 +217,18 @@ impl ClipScrollNode {
         self.scrolling.bouncing_back = false;
         self.scrolling.started_bouncing_back = false;
         true
     }
 
     pub fn update_transform(&mut self,
                             parent_reference_frame_transform: &LayerToWorldTransform,
                             parent_combined_viewport_rect: &ScrollLayerRect,
+                            parent_scroll_offset: LayerPoint,
                             parent_accumulated_scroll_offset: LayerPoint) {
-
         let local_transform = match self.node_type {
             NodeType::ReferenceFrame(transform) => transform,
             NodeType::Clip(_) => LayerToScrollTransform::identity(),
         };
 
         let inv_transform = match local_transform.inverse() {
             Some(transform) => transform,
             None => {
@@ -236,21 +236,21 @@ impl ClipScrollNode {
                 // to be non-invertible, the object and its content do not get displayed.
                 self.combined_local_viewport_rect = LayerRect::zero();
                 return;
             }
         };
 
         // We are trying to move the combined viewport rectangle of our parent nodes into the
         // coordinate system of this node, so we must invert our transformation (only for
-        // reference frames) and then apply the scroll offset of all the parent layers.
+        // reference frames) and then apply the scroll offset the parent layer. The combined
+        // local viewport rect doesn't include scrolling offsets so the only one that matters
+        // is the relative offset between us and the parent.
         let parent_combined_viewport_in_local_space =
-            inv_transform.pre_translated(-parent_accumulated_scroll_offset.x,
-                                         -parent_accumulated_scroll_offset.y,
-                                         0.0)
+            inv_transform.pre_translated(-parent_scroll_offset.x, -parent_scroll_offset.y, 0.0)
                          .transform_rect(parent_combined_viewport_rect);
 
         // Now that we have the combined viewport rectangle of the parent nodes in local space,
         // we do the intersection and get our combined viewport rect in the coordinate system
         // starting from our origin.
         self.combined_local_viewport_rect = match self.node_type {
             NodeType::Clip(_) => {
                 parent_combined_viewport_in_local_space.intersection(&self.local_clip_rect)
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,114 +1,114 @@
 /* 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 clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
 use fnv::FnvHasher;
 use std::collections::{HashMap, HashSet};
 use std::hash::BuildHasherDefault;
-use webrender_traits::{LayerPoint, LayerRect, LayerToScrollTransform, LayerToWorldTransform};
-use webrender_traits::{PipelineId, ScrollEventPhase, ScrollLayerId, ScrollLayerRect};
+use webrender_traits::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform};
+use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollEventPhase, ScrollLayerRect};
 use webrender_traits::{ScrollLayerState, ScrollLocation, WorldPoint, as_scroll_parent_rect};
 
-pub type ScrollStates = HashMap<ScrollLayerId, ScrollingState, BuildHasherDefault<FnvHasher>>;
+pub type ScrollStates = HashMap<ClipId, ScrollingState, BuildHasherDefault<FnvHasher>>;
 
 pub struct ClipScrollTree {
-    pub nodes: HashMap<ScrollLayerId, ClipScrollNode, BuildHasherDefault<FnvHasher>>,
-    pub pending_scroll_offsets: HashMap<ScrollLayerId, LayerPoint>,
+    pub nodes: HashMap<ClipId, ClipScrollNode, BuildHasherDefault<FnvHasher>>,
+    pub pending_scroll_offsets: HashMap<ClipId, LayerPoint>,
 
-    /// The ScrollLayerId of the currently scrolling node. Used to allow the same
+    /// The ClipId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
-    pub current_scroll_layer_id: Option<ScrollLayerId>,
+    pub currently_scrolling_node_id: Option<ClipId>,
 
     /// The current reference frame id, used for giving a unique id to all new
     /// reference frames. The ClipScrollTree increments this by one every time a
     /// reference frame is created.
     current_reference_frame_id: u64,
 
     /// The root reference frame, which is the true root of the ClipScrollTree. Initially
     /// this ID is not valid, which is indicated by ```node``` being empty.
-    pub root_reference_frame_id: ScrollLayerId,
+    pub root_reference_frame_id: ClipId,
 
     /// The root scroll node which is the first child of the root reference frame.
     /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
-    pub topmost_scroll_layer_id: ScrollLayerId,
+    pub topmost_scrolling_node_id: ClipId,
 
     /// A set of pipelines which should be discarded the next time this
     /// tree is drained.
     pub pipelines_to_discard: HashSet<PipelineId>,
 }
 
 impl ClipScrollTree {
     pub fn new() -> ClipScrollTree {
         let dummy_pipeline = PipelineId(0, 0);
         ClipScrollTree {
-            nodes: HashMap::with_hasher(Default::default()),
+            nodes: HashMap::default(),
             pending_scroll_offsets: HashMap::new(),
-            current_scroll_layer_id: None,
-            root_reference_frame_id: ScrollLayerId::root_reference_frame(dummy_pipeline),
-            topmost_scroll_layer_id: ScrollLayerId::root_scroll_layer(dummy_pipeline),
+            currently_scrolling_node_id: None,
+            root_reference_frame_id: ClipId::root_reference_frame(dummy_pipeline),
+            topmost_scrolling_node_id: ClipId::root_scroll_node(dummy_pipeline),
             current_reference_frame_id: 0,
             pipelines_to_discard: HashSet::new(),
         }
     }
 
-    pub fn root_reference_frame_id(&self) -> ScrollLayerId {
+    pub fn root_reference_frame_id(&self) -> ClipId {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(!self.nodes.is_empty());
         debug_assert!(self.nodes.contains_key(&self.root_reference_frame_id));
         self.root_reference_frame_id
     }
 
-    pub fn topmost_scroll_layer_id(&self) -> ScrollLayerId {
+    pub fn topmost_scrolling_node_id(&self) -> ClipId {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(!self.nodes.is_empty());
-        debug_assert!(self.nodes.contains_key(&self.topmost_scroll_layer_id));
-        self.topmost_scroll_layer_id
+        debug_assert!(self.nodes.contains_key(&self.topmost_scrolling_node_id));
+        self.topmost_scrolling_node_id
     }
 
     pub fn collect_nodes_bouncing_back(&self)
-                                       -> HashSet<ScrollLayerId, BuildHasherDefault<FnvHasher>> {
-        let mut nodes_bouncing_back = HashSet::with_hasher(Default::default());
-        for (scroll_layer_id, node) in self.nodes.iter() {
+                                       -> HashSet<ClipId, BuildHasherDefault<FnvHasher>> {
+        let mut nodes_bouncing_back = HashSet::default();
+        for (clip_id, node) in self.nodes.iter() {
             if node.scrolling.bouncing_back {
-                nodes_bouncing_back.insert(*scroll_layer_id);
+                nodes_bouncing_back.insert(*clip_id);
             }
         }
         nodes_bouncing_back
     }
 
     fn find_scrolling_node_at_point_in_node(&self,
                                             cursor: &WorldPoint,
-                                            scroll_layer_id: ScrollLayerId)
-                                            -> Option<ScrollLayerId> {
-        self.nodes.get(&scroll_layer_id).and_then(|node| {
+                                            clip_id: ClipId)
+                                            -> Option<ClipId> {
+        self.nodes.get(&clip_id).and_then(|node| {
             for child_layer_id in node.children.iter().rev() {
             if let Some(layer_id) =
                 self.find_scrolling_node_at_point_in_node(cursor, *child_layer_id) {
                     return Some(layer_id);
                 }
             }
 
-            if scroll_layer_id.is_reference_frame() {
+            if clip_id.is_reference_frame() {
                 return None;
             }
 
             if node.ray_intersects_node(cursor) {
-                Some(scroll_layer_id)
+                Some(clip_id)
             } else {
                 None
             }
         })
     }
 
-    pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ScrollLayerId {
+    pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipId {
         self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_id())
-            .unwrap_or(self.topmost_scroll_layer_id())
+            .unwrap_or(self.topmost_scrolling_node_id())
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
         let mut result = vec![];
         for (id, node) in self.nodes.iter() {
             match node.node_type {
                 NodeType::Clip(_) => result.push(
                     ScrollLayerState { id: *id, scroll_offset: node.scrolling.offset }),
@@ -116,28 +116,28 @@ impl ClipScrollTree {
             }
         }
         result
     }
 
     pub fn drain(&mut self) -> ScrollStates {
         self.current_reference_frame_id = 1;
 
-        let mut scroll_states = HashMap::with_hasher(Default::default());
+        let mut scroll_states = HashMap::default();
         for (layer_id, old_node) in &mut self.nodes.drain() {
             if !self.pipelines_to_discard.contains(&layer_id.pipeline_id()) {
                 scroll_states.insert(layer_id, old_node.scrolling);
             }
         }
 
         self.pipelines_to_discard.clear();
         scroll_states
     }
 
-    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ScrollLayerId) -> bool {
+    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ClipId) -> bool {
         if id.is_reference_frame() {
             warn!("Tried to scroll a reference frame.");
             return false;
         }
 
         if self.nodes.is_empty() {
             self.pending_scroll_offsets.insert(id, origin);
             return false;
@@ -156,194 +156,204 @@ impl ClipScrollTree {
                   scroll_location: ScrollLocation,
                   cursor: WorldPoint,
                   phase: ScrollEventPhase)
                   -> bool {
         if self.nodes.is_empty() {
             return false;
         }
 
-        let scroll_layer_id = match (
+        let clip_id = match (
             phase,
             self.find_scrolling_node_at_point(&cursor),
-            self.current_scroll_layer_id) {
+            self.currently_scrolling_node_id) {
             (ScrollEventPhase::Start, scroll_node_at_point_id, _) => {
-                self.current_scroll_layer_id = Some(scroll_node_at_point_id);
+                self.currently_scrolling_node_id = Some(scroll_node_at_point_id);
                 scroll_node_at_point_id
             },
-            (_, scroll_node_at_point_id, Some(cached_scroll_layer_id)) => {
-                let scroll_layer_id = match self.nodes.get(&cached_scroll_layer_id) {
-                    Some(_) => cached_scroll_layer_id,
+            (_, scroll_node_at_point_id, Some(cached_clip_id)) => {
+                let clip_id = match self.nodes.get(&cached_clip_id) {
+                    Some(_) => cached_clip_id,
                     None => {
-                        self.current_scroll_layer_id = Some(scroll_node_at_point_id);
+                        self.currently_scrolling_node_id = Some(scroll_node_at_point_id);
                         scroll_node_at_point_id
                     },
                 };
-                scroll_layer_id
+                clip_id
             },
             (_, _, None) => return false,
         };
 
-        let topmost_scroll_layer_id = self.topmost_scroll_layer_id();
-        let non_root_overscroll = if scroll_layer_id != topmost_scroll_layer_id {
+        let topmost_scrolling_node_id = self.topmost_scrolling_node_id();
+        let non_root_overscroll = if clip_id != topmost_scrolling_node_id {
             // true if the current node is overscrolling,
             // and it is not the root scroll node.
-            let child_node = self.nodes.get(&scroll_layer_id).unwrap();
+            let child_node = self.nodes.get(&clip_id).unwrap();
             let overscroll_amount = child_node.overscroll_amount();
             overscroll_amount.width != 0.0 || overscroll_amount.height != 0.0
         } else {
             false
         };
 
         let switch_node = match phase {
             ScrollEventPhase::Start => {
                 // if this is a new gesture, we do not switch node,
                 // however we do save the state of non_root_overscroll,
                 // for use in the subsequent Move phase.
-                let mut current_node = self.nodes.get_mut(&scroll_layer_id).unwrap();
+                let mut current_node = self.nodes.get_mut(&clip_id).unwrap();
                 current_node.scrolling.should_handoff_scroll = non_root_overscroll;
                 false
             },
             ScrollEventPhase::Move(_) => {
                 // Switch node if movement originated in a new gesture,
                 // from a non root node in overscroll.
-                let current_node = self.nodes.get_mut(&scroll_layer_id).unwrap();
+                let current_node = self.nodes.get_mut(&clip_id).unwrap();
                 current_node.scrolling.should_handoff_scroll && non_root_overscroll
             },
             ScrollEventPhase::End => {
                 // clean-up when gesture ends.
-                let mut current_node = self.nodes.get_mut(&scroll_layer_id).unwrap();
+                let mut current_node = self.nodes.get_mut(&clip_id).unwrap();
                 current_node.scrolling.should_handoff_scroll = false;
                 false
             },
         };
 
-        let scroll_layer_id = if switch_node {
-            topmost_scroll_layer_id
+        let clip_id = if switch_node {
+            topmost_scrolling_node_id
         } else {
-            scroll_layer_id
+            clip_id
         };
 
-        self.nodes.get_mut(&scroll_layer_id).unwrap().scroll(scroll_location, phase)
+        self.nodes.get_mut(&clip_id).unwrap().scroll(scroll_location, phase)
     }
 
     pub fn update_all_node_transforms(&mut self, pan: LayerPoint) {
         if self.nodes.is_empty() {
             return;
         }
 
         let root_reference_frame_id = self.root_reference_frame_id();
         let root_viewport = self.nodes[&root_reference_frame_id].local_clip_rect;
         self.update_node_transform(root_reference_frame_id,
                                    &LayerToWorldTransform::create_translation(pan.x, pan.y, 0.0),
                                    &as_scroll_parent_rect(&root_viewport),
+                                   LayerPoint::zero(),
                                    LayerPoint::zero());
     }
 
     fn update_node_transform(&mut self,
-                             layer_id: ScrollLayerId,
+                             layer_id: ClipId,
                              parent_reference_frame_transform: &LayerToWorldTransform,
                              parent_viewport_rect: &ScrollLayerRect,
+                             parent_scroll_offset: LayerPoint,
                              parent_accumulated_scroll_offset: LayerPoint) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
-        let (reference_frame_transform, viewport_rect, accumulated_scroll_offset, node_children) = {
+        let (reference_frame_transform,
+             viewport_rect,
+             scroll_offset,
+             accumulated_scroll_offset,
+             node_children) = {
             match self.nodes.get_mut(&layer_id) {
                 Some(node) => {
                     node.update_transform(parent_reference_frame_transform,
                                           parent_viewport_rect,
+                                          parent_scroll_offset,
                                           parent_accumulated_scroll_offset);
 
                     // The transformation we are passing is the transformation of the parent
                     // reference frame and the offset is the accumulated offset of all the nodes
                     // between us and the parent reference frame. If we are a reference frame,
                     // we need to reset both these values.
-                    let (transform, offset) = match node.node_type {
+                    let (transform, offset, accumulated_scroll_offset) = match node.node_type {
                         NodeType::ReferenceFrame(..) =>
-                            (node.world_viewport_transform, LayerPoint::zero()),
+                            (node.world_viewport_transform, LayerPoint::zero(), LayerPoint::zero()),
                         NodeType::Clip(_) => {
                             (*parent_reference_frame_transform,
-                             parent_accumulated_scroll_offset + node.scrolling.offset)
+                             node.scrolling.offset,
+                             node.scrolling.offset + parent_accumulated_scroll_offset)
                         }
                     };
 
                     (transform,
                      as_scroll_parent_rect(&node.combined_local_viewport_rect),
                      offset,
+                     accumulated_scroll_offset,
                      node.children.clone())
                 }
                 None => return,
             }
         };
 
         for child_layer_id in node_children {
             self.update_node_transform(child_layer_id,
                                        &reference_frame_transform,
                                        &viewport_rect,
+                                       scroll_offset,
                                        accumulated_scroll_offset);
         }
     }
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
         for (_, node) in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
         // TODO(gw): These are all independent - can be run through thread pool if it shows up
         // in the profile!
-        for (scroll_layer_id, node) in &mut self.nodes {
-            let scrolling_state = match old_states.get(scroll_layer_id) {
+        for (clip_id, node) in &mut self.nodes {
+            let scrolling_state = match old_states.get(clip_id) {
                 Some(old_scrolling_state) => *old_scrolling_state,
                 None => ScrollingState::new(),
             };
 
             node.finalize(&scrolling_state);
 
-            if let Some(pending_offset) = self.pending_scroll_offsets.remove(scroll_layer_id) {
+            if let Some(pending_offset) = self.pending_scroll_offsets.remove(clip_id) {
                 node.set_scroll_origin(&pending_offset);
             }
         }
 
     }
 
     pub fn add_reference_frame(&mut self,
                                rect: &LayerRect,
                                transform: &LayerToScrollTransform,
                                pipeline_id: PipelineId,
-                               parent_id: Option<ScrollLayerId>)
-                               -> ScrollLayerId {
+                               parent_id: Option<ClipId>)
+                               -> ClipId {
 
         let reference_frame_id =
-            ScrollLayerId::ReferenceFrame(self.current_reference_frame_id, pipeline_id);
+            ClipId::ReferenceFrame(self.current_reference_frame_id, pipeline_id);
         self.current_reference_frame_id += 1;
 
         let node = ClipScrollNode::new_reference_frame(parent_id,
                                                        rect,
                                                        rect.size,
                                                        transform,
                                                        pipeline_id);
         self.add_node(node, reference_frame_id);
         reference_frame_id
     }
 
-    pub fn add_node(&mut self, node: ClipScrollNode, id: ScrollLayerId) {
+    pub fn add_node(&mut self, node: ClipScrollNode, id: ClipId) {
         // When the parent node is None this means we are adding the root.
         match node.parent {
             Some(parent_id) => self.nodes.get_mut(&parent_id).unwrap().add_child(id),
             None => self.root_reference_frame_id = id,
         }
 
         debug_assert!(!self.nodes.contains_key(&id));
         self.nodes.insert(id, node);
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.pipelines_to_discard.insert(pipeline_id);
 
-        match self.current_scroll_layer_id {
-            Some(id) if id.pipeline_id() == pipeline_id => self.current_scroll_layer_id = None,
+        match self.currently_scrolling_node_id {
+            Some(id) if id.pipeline_id() == pipeline_id => self.currently_scrolling_node_id = None,
             _ => {}
         }
     }
 }
 
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -927,19 +927,19 @@ impl Device {
             bound_textures: [ TextureId::invalid(); 16 ],
             bound_program: ProgramId(0),
             bound_vao: VAOId(0),
             bound_read_fbo: FBOId(0),
             bound_draw_fbo: FBOId(0),
             default_read_fbo: 0,
             default_draw_fbo: 0,
 
-            textures: HashMap::with_hasher(Default::default()),
-            programs: HashMap::with_hasher(Default::default()),
-            vaos: HashMap::with_hasher(Default::default()),
+            textures: HashMap::default(),
+            programs: HashMap::default(),
+            vaos: HashMap::default(),
 
             shader_preamble: shader_preamble,
 
             next_vao_id: 1,
             //file_watcher: file_watcher,
 
             max_texture_size: max_texture_size,
             frame_id: FrameId(0),
@@ -1718,16 +1718,17 @@ impl Device {
                     }
                     (get_gl_format_bgra(self.gl()), 4, expanded_data.as_slice())
                 } else {
                     (GL_FORMAT_A, 1, data)
                 }
             }
             ImageFormat::RGB8 => (gl::RGB, 3, data),
             ImageFormat::RGBA8 => (get_gl_format_bgra(self.gl()), 4, data),
+            ImageFormat::RG8 => (gl::RG, 2, data),
             ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
         };
 
         let row_length = match stride {
             Some(value) => value / bpp,
             None => width,
         };
 
@@ -2083,16 +2084,17 @@ impl Device {
 }
 
 impl Drop for Device {
     fn drop(&mut self) {
         //self.file_watcher.exit();
     }
 }
 
+/// return (gl_internal_format, gl_format)
 fn gl_texture_formats_for_image_format(gl: &gl::Gl, format: ImageFormat) -> (gl::GLint, gl::GLuint) {
     match format {
         ImageFormat::A8 => {
             if cfg!(any(target_arch="arm", target_arch="aarch64")) {
                 (get_gl_format_bgra(gl) as gl::GLint, get_gl_format_bgra(gl))
             } else {
                 (GL_FORMAT_A as gl::GLint, GL_FORMAT_A)
             }
@@ -2104,16 +2106,17 @@ fn gl_texture_formats_for_image_format(g
                     (gl::RGBA as gl::GLint, get_gl_format_bgra(gl))
                 }
                 gl::GlType::Gles => {
                     (get_gl_format_bgra(gl) as gl::GLint, get_gl_format_bgra(gl))
                 }
             }
         }
         ImageFormat::RGBAF32 => (gl::RGBA32F as gl::GLint, gl::RGBA),
+        ImageFormat::RG8 => (gl::RG8 as gl::GLint, gl::RG),
         ImageFormat::Invalid => unreachable!(),
     }
 }
 
 fn gl_type_for_texture_format(format: ImageFormat) -> gl::GLuint {
     match format {
         ImageFormat::RGBAF32 => gl::FLOAT,
         _ => gl::UNSIGNED_BYTE,
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -12,49 +12,49 @@ use frame_builder::{FrameBuilder, FrameB
 use clip_scroll_tree::{ClipScrollTree, ScrollStates};
 use profiler::TextureCacheProfileCounters;
 use resource_cache::ResourceCache;
 use scene::{Scene, SceneProperties};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use tiling::{AuxiliaryListsMap, CompositeOps, PrimitiveFlags};
 use util::{ComplexClipRegionHelpers, subtract_rect};
-use webrender_traits::{AuxiliaryLists, ClipDisplayItem, ClipRegion, ColorF, DeviceUintRect};
-use webrender_traits::{DeviceUintSize, DisplayItem, Epoch, FilterOp, ImageDisplayItem, LayerPoint};
-use webrender_traits::{LayerRect, LayerSize, LayerToScrollTransform, LayoutRect, LayoutTransform};
-use webrender_traits::{MixBlendMode, PipelineId, ScrollEventPhase, ScrollLayerId};
+use webrender_traits::{AuxiliaryLists, ClipDisplayItem, ClipId, ClipRegion, ColorF};
+use webrender_traits::{DeviceUintRect, DeviceUintSize, DisplayItem, Epoch, FilterOp};
+use webrender_traits::{ImageDisplayItem, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
+use webrender_traits::{LayoutRect, LayoutTransform, MixBlendMode, PipelineId, ScrollEventPhase};
 use webrender_traits::{ScrollLayerState, ScrollLocation, ScrollPolicy, SpecificDisplayItem};
 use webrender_traits::{StackingContext, TileOffset, WorldPoint};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 };
 
 struct FlattenContext<'a> {
     scene: &'a Scene,
     builder: &'a mut FrameBuilder,
     resource_cache: &'a mut ResourceCache,
-    replacements: Vec<(ScrollLayerId, ScrollLayerId)>,
+    replacements: Vec<(ClipId, ClipId)>,
 }
 
 impl<'a> FlattenContext<'a> {
     fn new(scene: &'a Scene,
            builder: &'a mut FrameBuilder,
            resource_cache: &'a mut ResourceCache)
            -> FlattenContext<'a> {
         FlattenContext {
             scene: scene,
             builder: builder,
             resource_cache: resource_cache,
             replacements: Vec::new(),
         }
     }
 
-    fn scroll_layer_id_with_replacement(&self, id: ScrollLayerId) -> ScrollLayerId {
+    fn clip_id_with_replacement(&self, id: ClipId) -> ClipId {
         match self.replacements.last() {
             Some(&(to_replace, replacement)) if to_replace == id => replacement,
             _ => id,
         }
     }
 }
 
 // TODO: doc
@@ -220,18 +220,18 @@ fn clip_intersection(original_rect: &Lay
             ccr.get_inner_rect_full().and_then(|ir| ir.intersection(&combined))
         })
     })
 }
 
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
         Frame {
-            pipeline_epoch_map: HashMap::with_hasher(Default::default()),
-            pipeline_auxiliary_lists: HashMap::with_hasher(Default::default()),
+            pipeline_epoch_map: HashMap::default(),
+            pipeline_auxiliary_lists: HashMap::default(),
             clip_scroll_tree: ClipScrollTree::new(),
             id: FrameId(0),
             frame_builder: None,
             frame_builder_config: config,
         }
     }
 
     pub fn reset(&mut self) -> ScrollStates {
@@ -243,17 +243,17 @@ impl Frame {
         self.clip_scroll_tree.drain()
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
         self.clip_scroll_tree.get_scroll_node_state()
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
-    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ScrollLayerId) -> bool {
+    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ClipId) -> bool {
         self.clip_scroll_tree.scroll_nodes(origin, id)
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
     pub fn scroll(&mut self,
                   scroll_location: ScrollLocation,
                   cursor: WorldPoint,
                   phase: ScrollEventPhase)
@@ -287,18 +287,17 @@ impl Frame {
 
         let display_list = scene.display_lists.get(&root_pipeline_id);
         let display_list = match display_list {
             Some(display_list) => display_list,
             None => return,
         };
 
         if window_size.width == 0 || window_size.height == 0 {
-            println!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
-            return;
+            error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
         }
 
         let old_scrolling_states = self.reset();
         self.pipeline_auxiliary_lists = scene.pipeline_auxiliary_lists.clone();
 
         self.pipeline_epoch_map.insert(root_pipeline_id, root_pipeline.epoch);
 
         let (root_stacking_context, root_bounds) = match display_list.starting_stacking_context() {
@@ -319,62 +318,62 @@ impl Frame {
 
         let mut frame_builder = FrameBuilder::new(window_size,
                                                   background_color,
                                                   self.frame_builder_config);
 
         {
             let mut context = FlattenContext::new(scene, &mut frame_builder, resource_cache);
 
-            let scroll_layer_id = context.builder.push_root(root_pipeline_id,
-                                                            &root_pipeline.viewport_size,
-                                                            &root_bounds.size,
-                                                            &mut self.clip_scroll_tree);
+            let clip_id = context.builder.push_root(root_pipeline_id,
+                                                    &root_pipeline.viewport_size,
+                                                    &root_bounds.size,
+                                                    &mut self.clip_scroll_tree);
 
             context.builder.setup_viewport_offset(window_size,
                                                   inner_rect,
                                                   device_pixel_ratio,
                                                   &mut self.clip_scroll_tree);
 
             let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
             self.flatten_stacking_context(&mut traversal,
                                           root_pipeline_id,
                                           &mut context,
-                                          scroll_layer_id,
+                                          clip_id,
                                           LayerPoint::zero(),
                                           0,
                                           root_bounds,
                                           root_stacking_context);
         }
 
         self.frame_builder = Some(frame_builder);
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
     }
 
     fn flatten_clip<'a>(&mut self,
                         context: &mut FlattenContext,
                         pipeline_id: PipelineId,
-                        parent_id: ScrollLayerId,
+                        parent_id: ClipId,
                         item: &ClipDisplayItem,
                         content_rect: &LayerRect,
                         clip: &ClipRegion) {
         context.builder.add_clip_scroll_node(item.id,
                                              parent_id,
                                              pipeline_id,
                                              &content_rect,
                                              clip,
                                              &mut self.clip_scroll_tree);
 
     }
 
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut DisplayListTraversal<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
-                                    context_scroll_layer_id: ScrollLayerId,
+                                    context_clip_id: ClipId,
                                     mut reference_frame_relative_offset: LayerPoint,
                                     level: i32,
                                     bounds: &LayerRect,
                                     stacking_context: &StackingContext) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
@@ -389,21 +388,20 @@ impl Frame {
                 stacking_context.mix_blend_mode_for_compositing())
         };
 
         if composition_operations.will_make_invisible() {
             traversal.skip_current_stacking_context();
             return;
         }
 
-        let mut scroll_layer_id =
-            context.scroll_layer_id_with_replacement(context_scroll_layer_id);
+        let mut clip_id = context.clip_id_with_replacement(context_clip_id);
 
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
-            context.replacements.push((context_scroll_layer_id,
+            context.replacements.push((context_clip_id,
                                        context.builder.current_reference_frame_id()));
         }
 
         // If we have a transformation, we establish a new reference frame. This means
         // that fixed position stacking contexts are positioned relative to us.
         let is_reference_frame = stacking_context.transform.is_some() ||
                                  stacking_context.perspective.is_some();
         if is_reference_frame {
@@ -415,22 +413,22 @@ impl Frame {
                 LayerToScrollTransform::create_translation(reference_frame_relative_offset.x,
                                                            reference_frame_relative_offset.y,
                                                            0.0)
                                         .pre_translated(bounds.origin.x, bounds.origin.y, 0.0)
                                         .pre_mul(&transform)
                                         .pre_mul(&perspective);
 
             let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
-            scroll_layer_id = context.builder.push_reference_frame(Some(scroll_layer_id),
-                                                                   pipeline_id,
-                                                                   &reference_frame_bounds,
-                                                                   &transform,
-                                                                   &mut self.clip_scroll_tree);
-            context.replacements.push((context_scroll_layer_id, scroll_layer_id));
+            clip_id = context.builder.push_reference_frame(Some(clip_id),
+                                                           pipeline_id,
+                                                           &reference_frame_bounds,
+                                                           &transform,
+                                                           &mut self.clip_scroll_tree);
+            context.replacements.push((context_clip_id, clip_id));
             reference_frame_relative_offset = LayerPoint::zero();
         } else {
             reference_frame_relative_offset = LayerPoint::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y);
         }
 
         // TODO(gw): Int with overflow etc
@@ -443,17 +441,17 @@ impl Frame {
         // For the root pipeline, there's no need to add a full screen rectangle
         // here, as it's handled by the framebuffer clear.
         if level == 0 && context.scene.root_pipeline_id.unwrap() != pipeline_id {
             if let Some(pipeline) = context.scene.pipeline_map.get(&pipeline_id) {
                 if let Some(bg_color) = pipeline.background_color {
                     // Note: we don't use the original clip region here,
                     // it's already processed by the node we just pushed.
                     let background_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
-                    context.builder.add_solid_rectangle(scroll_layer_id,
+                    context.builder.add_solid_rectangle(clip_id,
                                                         bounds,
                                                         &ClipRegion::simple(&background_rect),
                                                         &bg_color,
                                                         PrimitiveFlags::None);
                 }
             }
         }
 
@@ -461,38 +459,38 @@ impl Frame {
                            pipeline_id,
                            context,
                            reference_frame_relative_offset,
                            level);
 
         if level == 0 && self.frame_builder_config.enable_scrollbars {
             let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
             context.builder.add_solid_rectangle(
-                scroll_layer_id,
+                clip_id,
                 &scrollbar_rect,
                 &ClipRegion::simple(&scrollbar_rect),
                 &DEFAULT_SCROLLBAR_COLOR,
-                PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scroll_layer_id(), 4.0));
+                PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0));
         }
 
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
             context.replacements.pop();
         }
 
         if is_reference_frame {
             context.replacements.pop();
             context.builder.pop_reference_frame();
         }
 
         context.builder.pop_stacking_context();
     }
 
     fn flatten_iframe<'a>(&mut self,
                           pipeline_id: PipelineId,
-                          parent_id: ScrollLayerId,
+                          parent_id: ClipId,
                           bounds: &LayerRect,
                           context: &mut FlattenContext,
                           reference_frame_relative_offset: LayerPoint) {
         let pipeline = match context.scene.pipeline_map.get(&pipeline_id) {
             Some(pipeline) => pipeline,
             None => return,
         };
 
@@ -521,89 +519,89 @@ impl Frame {
 
         let iframe_reference_frame_id =
             context.builder.push_reference_frame(Some(parent_id),
                                                  pipeline_id,
                                                  &iframe_rect,
                                                  &transform,
                                                  &mut self.clip_scroll_tree);
 
-        let iframe_scroll_layer_id = ScrollLayerId::root_scroll_layer(pipeline_id);
+        let iframe_clip_id = ClipId::root_scroll_node(pipeline_id);
         context.builder.add_clip_scroll_node(
-            iframe_scroll_layer_id,
+            iframe_clip_id,
             iframe_reference_frame_id,
             pipeline_id,
             &LayerRect::new(LayerPoint::zero(), iframe_stacking_context_bounds.size),
             &ClipRegion::simple(&iframe_rect),
             &mut self.clip_scroll_tree);
 
         let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
         self.flatten_stacking_context(&mut traversal,
                                       pipeline_id,
                                       context,
-                                      iframe_scroll_layer_id,
+                                      iframe_clip_id,
                                       LayerPoint::zero(),
                                       0,
                                       iframe_stacking_context_bounds,
                                       iframe_stacking_context);
 
         context.builder.pop_reference_frame();
     }
 
     fn flatten_items<'a>(&mut self,
                          traversal: &mut DisplayListTraversal<'a>,
                          pipeline_id: PipelineId,
                          context: &mut FlattenContext,
                          reference_frame_relative_offset: LayerPoint,
                          level: i32) {
         while let Some(item) = traversal.next() {
-            let scroll_layer_id = context.scroll_layer_id_with_replacement(item.scroll_layer_id);
+            let clip_id = context.clip_id_with_replacement(item.clip_id);
             match item.item {
                 SpecificDisplayItem::WebGL(ref info) => {
-                    context.builder.add_webgl_rectangle(scroll_layer_id,
+                    context.builder.add_webgl_rectangle(clip_id,
                                                         item.rect,
                                                         &item.clip,
                                                         info.context_id);
                 }
                 SpecificDisplayItem::Image(ref info) => {
                     let image = context.resource_cache.get_image_properties(info.image_key);
                     if let Some(tile_size) = image.tiling {
                         // The image resource is tiled. We have to generate an image primitive
                         // for each tile.
                         let image_size = DeviceUintSize::new(image.descriptor.width, image.descriptor.height);
-                        self.decompose_image(scroll_layer_id,
+                        self.decompose_image(clip_id,
                                              context,
                                              &item.rect,
                                              &item.clip,
                                              info,
                                              image_size,
                                              tile_size as u32);
                     } else {
-                        context.builder.add_image(scroll_layer_id,
+                        context.builder.add_image(clip_id,
                                                   item.rect,
                                                   &item.clip,
                                                   &info.stretch_size,
                                                   &info.tile_spacing,
                                                   None,
                                                   info.image_key,
                                                   info.image_rendering,
                                                   None);
                     }
                 }
                 SpecificDisplayItem::YuvImage(ref info) => {
-                    context.builder.add_yuv_image(scroll_layer_id,
+                    context.builder.add_yuv_image(clip_id,
                                                   item.rect,
                                                   &item.clip,
                                                   info.y_image_key,
                                                   info.u_image_key,
                                                   info.v_image_key,
                                                   info.color_space);
                 }
                 SpecificDisplayItem::Text(ref text_info) => {
-                    context.builder.add_text(scroll_layer_id,
+                    context.builder.add_text(clip_id,
                                              item.rect,
                                              &item.clip,
                                              text_info.font_key,
                                              text_info.size,
                                              text_info.blur_radius,
                                              &text_info.color,
                                              text_info.glyphs,
                                              text_info.glyph_options);
@@ -613,100 +611,100 @@ impl Frame {
                                               .get(&pipeline_id)
                                               .expect("No auxiliary lists?!");
                     // Try to extract the opaque inner rectangle out of the clipped primitive.
                     if let Some(opaque_rect) = clip_intersection(&item.rect, &item.clip, auxiliary_lists) {
                         let mut results = Vec::new();
                         subtract_rect(&item.rect, &opaque_rect, &mut results);
                         // The inner rectangle is considered opaque within this layer.
                         // It may still inherit some masking from the clip stack.
-                        context.builder.add_solid_rectangle(scroll_layer_id,
+                        context.builder.add_solid_rectangle(clip_id,
                                                             &opaque_rect,
                                                             &ClipRegion::simple(&item.clip.main),
                                                             &info.color,
                                                             PrimitiveFlags::None);
                         for transparent_rect in &results {
-                            context.builder.add_solid_rectangle(scroll_layer_id,
+                            context.builder.add_solid_rectangle(clip_id,
                                                                 transparent_rect,
                                                                 &item.clip,
                                                                 &info.color,
                                                                 PrimitiveFlags::None);
                         }
                     } else {
-                        context.builder.add_solid_rectangle(scroll_layer_id,
+                        context.builder.add_solid_rectangle(clip_id,
                                                             &item.rect,
                                                             &item.clip,
                                                             &info.color,
                                                             PrimitiveFlags::None);
                     }
                 }
                 SpecificDisplayItem::Gradient(ref info) => {
-                    context.builder.add_gradient(scroll_layer_id,
+                    context.builder.add_gradient(clip_id,
                                                  item.rect,
                                                  &item.clip,
                                                  info.gradient.start_point,
                                                  info.gradient.end_point,
                                                  info.gradient.stops,
                                                  info.gradient.extend_mode,
                                                  info.tile_size,
                                                  info.tile_spacing);
                 }
                 SpecificDisplayItem::RadialGradient(ref info) => {
-                    context.builder.add_radial_gradient(scroll_layer_id,
+                    context.builder.add_radial_gradient(clip_id,
                                                         item.rect,
                                                         &item.clip,
                                                         info.gradient.start_center,
                                                         info.gradient.start_radius,
                                                         info.gradient.end_center,
                                                         info.gradient.end_radius,
                                                         info.gradient.ratio_xy,
                                                         info.gradient.stops,
                                                         info.gradient.extend_mode,
                                                         info.tile_size,
                                                         info.tile_spacing);
                 }
                 SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
-                    context.builder.add_box_shadow(scroll_layer_id,
+                    context.builder.add_box_shadow(clip_id,
                                                    &box_shadow_info.box_bounds,
                                                    &item.clip,
                                                    &box_shadow_info.offset,
                                                    &box_shadow_info.color,
                                                    box_shadow_info.blur_radius,
                                                    box_shadow_info.spread_radius,
                                                    box_shadow_info.border_radius,
                                                    box_shadow_info.clip_mode);
                 }
                 SpecificDisplayItem::Border(ref info) => {
-                    context.builder.add_border(scroll_layer_id,
+                    context.builder.add_border(clip_id,
                                                item.rect,
                                                &item.clip,
                                                info);
                 }
                 SpecificDisplayItem::PushStackingContext(ref info) => {
                     self.flatten_stacking_context(traversal,
                                                   pipeline_id,
                                                   context,
-                                                  item.scroll_layer_id,
+                                                  item.clip_id,
                                                   reference_frame_relative_offset,
                                                   level + 1,
                                                   &item.rect,
                                                   &info.stacking_context);
                 }
                 SpecificDisplayItem::Iframe(ref info) => {
                     self.flatten_iframe(info.pipeline_id,
-                                        scroll_layer_id,
+                                        clip_id,
                                         &item.rect,
                                         context,
                                         reference_frame_relative_offset);
                 }
                 SpecificDisplayItem::Clip(ref info) => {
                     let content_rect = &item.rect.translate(&reference_frame_relative_offset);
                     self.flatten_clip(context,
                                       pipeline_id,
-                                      scroll_layer_id,
+                                      clip_id,
                                       &info,
                                       &content_rect,
                                       &item.clip);
                 }
                 SpecificDisplayItem::PopStackingContext => return,
             }
         }
     }
@@ -718,77 +716,101 @@ impl Frame {
     /// In all of the "decompose" methods below, we independently handle horizontal and vertical
     /// decomposition. This lets us generate the minimum amount of primitives by, for  example,
     /// decompositing the repetition horizontally while repeating vertically in the shader (for
     /// an image where the width is too bug but the height is not).
     ///
     /// decompose_image and decompose_image_row handle image repetitions while decompose_tiled_image
     /// takes care of the decomposition required by the internal tiling of the image.
     fn decompose_image(&mut self,
-                       scroll_layer_id: ScrollLayerId,
+                       clip_id: ClipId,
                        context: &mut FlattenContext,
                        item_rect: &LayerRect,
                        item_clip: &ClipRegion,
                        info: &ImageDisplayItem,
                        image_size: DeviceUintSize,
                        tile_size: u32) {
         let no_vertical_tiling = image_size.height <= tile_size;
         let no_vertical_spacing = info.tile_spacing.height == 0.0;
         if no_vertical_tiling && no_vertical_spacing {
-            self.decompose_image_row(scroll_layer_id, context, item_rect, item_clip, info, image_size, tile_size);
+            self.decompose_image_row(clip_id,
+                                     context,
+                                     item_rect,
+                                     item_clip,
+                                     info,
+                                     image_size,
+                                     tile_size);
             return;
         }
 
         // Decompose each vertical repetition into rows.
         let layout_stride = info.stretch_size.height + info.tile_spacing.height;
         let num_repetitions = (item_rect.size.height / layout_stride).ceil() as u32;
         for i in 0..num_repetitions {
             if let Some(row_rect) = rect(
                 item_rect.origin.x,
                 item_rect.origin.y + (i as f32) * layout_stride,
                 item_rect.size.width,
                 info.stretch_size.height
             ).intersection(item_rect) {
-                self.decompose_image_row(scroll_layer_id, context, &row_rect, item_clip, info, image_size, tile_size);
+                self.decompose_image_row(clip_id,
+                                         context,
+                                         &row_rect,
+                                         item_clip,
+                                         info,
+                                         image_size,
+                                         tile_size);
             }
         }
     }
 
     fn decompose_image_row(&mut self,
-                           scroll_layer_id: ScrollLayerId,
+                           clip_id: ClipId,
                            context: &mut FlattenContext,
                            item_rect: &LayerRect,
                            item_clip: &ClipRegion,
                            info: &ImageDisplayItem,
                            image_size: DeviceUintSize,
                            tile_size: u32) {
         let no_horizontal_tiling = image_size.width <= tile_size;
         let no_horizontal_spacing = info.tile_spacing.width == 0.0;
         if no_horizontal_tiling && no_horizontal_spacing {
-            self.decompose_tiled_image(scroll_layer_id, context, item_rect, item_clip, info, image_size, tile_size);
+            self.decompose_tiled_image(clip_id,
+                                       context,
+                                       item_rect,
+                                       item_clip,
+                                       info,
+                                       image_size,
+                                       tile_size);
             return;
         }
 
         // Decompose each horizontal repetition.
         let layout_stride = info.stretch_size.width + info.tile_spacing.width;
         let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32;
         for i in 0..num_repetitions {
             if let Some(decomposed_rect) = rect(
                 item_rect.origin.x + (i as f32) * layout_stride,
                 item_rect.origin.y,
                 info.stretch_size.width,
                 item_rect.size.height,
             ).intersection(item_rect) {
-                self.decompose_tiled_image(scroll_layer_id, context, &decomposed_rect, item_clip, info, image_size, tile_size);
+                self.decompose_tiled_image(clip_id,
+                                           context,
+                                           &decomposed_rect,
+                                           item_clip,
+                                           info,
+                                           image_size,
+                                           tile_size);
             }
         }
     }
 
     fn decompose_tiled_image(&mut self,
-                             scroll_layer_id: ScrollLayerId,
+                             clip_id: ClipId,
                              context: &mut FlattenContext,
                              item_rect: &LayerRect,
                              item_clip: &ClipRegion,
                              info: &ImageDisplayItem,
                              image_size: DeviceUintSize,
                              tile_size: u32) {
         // The image resource is tiled. We have to generate an image primitive
         // for each tile.
@@ -857,76 +879,76 @@ impl Frame {
 
         // The size in pixels of the tiles on the right and bottom edges, smaller
         // than the regular tile size if the image is not a multiple of the tile size.
         // Zero means the image size is a multiple of the tile size.
         let leftover = DeviceUintSize::new(image_size.width % tile_size, image_size.height % tile_size);
 
         for ty in 0..num_tiles_y {
             for tx in 0..num_tiles_x {
-                self.add_tile_primitive(scroll_layer_id,
+                self.add_tile_primitive(clip_id,
                                         context,
                                         item_rect,
                                         item_clip,
                                         info,
                                         TileOffset::new(tx, ty),
                                         stretched_tile_size,
                                         1.0, 1.0,
                                         repeat_x, repeat_y);
             }
             if leftover.width != 0 {
                 // Tiles on the right edge that are smaller than the tile size.
-                self.add_tile_primitive(scroll_layer_id,
+                self.add_tile_primitive(clip_id,
                                         context,
                                         item_rect,
                                         item_clip,
                                         info,
                                         TileOffset::new(num_tiles_x, ty),
                                         stretched_tile_size,
                                         (leftover.width as f32) / tile_size_f32,
                                         1.0,
                                         repeat_x, repeat_y);
             }
         }
 
         if leftover.height != 0 {
             for tx in 0..num_tiles_x {
                 // Tiles on the bottom edge that are smaller than the tile size.
-                self.add_tile_primitive(scroll_layer_id,
+                self.add_tile_primitive(clip_id,
                                         context,
                                         item_rect,
                                         item_clip,
                                         info,
                                         TileOffset::new(tx, num_tiles_y),
                                         stretched_tile_size,
                                         1.0,
                                         (leftover.height as f32) / tile_size_f32,
                                         repeat_x,
                                         repeat_y);
             }
 
             if leftover.width != 0 {
                 // Finally, the bottom-right tile with a "leftover" size.
-                self.add_tile_primitive(scroll_layer_id,
+                self.add_tile_primitive(clip_id,
                                         context,
                                         item_rect,
                                         item_clip,
                                         info,
                                         TileOffset::new(num_tiles_x, num_tiles_y),
                                         stretched_tile_size,
                                         (leftover.width as f32) / tile_size_f32,
                                         (leftover.height as f32) / tile_size_f32,
                                         repeat_x,
                                         repeat_y);
             }
         }
     }
 
     fn add_tile_primitive(&mut self,
-                          scroll_layer_id: ScrollLayerId,
+                          clip_id: ClipId,
                           context: &mut FlattenContext,
                           item_rect: &LayerRect,
                           item_clip: &ClipRegion,
                           info: &ImageDisplayItem,
                           tile_offset: TileOffset,
                           stretched_tile_size: LayerSize,
                           tile_ratio_width: f32,
                           tile_ratio_height: f32,
@@ -961,17 +983,17 @@ impl Frame {
 
         if repeat_y {
             assert_eq!(tile_offset.y, 0);
             prim_rect.size.height = item_rect.size.height;
         }
 
         // Fix up the primitive's rect if it overflows the original item rect.
         if let Some(prim_rect) = prim_rect.intersection(item_rect) {
-            context.builder.add_image(scroll_layer_id,
+            context.builder.add_image(clip_id,
                                       prim_rect,
                                       item_clip,
                                       &stretched_size,
                                       &info.tile_spacing,
                                       None,
                                       info.image_key,
                                       info.image_rendering,
                                       Some(tile_offset));
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,45 +1,42 @@
 /* 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 app_units::Au;
-use batch_builder::BorderSideHelpers;
 use frame::FrameId;
 use gpu_store::GpuStoreAddress;
 use internal_types::{HardwareCompositeOp, SourceTexture};
 use mask_cache::{ClipMode, ClipSource, MaskCacheInfo, RegionMode};
-use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, BoxShadowPrimitiveGpu};
 use prim_store::{GradientPrimitiveCpu, GradientPrimitiveGpu, ImagePrimitiveCpu, ImagePrimitiveGpu};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveGeometry, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, RadialGradientPrimitiveGpu};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextRunPrimitiveGpu};
-use prim_store::{TexelRect, YuvImagePrimitiveCpu, YuvImagePrimitiveGpu};
+use prim_store::{BoxShadowPrimitiveGpu, TexelRect, YuvImagePrimitiveCpu, YuvImagePrimitiveGpu};
 use profiler::{FrameProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, MaskCacheKey, MaskResult, RenderTask, RenderTaskIndex};
 use render_task::RenderTaskLocation;
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
 use euclid::SideOffsets2D;
 use tiling::StackingContextIndex;
 use tiling::{AuxiliaryListsMap, ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, subtract_rect};
 use util::RectHelpers;
-use webrender_traits::{BorderDetails, BorderDisplayItem};
-use webrender_traits::{BoxShadowClipMode, ClipRegion, ColorF, DeviceIntPoint, DeviceIntRect};
-use webrender_traits::{DeviceIntSize, DeviceUintRect, DeviceUintSize, ExtendMode, FontKey};
-use webrender_traits::{FontRenderMode, GlyphOptions, ImageKey, ImageRendering, ItemRange};
-use webrender_traits::{LayerPoint, LayerRect, LayerSize, LayerToScrollTransform, PipelineId};
-use webrender_traits::{RepeatMode, ScrollLayerId, TileOffset, WebGLContextId, YuvColorSpace};
-use webrender_traits::{TransformStyle};
+use webrender_traits::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipId, ClipRegion};
+use webrender_traits::{ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect};
+use webrender_traits::{DeviceUintSize, ExtendMode, FontKey, FontRenderMode, GlyphOptions};
+use webrender_traits::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
+use webrender_traits::{LayerToScrollTransform, PipelineId, RepeatMode, TileOffset, TransformStyle};
+use webrender_traits::{WebGLContextId, YuvColorSpace};
 
 #[derive(Debug, Clone)]
 struct ImageBorderSegment {
     geom_rect: LayerRect,
     sub_rect: TexelRect,
     stretch_size: LayerSize,
     tile_spacing: LayerSize,
 }
@@ -114,17 +111,17 @@ pub struct FrameBuilder {
     stacking_context_store: Vec<StackingContext>,
     clip_scroll_group_store: Vec<ClipScrollGroup>,
     packed_layers: Vec<PackedLayer>,
 
     scrollbar_prims: Vec<ScrollbarPrimitive>,
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
-    reference_frame_stack: Vec<ScrollLayerId>,
+    reference_frame_stack: Vec<ClipId>,
 
     /// A stack of stacking contexts used for creating ClipScrollGroups as
     /// primitives are added to the frame.
     stacking_context_stack: Vec<StackingContextIndex>,
 }
 
 impl FrameBuilder {
     pub fn new(screen_size: DeviceUintSize,
@@ -140,85 +137,80 @@ impl FrameBuilder {
             packed_layers: Vec::new(),
             scrollbar_prims: Vec::new(),
             config: config,
             reference_frame_stack: Vec::new(),
             stacking_context_stack: Vec::new(),
         }
     }
 
-    fn add_primitive(&mut self,
-                     scroll_layer_id: ScrollLayerId,
-                     rect: &LayerRect,
-                     clip_region: &ClipRegion,
-                     extra_clip: Option<ClipSource>,
-                     container: PrimitiveContainer)
-                     -> PrimitiveIndex {
+    pub fn add_primitive(&mut self,
+                         clip_id: ClipId,
+                         rect: &LayerRect,
+                         clip_region: &ClipRegion,
+                         extra_clips: &[ClipSource],
+                         container: PrimitiveContainer)
+                         -> PrimitiveIndex {
         let stacking_context_index = *self.stacking_context_stack.last().unwrap();
-        if !self.stacking_context_store[stacking_context_index.0]
-                .has_clip_scroll_group(scroll_layer_id) {
-            let group_index = self.create_clip_scroll_group(stacking_context_index,
-                                                            scroll_layer_id);
+        if !self.stacking_context_store[stacking_context_index.0] .has_clip_scroll_group(clip_id) {
+            let group_index = self.create_clip_scroll_group(stacking_context_index, clip_id);
             let stacking_context = &mut self.stacking_context_store[stacking_context_index.0];
             stacking_context.clip_scroll_groups.push(group_index);
         }
 
         let geometry = PrimitiveGeometry {
             local_rect: *rect,
             local_clip_rect: clip_region.main,
         };
         let mut clip_sources = Vec::new();
         if clip_region.is_complex() {
             clip_sources.push(ClipSource::Region(clip_region.clone(), RegionMode::ExcludeRect));
         }
-        // TODO(gw): Perhaps in the future it's worth passing in an array
-        //           so that callers can provide an arbitrary number
-        //           of clips?
-        if let Some(extra_clip) = extra_clip {
-            clip_sources.push(extra_clip);
-        }
+
+        clip_sources.extend(extra_clips.iter().cloned());
+
         let clip_info = MaskCacheInfo::new(&clip_sources,
                                            &mut self.prim_store.gpu_data32);
 
         let prim_index = self.prim_store.add_primitive(geometry,
                                                        clip_sources,
                                                        clip_info,
                                                        container);
 
         match self.cmds.last_mut().unwrap() {
             &mut PrimitiveRunCmd::PrimitiveRun(_run_prim_index, ref mut count, run_layer_id)
-                if run_layer_id == scroll_layer_id => {
+                if run_layer_id == clip_id => {
                     debug_assert!(_run_prim_index.0 + *count == prim_index.0);
                     *count += 1;
                     return prim_index;
             }
             &mut PrimitiveRunCmd::PrimitiveRun(..) |
             &mut PrimitiveRunCmd::PushStackingContext(..) |
             &mut PrimitiveRunCmd::PopStackingContext => {}
         }
 
-        self.cmds.push(PrimitiveRunCmd::PrimitiveRun(prim_index, 1, scroll_layer_id));
+        self.cmds.push(PrimitiveRunCmd::PrimitiveRun(prim_index, 1, clip_id));
         prim_index
     }
 
     pub fn create_clip_scroll_group(&mut self,
                                     stacking_context_index: StackingContextIndex,
-                                    scroll_layer_id: ScrollLayerId)
+                                    clip_id: ClipId)
                                     -> ClipScrollGroupIndex {
         let packed_layer_index = PackedLayerIndex(self.packed_layers.len());
         self.packed_layers.push(PackedLayer::empty());
 
         self.clip_scroll_group_store.push(ClipScrollGroup {
             stacking_context_index: stacking_context_index,
-            scroll_layer_id: scroll_layer_id,
+            clip_id: clip_id,
             packed_layer_index: packed_layer_index,
             xf_rect: None,
          });
 
-        ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, scroll_layer_id)
+        ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, clip_id)
     }
 
     pub fn push_stacking_context(&mut self,
                                  reference_frame_offset: &LayerPoint,
                                  pipeline_id: PipelineId,
                                  is_page_root: bool,
                                  composite_ops: CompositeOps,
                                  transform_style: TransformStyle) {
@@ -244,28 +236,28 @@ impl FrameBuilder {
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
         self.stacking_context_stack.pop();
     }
 
     pub fn push_reference_frame(&mut self,
-                                parent_id: Option<ScrollLayerId>,
+                                parent_id: Option<ClipId>,
                                 pipeline_id: PipelineId,
                                 rect: &LayerRect,
                                 transform: &LayerToScrollTransform,
                                 clip_scroll_tree: &mut ClipScrollTree)
-                                -> ScrollLayerId {
+                                -> ClipId {
         let new_id = clip_scroll_tree.add_reference_frame(rect, transform, pipeline_id, parent_id);
         self.reference_frame_stack.push(new_id);
         new_id
     }
 
-    pub fn current_reference_frame_id(&self) -> ScrollLayerId {
+    pub fn current_reference_frame_id(&self) -> ClipId {
         *self.reference_frame_stack.last().unwrap()
     }
 
     pub fn setup_viewport_offset(&mut self,
                                  window_size: DeviceUintSize,
                                  inner_rect: DeviceUintRect,
                                  device_pixel_ratio: f32,
                                  clip_scroll_tree: &mut ClipScrollTree) {
@@ -286,46 +278,46 @@ impl FrameBuilder {
             if let NodeType::ReferenceFrame(ref mut transform) = root_node.node_type {
                 *transform = LayerToScrollTransform::create_translation(viewport_offset.x,
                                                                         viewport_offset.y,
                                                                         0.0);
             }
             root_node.local_clip_rect = viewport_clip;
         }
 
-        let scroll_layer_id = clip_scroll_tree.topmost_scroll_layer_id();
-        if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&scroll_layer_id) {
+        let clip_id = clip_scroll_tree.topmost_scrolling_node_id();
+        if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&clip_id) {
             root_node.local_clip_rect = viewport_clip;
         }
     }
 
     pub fn push_root(&mut self,
                      pipeline_id: PipelineId,
                      viewport_size: &LayerSize,
                      content_size: &LayerSize,
                      clip_scroll_tree: &mut ClipScrollTree)
-                     -> ScrollLayerId {
+                     -> ClipId {
         let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
         let identity = &LayerToScrollTransform::identity();
         self.push_reference_frame(None, pipeline_id, &viewport_rect, identity, clip_scroll_tree);
 
-        let topmost_scroll_layer_id = ScrollLayerId::root_scroll_layer(pipeline_id);
-        clip_scroll_tree.topmost_scroll_layer_id = topmost_scroll_layer_id;
-        self.add_clip_scroll_node(topmost_scroll_layer_id,
+        let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
+        clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
+        self.add_clip_scroll_node(topmost_scrolling_node_id,
                                    clip_scroll_tree.root_reference_frame_id,
                                    pipeline_id,
                                    &LayerRect::new(LayerPoint::zero(), *content_size),
                                    &ClipRegion::simple(&viewport_rect),
                                    clip_scroll_tree);
-        topmost_scroll_layer_id
+        topmost_scrolling_node_id
     }
 
     pub fn add_clip_scroll_node(&mut self,
-                                new_node_id: ScrollLayerId,
-                                parent_id: ScrollLayerId,
+                                new_node_id: ClipId,
+                                parent_id: ClipId,
                                 pipeline_id: PipelineId,
                                 content_rect: &LayerRect,
                                 clip_region: &ClipRegion,
                                 clip_scroll_tree: &mut ClipScrollTree) {
         let clip_info = ClipInfo::new(clip_region,
                                       &mut self.prim_store.gpu_data32,
                                       PackedLayerIndex(self.packed_layers.len()));
         let node = ClipScrollNode::new(pipeline_id,
@@ -338,49 +330,49 @@ impl FrameBuilder {
         self.packed_layers.push(PackedLayer::empty());
     }
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
     }
 
     pub fn add_solid_rectangle(&mut self,
-                               scroll_layer_id: ScrollLayerId,
+                               clip_id: ClipId,
                                rect: &LayerRect,
                                clip_region: &ClipRegion,
                                color: &ColorF,
                                flags: PrimitiveFlags) {
         if color.a == 0.0 {
             return;
         }
 
         let prim = RectanglePrimitive {
             color: *color,
         };
 
-        let prim_index = self.add_primitive(scroll_layer_id,
+        let prim_index = self.add_primitive(clip_id,
                                             rect,
                                             clip_region,
-                                            None,
+                                            &[],
                                             PrimitiveContainer::Rectangle(prim));
 
         match flags {
             PrimitiveFlags::None => {}
-            PrimitiveFlags::Scrollbar(scroll_layer_id, border_radius) => {
+            PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
                 self.scrollbar_prims.push(ScrollbarPrimitive {
                     prim_index: prim_index,
-                    scroll_layer_id: scroll_layer_id,
+                    clip_id: clip_id,
                     border_radius: border_radius,
                 });
             }
         }
     }
 
     pub fn add_border(&mut self,
-                      scroll_layer_id: ScrollLayerId,
+                      clip_id: ClipId,
                       rect: LayerRect,
                       clip_region: &ClipRegion,
                       border_item: &BorderDisplayItem) {
         let create_segments = |outset: SideOffsets2D<f32>| {
             // Calculate the modified rect as specific by border-image-outset
             let origin = LayerPoint::new(rect.origin.x - outset.left,
                                          rect.origin.y - outset.top);
             let size = LayerSize::new(rect.size.width + outset.left + outset.right,
@@ -506,100 +498,54 @@ impl FrameBuilder {
                         ImageBorderSegment::new(LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
                                                 TexelRect::new(px2, py1, px3, py2),
                                                 RepeatMode::Stretch,
                                                 border.repeat_vertical),
                     ]);
                 }
 
                 for segment in segments {
-                    self.add_image(scroll_layer_id,
+                    self.add_image(clip_id,
                                    segment.geom_rect,
                                    clip_region,
                                    &segment.stretch_size,
                                    &segment.tile_spacing,
                                    Some(segment.sub_rect),
                                    border.image_key,
                                    ImageRendering::Auto,
                                    None);
                 }
             }
             BorderDetails::Normal(ref border) => {
-                // Gradually move border types over to a simplified
-                // shader and code path that can handle all border
-                // cases correctly.
-                if self.add_simple_border(&rect,
-                                          border,
-                                          &border_item.widths,
-                                          scroll_layer_id,
-                                          clip_region) {
-                    return;
-                }
-
-                let radius = &border.radius;
-                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);
-                let top_color       = top.border_color(1.0, 2.0/3.0, 0.3, 0.7);
-                let right_color     = right.border_color(2.0/3.0, 1.0, 0.7, 0.3);
-                let bottom_color    = bottom.border_color(2.0/3.0, 1.0, 0.7, 0.3);
-
-                let prim_cpu = BorderPrimitiveCpu {
-                };
-
-                let prim_gpu = BorderPrimitiveGpu {
-                    colors: [ left_color, top_color, right_color, bottom_color ],
-                    widths: [ border_item.widths.left,
-                              border_item.widths.top,
-                              border_item.widths.right,
-                              border_item.widths.bottom ],
-                    style: [
-                        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),
-                    ],
-                    radii: [
-                        radius.top_left,
-                        radius.top_right,
-                        radius.bottom_right,
-                        radius.bottom_left,
-                    ],
-                };
-
-                self.add_primitive(scroll_layer_id,
-                                   &rect,
-                                   clip_region,
-                                   None,
-                                   PrimitiveContainer::Border(prim_cpu, prim_gpu));
+                self.add_normal_border(&rect,
+                                       border,
+                                       &border_item.widths,
+                                       clip_id,
+                                       clip_region);
             }
             BorderDetails::Gradient(ref border) => {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
 
-                    self.add_gradient(scroll_layer_id,
+                    self.add_gradient(clip_id,
                                       segment,
                                       clip_region,
                                       border.gradient.start_point - segment_rel,
                                       border.gradient.end_point - segment_rel,
                                       border.gradient.stops,
                                       border.gradient.extend_mode,
                                       segment.size,
                                       LayerSize::zero());
                 }
             }
             BorderDetails::RadialGradient(ref border) => {
                 for segment in create_segments(border.outset) {
                     let segment_rel = segment.origin - rect.origin;
 
-                    self.add_radial_gradient(scroll_layer_id,
+                    self.add_radial_gradient(clip_id,
                                              segment,
                                              clip_region,
                                              border.gradient.start_center - segment_rel,
                                              border.gradient.start_radius,
                                              border.gradient.end_center - segment_rel,
                                              border.gradient.end_radius,
                                              border.gradient.ratio_xy,
                                              border.gradient.stops,
@@ -607,17 +553,17 @@ impl FrameBuilder {
                                              segment.size,
                                              LayerSize::zero());
                 }
             }
         }
     }
 
     pub fn add_gradient(&mut self,
-                        scroll_layer_id: ScrollLayerId,
+                        clip_id: ClipId,
                         rect: LayerRect,
                         clip_region: &ClipRegion,
                         start_point: LayerPoint,
                         end_point: LayerPoint,
                         stops: ItemRange,
                         extend_mode: ExtendMode,
                         tile_size: LayerSize,
                         tile_spacing: LayerSize) {
@@ -672,25 +618,25 @@ impl FrameBuilder {
         };
 
         let prim = if aligned {
             PrimitiveContainer::AlignedGradient(gradient_cpu, gradient_gpu)
         } else {
             PrimitiveContainer::AngleGradient(gradient_cpu, gradient_gpu)
         };
 
-        self.add_primitive(scroll_layer_id,
+        self.add_primitive(clip_id,
                            &rect,
                            clip_region,
-                           None,
+                           &[],
                            prim);
     }
 
     pub fn add_radial_gradient(&mut self,
-                               scroll_layer_id: ScrollLayerId,
+                               clip_id: ClipId,
                                rect: LayerRect,
                                clip_region: &ClipRegion,
                                start_center: LayerPoint,
                                start_radius: f32,
                                end_center: LayerPoint,
                                end_radius: f32,
                                ratio_xy: f32,
                                stops: ItemRange,
@@ -710,25 +656,25 @@ impl FrameBuilder {
             end_radius: end_radius,
             ratio_xy: ratio_xy,
             extend_mode: pack_as_float(extend_mode as u32),
             tile_size: tile_size,
             tile_repeat: tile_size + tile_spacing,
             padding: [0.0, 0.0, 0.0, 0.0],
         };
 
-        self.add_primitive(scroll_layer_id,
+        self.add_primitive(clip_id,
                            &rect,
                            clip_region,
-                           None,
+                           &[],
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu, radial_gradient_gpu));
     }
 
     pub fn add_text(&mut self,
-                    scroll_layer_id: ScrollLayerId,
+                    clip_id: ClipId,
                     rect: LayerRect,
                     clip_region: &ClipRegion,
                     font_key: FontKey,
                     size: Au,
                     blur_radius: Au,
                     color: &ColorF,
                     glyph_range: ItemRange,
                     glyph_options: Option<GlyphOptions>) {
@@ -763,57 +709,114 @@ impl FrameBuilder {
             glyph_options: glyph_options,
             resource_address: GpuStoreAddress(0),
         };
 
         let prim_gpu = TextRunPrimitiveGpu {
             color: *color,
         };
 
-        self.add_primitive(scroll_layer_id,
+        self.add_primitive(clip_id,
                            &rect,
                            clip_region,
-                           None,
+                           &[],
                            PrimitiveContainer::TextRun(prim_cpu, prim_gpu));
     }
 
+    pub fn add_box_shadow_no_blur(&mut self,
+                                  clip_id: ClipId,
+                                  box_bounds: &LayerRect,
+                                  clip_region: &ClipRegion,
+                                  box_offset: &LayerPoint,
+                                  color: &ColorF,
+                                  spread_radius: f32,
+                                  border_radius: f32,
+                                  clip_mode: BoxShadowClipMode) {
+        assert!(spread_radius == 0.0); // TODO: fix cases where this isn't true.
+        let bs_rect = box_bounds.translate(box_offset);
+
+        // We can draw a rectangle instead with the proper border radius clipping.
+        let (bs_clip_mode, rect_to_draw) = match clip_mode {
+            BoxShadowClipMode::Outset |
+            BoxShadowClipMode::None => (ClipMode::Clip, bs_rect),
+            BoxShadowClipMode::Inset => (ClipMode::ClipOut, *box_bounds),
+        };
+
+        let box_clip_mode = !bs_clip_mode;
+
+        // Clip the inside
+        let extra_clips = [ClipSource::Complex(bs_rect,
+                                               border_radius,
+                                               bs_clip_mode),
+                           // Clip the outside of the box
+                           ClipSource::Complex(*box_bounds,
+                                             border_radius,
+                                             box_clip_mode)];
+
+        let prim = RectanglePrimitive {
+            color: *color,
+        };
+
+        self.add_primitive(clip_id,
+                           &rect_to_draw,
+                           clip_region,
+                           &extra_clips,
+                           PrimitiveContainer::Rectangle(prim));
+    }
+
     pub fn add_box_shadow(&mut self,
-                          scroll_layer_id: ScrollLayerId,
+                          clip_id: ClipId,
                           box_bounds: &LayerRect,
                           clip_region: &ClipRegion,
                           box_offset: &LayerPoint,
                           color: &ColorF,
                           blur_radius: f32,
                           spread_radius: f32,
                           border_radius: f32,
                           clip_mode: BoxShadowClipMode) {
         if color.a == 0.0 {
             return
         }
 
-        // Fast path.
-        if blur_radius == 0.0 && spread_radius == 0.0 && clip_mode == BoxShadowClipMode::None {
-            self.add_solid_rectangle(scroll_layer_id,
-                                     box_bounds,
-                                     clip_region,
-                                     color,
-                                     PrimitiveFlags::None);
-            return;
-        }
-
         // The local space box shadow rect. It is the element rect
         // translated by the box shadow offset and inflated by the
         // box shadow spread.
         let inflate_amount = match clip_mode {
             BoxShadowClipMode::Outset | BoxShadowClipMode::None => spread_radius,
             BoxShadowClipMode::Inset => -spread_radius,
         };
 
         let bs_rect = box_bounds.translate(box_offset)
                                 .inflate(inflate_amount, inflate_amount);
+        // If we have negative inflate amounts.
+        // Have to explicitly check this since euclid::TypedRect relies on negative rects
+        let bs_rect_empty = bs_rect.size.width <= 0.0 || bs_rect.size.height <= 0.0;
+
+        // Just draw a rectangle
+        if (blur_radius == 0.0 && spread_radius == 0.0 && clip_mode == BoxShadowClipMode::None)
+           || bs_rect_empty {
+            self.add_solid_rectangle(clip_id,
+                                     box_bounds,
+                                     clip_region,
+                                     color,
+                                     PrimitiveFlags::None);
+            return;
+        }
+
+        if blur_radius == 0.0 && spread_radius == 0.0 && border_radius != 0.0 {
+            self.add_box_shadow_no_blur(clip_id,
+                                        box_bounds,
+                                        clip_region,
+                                        box_offset,
+                                        color,
+                                        spread_radius,
+                                        border_radius,
+                                        clip_mode);
+            return;
+        }
 
         // Get the outer rectangle, based on the blur radius.
         let outside_edge_size = 2.0 * blur_radius;
         let inside_edge_size = outside_edge_size.max(border_radius);
         let edge_size = outside_edge_size + inside_edge_size;
         let outer_rect = bs_rect.inflate(outside_edge_size, outside_edge_size);
 
         // Box shadows are often used for things like text underline and other
@@ -869,17 +872,17 @@ impl FrameBuilder {
                     BoxShadowKind::Shadow(rects)
                 }
             }
         };
 
         match shadow_kind {
             BoxShadowKind::Simple(rects) => {
                 for rect in &rects {
-                    self.add_solid_rectangle(scroll_layer_id,
+                    self.add_solid_rectangle(clip_id,
                                              rect,
                                              clip_region,
                                              color,
                                              PrimitiveFlags::None)
                 }
             }
             BoxShadowKind::Shadow(rects) => {
                 let inverted = match clip_mode {
@@ -889,69 +892,68 @@ impl FrameBuilder {
 
                 // Outset box shadows with border radius
                 // need a clip out of the center box.
                 let extra_clip_mode = match clip_mode {
                     BoxShadowClipMode::Outset | BoxShadowClipMode::None => ClipMode::ClipOut,
                     BoxShadowClipMode::Inset => ClipMode::Clip,
                 };
 
-                let extra_clip = if border_radius > 0.0 {
-                    Some(ClipSource::Complex(*box_bounds,
-                                             border_radius,
-                                             extra_clip_mode))
-                } else {
-                    None
-                };
+                let mut extra_clips = Vec::new();
+                if border_radius >= 0.0 {
+                    extra_clips.push(ClipSource::Complex(*box_bounds,
+                                                border_radius,
+                                                extra_clip_mode));
+                }
 
                 let prim_gpu = BoxShadowPrimitiveGpu {
                     src_rect: *box_bounds,
                     bs_rect: bs_rect,
                     color: *color,
                     blur_radius: blur_radius,
                     border_radius: border_radius,
                     edge_size: edge_size,
                     inverted: inverted,
                 };
 
-                self.add_primitive(scroll_layer_id,
+                self.add_primitive(clip_id,
                                    &outer_rect,
                                    clip_region,
-                                   extra_clip,
+                                   extra_clips.as_slice(),
                                    PrimitiveContainer::BoxShadow(prim_gpu, rects));
             }
         }
     }
 
     pub fn add_webgl_rectangle(&mut self,
-                               scroll_layer_id: ScrollLayerId,
+                               clip_id: ClipId,
                                rect: LayerRect,
                                clip_region: &ClipRegion,
                                context_id: WebGLContextId) {
         let prim_cpu = ImagePrimitiveCpu {
             kind: ImagePrimitiveKind::WebGL(context_id),
             color_texture_id: SourceTexture::Invalid,
             resource_address: GpuStoreAddress(0),
             sub_rect: None,
         };
 
         let prim_gpu = ImagePrimitiveGpu {
             stretch_size: rect.size,
             tile_spacing: LayerSize::zero(),
         };
 
-        self.add_primitive(scroll_layer_id,
+        self.add_primitive(clip_id,
                            &rect,
                            clip_region,
-                           None,
+                           &[],
                            PrimitiveContainer::Image(prim_cpu, prim_gpu));
     }
 
     pub fn add_image(&mut self,
-                     scroll_layer_id: ScrollLayerId,
+                     clip_id: ClipId,
                      rect: LayerRect,
                      clip_region: &ClipRegion,
                      stretch_size: &LayerSize,
                      tile_spacing: &LayerSize,
                      sub_rect: Option<TexelRect>,
                      image_key: ImageKey,
                      image_rendering: ImageRendering,
                      tile: Option<TileOffset>) {
@@ -965,44 +967,44 @@ impl FrameBuilder {
             sub_rect: sub_rect,
         };
 
         let prim_gpu = ImagePrimitiveGpu {
             stretch_size: *stretch_size,
             tile_spacing: *tile_spacing,
         };
 
-        self.add_primitive(scroll_layer_id,
+        self.add_primitive(clip_id,
                            &rect,
                            clip_region,
-                           None,
+                           &[],
                            PrimitiveContainer::Image(prim_cpu, prim_gpu));
     }
 
     pub fn add_yuv_image(&mut self,
-                         scroll_layer_id: ScrollLayerId,
+                         clip_id: ClipId,
                          rect: LayerRect,
                          clip_region: &ClipRegion,
                          y_image_key: ImageKey,
                          u_image_key: ImageKey,
                          v_image_key: ImageKey,
                          color_space: YuvColorSpace) {
 
         let prim_cpu = YuvImagePrimitiveCpu {
             yuv_key: [y_image_key, u_image_key, v_image_key],
             yuv_texture_id: [SourceTexture::Invalid, SourceTexture::Invalid, SourceTexture::Invalid],
             yuv_resource_address: GpuStoreAddress(0),
         };
 
         let prim_gpu = YuvImagePrimitiveGpu::new(rect.size, color_space);
 
-        self.add_primitive(scroll_layer_id,
+        self.add_primitive(clip_id,
                            &rect,
                            clip_region,
-                           None,
+                           &[],
                            PrimitiveContainer::YuvImage(prim_cpu, prim_gpu));
     }
 
     /// 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,
                                                 screen_rect: &DeviceIntRect,
                                                 clip_scroll_tree: &mut ClipScrollTree,
@@ -1020,17 +1022,17 @@ impl FrameBuilder {
                                                            device_pixel_ratio);
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree) {
         let distance_from_edge = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let mut geom = (*self.prim_store.gpu_geometry.get(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32))).clone();
-            let clip_scroll_node = &clip_scroll_tree.nodes[&scrollbar_prim.scroll_layer_id];
+            let clip_scroll_node = &clip_scroll_tree.nodes[&scrollbar_prim.clip_id];
 
             let scrollable_distance = clip_scroll_node.scrollable_height();
 
             if scrollable_distance <= 0.0 {
                 geom.local_clip_rect.size = LayerSize::zero();
                 *self.prim_store.gpu_geometry.get_mut(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32)) = geom;
                 continue;
             }
@@ -1160,27 +1162,27 @@ impl FrameBuilder {
                                                               next_z);
                         next_z += 1;
                         prev_task.as_alpha_batch().items.push(item);
                         prev_task.children.push(current_task);
                         prev_task.children.push(readback_task);
                         current_task = prev_task;
                     }
                 }
-                PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, scroll_layer_id) => {
+                PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_id) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
 
                     if !stacking_context.is_visible {
                         continue;
                     }
 
                     let stacking_context_index = *sc_stack.last().unwrap();
                     let group_index = self.stacking_context_store[stacking_context_index.0]
-                                          .clip_scroll_group(scroll_layer_id);
+                                          .clip_scroll_group(clip_id);
                     if self.clip_scroll_group_store[group_index.0].xf_rect.is_none() {
                         continue
                     }
 
                     for i in 0..prim_count {
                         let prim_index = PrimitiveIndex(first_prim_index.0 + i);
 
                         if self.prim_store.cpu_bounding_rects[prim_index.0].is_some() {
@@ -1325,17 +1327,17 @@ struct LayerRectCalculationAndCullingPas
 
     /// A cached clip info stack, which should handle the most common situation,
     /// which is that we are using the same clip info stack that we were using
     /// previously.
     current_clip_stack: Vec<(PackedLayerIndex, MaskCacheInfo)>,
 
     /// Information about the cached clip stack, which is used to avoid having
     /// to recalculate it for every primitive.
-    current_clip_info: Option<(ScrollLayerId, Option<DeviceIntRect>)>
+    current_clip_info: Option<(ClipId, Option<DeviceIntRect>)>
 }
 
 impl<'a> LayerRectCalculationAndCullingPass<'a> {
     fn create_and_run(frame_builder: &'a mut FrameBuilder,
                       screen_rect: &'a DeviceIntRect,
                       clip_scroll_tree: &'a mut ClipScrollTree,
                       auxiliary_lists_map: &'a AuxiliaryListsMap,
                       resource_cache: &'a mut ResourceCache,
@@ -1362,18 +1364,18 @@ impl<'a> LayerRectCalculationAndCullingP
         self.recalculate_clip_scroll_nodes();
         self.compute_stacking_context_visibility();
 
         let commands = mem::replace(&mut self.frame_builder.cmds, Vec::new());
         for cmd in &commands {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) =>
                     self.handle_push_stacking_context(stacking_context_index),
-                PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count, scroll_layer_id) =>
-                    self.handle_primitive_run(prim_index, prim_count, scroll_layer_id),
+                PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count, clip_id) =>
+                    self.handle_primitive_run(prim_index, prim_count, clip_id),
                 PrimitiveRunCmd::PopStackingContext => self.handle_pop_stacking_context(),
             }
         }
 
         mem::replace(&mut self.frame_builder.cmds, commands);
     }
 
     fn recalculate_clip_scroll_nodes(&mut self) {
@@ -1429,17 +1431,17 @@ impl<'a> LayerRectCalculationAndCullingP
     }
 
     fn recalculate_clip_scroll_groups(&mut self) {
         for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
             let stacking_context_index = group.stacking_context_index;
             let stacking_context = &mut self.frame_builder
                                             .stacking_context_store[stacking_context_index.0];
 
-            let node = &self.clip_scroll_tree.nodes[&group.scroll_layer_id];
+            let node = &self.clip_scroll_tree.nodes[&group.clip_id];
             let packed_layer = &mut self.frame_builder.packed_layers[group.packed_layer_index.0];
 
             // The world content transform is relative to the containing reference frame,
             // so we translate into the origin of the stacking context itself.
             let transform = node.world_content_transform
                                 .pre_translated(stacking_context.reference_frame_offset.x,
                                                 stacking_context.reference_frame_offset.y,
                                                 0.0);
@@ -1505,17 +1507,17 @@ impl<'a> LayerRectCalculationAndCullingP
         // calculate the device bounding rect. In the future, we could cache this during
         // the initial adding of items for the common case (where there is only a single
         // scroll layer for items in a stacking context).
         let stacking_context = &mut self.frame_builder
                                         .stacking_context_store[stacking_context_index.0];
         stacking_context.bounding_rect = DeviceIntRect::zero();
     }
 
-    fn rebuild_clip_info_stack_if_necessary(&mut self, id: ScrollLayerId) -> Option<DeviceIntRect> {
+    fn rebuild_clip_info_stack_if_necessary(&mut self, id: ClipId) -> Option<DeviceIntRect> {
         if let Some((current_scroll_id, bounding_rect)) = self.current_clip_info {
             if current_scroll_id == id {
                 return bounding_rect;
             }
         }
 
         // TODO(mrobinson): If we notice that this process is expensive, we can special-case
         // more common situations, such as moving from a child or a parent.
@@ -1544,32 +1546,32 @@ impl<'a> LayerRectCalculationAndCullingP
 
         self.current_clip_info = Some((id, bounding_rect));
         bounding_rect
     }
 
     fn handle_primitive_run(&mut self,
                             prim_index: PrimitiveIndex,
                             prim_count: usize,
-                            scroll_layer_id: ScrollLayerId) {
+                            clip_id: ClipId) {
         let stacking_context_index = *self.stacking_context_stack.last().unwrap();
         let (packed_layer_index, pipeline_id) = {
             let stacking_context =
                 &self.frame_builder.stacking_context_store[stacking_context_index.0];
 
             if !stacking_context.is_visible {
                 return;
             }
 
-            let group_index = stacking_context.clip_scroll_group(scroll_layer_id);
+            let group_index = stacking_context.clip_scroll_group(clip_id);
             let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
             (clip_scroll_group.packed_layer_index, stacking_context.pipeline_id)
         };
 
-        let node_clip_bounds = self.rebuild_clip_info_stack_if_necessary(scroll_layer_id);
+        let node_clip_bounds = self.rebuild_clip_info_stack_if_necessary(clip_id);
         if node_clip_bounds.map_or(false, |bounds| bounds.is_empty()) {
             return;
         }
 
         let stacking_context =
             &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
         let packed_layer = &self.frame_builder.packed_layers[packed_layer_index.0];
         let auxiliary_lists = self.auxiliary_lists_map.get(&pipeline_id)
@@ -1617,17 +1619,17 @@ impl<'a> LayerRectCalculationAndCullingP
                 if !self.current_clip_stack.is_empty() {
                     // If the primitive doesn't have a specific clip, key the task ID off the
                     // stacking context. This means that two primitives which are only clipped
                     // by the stacking context stack can share clip masks during render task
                     // assignment to targets.
                     let node_clip_bounds = node_clip_bounds.unwrap_or_else(DeviceIntRect::zero);
                     let (mask_key, mask_rect) = match prim_clip_info {
                         Some(..) => (MaskCacheKey::Primitive(prim_index), prim_bounding_rect),
-                        None => (MaskCacheKey::ScrollLayer(scroll_layer_id), node_clip_bounds)
+                        None => (MaskCacheKey::ClipNode(clip_id), node_clip_bounds)
                     };
                     let mask_opt =
                         RenderTask::new_mask(mask_rect, mask_key, &self.current_clip_stack);
                     match mask_opt {
                         MaskResult::Outside => { // Primitive is completely clipped out.
                             prim_metadata.clip_task = None;
                             self.frame_builder.prim_store.cpu_bounding_rects[prim_index.0] = None;
                             visible = false;
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -10,21 +10,18 @@ use profiler::BackendProfileCounters;
 use std::collections::{HashMap, HashSet};
 use std::f32;
 use std::hash::BuildHasherDefault;
 use std::{i32, usize};
 use std::path::PathBuf;
 use std::sync::Arc;
 use tiling;
 use renderer::BlendMode;
-use webrender_traits::{Epoch, ColorF, PipelineId};
-use webrender_traits::{ImageFormat, NativeFontHandle};
-use webrender_traits::{ExternalImageData, ExternalImageId, ScrollLayerId};
-use webrender_traits::{ImageData};
-use webrender_traits::{DeviceUintRect};
+use webrender_traits::{ClipId, ColorF, DeviceUintRect, Epoch, ExternalImageData, ExternalImageId};
+use webrender_traits::{ImageData, ImageFormat, NativeFontHandle, PipelineId};
 
 // An ID for a texture that is owned by the
 // texture cache module. This can include atlases
 // or standalone textures allocated via the
 // texture cache (e.g. if an image is too large
 // to be added to an atlas). The texture cache
 // manages the allocation and freeing of these
 // IDs, and the rendering thread maintains a
@@ -325,24 +322,24 @@ impl TextureUpdateList {
 
 /// Mostly wraps a tiling::Frame, adding a bit of extra information.
 pub struct RendererFrame {
     /// The last rendered epoch for each pipeline present in the frame.
     /// This information is used to know if a certain transformation on the layout has
     /// been rendered, which is necessary for reftests.
     pub pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
     /// The layers that are currently affected by the over-scrolling animation.
-    pub layers_bouncing_back: HashSet<ScrollLayerId, BuildHasherDefault<FnvHasher>>,
+    pub layers_bouncing_back: HashSet<ClipId, BuildHasherDefault<FnvHasher>>,
 
     pub frame: Option<tiling::Frame>,
 }
 
 impl RendererFrame {
     pub fn new(pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
-               layers_bouncing_back: HashSet<ScrollLayerId, BuildHasherDefault<FnvHasher>>,
+               layers_bouncing_back: HashSet<ClipId, BuildHasherDefault<FnvHasher>>,
                frame: Option<tiling::Frame>)
                -> RendererFrame {
         RendererFrame {
             pipeline_epoch_map: pipeline_epoch_map,
             layers_bouncing_back: layers_bouncing_back,
             frame: frame,
         }
     }
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -40,17 +40,16 @@
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate bitflags;
 #[macro_use]
 extern crate thread_profiler;
 
-mod batch_builder;
 mod border;
 mod clip_scroll_node;
 mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 mod device;
 mod frame;
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -5,26 +5,38 @@
 use gpu_store::GpuStoreAddress;
 use prim_store::{ClipData, GpuBlock32, PrimitiveStore};
 use prim_store::{CLIP_DATA_GPU_SIZE, MASK_DATA_GPU_SIZE};
 use renderer::VertexDataStore;
 use util::{ComplexClipRegionHelpers, MatrixHelpers, TransformedRect};
 use webrender_traits::{AuxiliaryLists, BorderRadius, ClipRegion, ComplexClipRegion, ImageMask};
 use webrender_traits::{DeviceIntRect, LayerToWorldTransform};
 use webrender_traits::{LayerRect, LayerPoint, LayerSize};
+use std::ops::Not;
 
 const MAX_CLIP: f32 = 1000000.0;
 
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum ClipMode {
     Clip,           // Pixels inside the region are visible.
     ClipOut,        // Pixels outside the region are visible.
 }
 
+impl Not for ClipMode {
+    type Output = ClipMode;
+
+    fn not(self) -> ClipMode {
+        match self {
+            ClipMode::Clip => ClipMode::ClipOut,
+            ClipMode::ClipOut => ClipMode::Clip
+        }
+    }
+}
+
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum RegionMode {
     IncludeRect,
     ExcludeRect,
 }
 
 #[derive(Clone, Debug)]
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -186,16 +186,19 @@ impl YuvImagePrimitiveGpu {
             color_space: color_space as u32 as f32,
             padding: 0.0,
         }
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct BorderPrimitiveCpu {
+    // TODO(gw): Remove this when all border kinds are switched
+    //           over to the new border path!
+    pub use_new_border_path: bool,
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 pub struct BorderPrimitiveGpu {
     pub style: [f32; 4],
     pub widths: [f32; 4],
     pub colors: [ColorF; 4],
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -266,24 +266,26 @@ impl FrameProfileCounters {
     }
 }
 
 #[derive(Clone)]
 pub struct TextureCacheProfileCounters {
     pub pages_a8: ResourceProfileCounter,
     pub pages_rgb8: ResourceProfileCounter,
     pub pages_rgba8: ResourceProfileCounter,
+    pub pages_rg8: ResourceProfileCounter,
 }
 
 impl TextureCacheProfileCounters {
     pub fn new() -> TextureCacheProfileCounters {
         TextureCacheProfileCounters {
             pages_a8: ResourceProfileCounter::new("Texture A8 cached pages"),
             pages_rgb8: ResourceProfileCounter::new("Texture RGB8 cached pages"),
             pages_rgba8: ResourceProfileCounter::new("Texture RGBA8 cached pages"),
+            pages_rg8: ResourceProfileCounter::new("Texture RG8 cached pages"),
         }
     }
 }
 
 #[derive(Clone)]
 pub struct BackendProfileCounters {
     pub font_templates: ResourceProfileCounter,
     pub image_templates: ResourceProfileCounter,
@@ -649,16 +651,23 @@ impl Profiler {
                         debug_renderer: &mut DebugRenderer) {
 
         let _gm = GpuMarker::new(device.rc_gl(), "profile");
         self.x_left = 20.0;
         self.y_left = 40.0;
         self.x_right = 400.0;
         self.y_right = 40.0;
 
+        let mut gpu_time = 0;
+        let gpu_samples = mem::replace(&mut renderer_timers.gpu_samples, Vec::new());
+        for sample in &gpu_samples {
+            gpu_time += sample.time_ns;
+        }
+        renderer_timers.gpu_time.set(gpu_time);
+
         self.draw_counters(&[
             &renderer_profile.frame_counter,
             &renderer_profile.frame_time,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &frame_profile.total_primitives,
             &frame_profile.visible_primitives,
@@ -671,34 +680,30 @@ impl Profiler {
             &backend_profile.font_templates,
             &backend_profile.image_templates,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &backend_profile.texture_cache.pages_a8,
             &backend_profile.texture_cache.pages_rgb8,
             &backend_profile.texture_cache.pages_rgba8,
+            &backend_profile.texture_cache.pages_rg8,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &renderer_profile.draw_calls,
             &renderer_profile.vertices,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &backend_profile.total_time,
             &renderer_timers.cpu_time,
             &renderer_timers.gpu_time,
         ], debug_renderer, false);
 
-        let mut gpu_time = 0;
-        let gpu_samples = mem::replace(&mut renderer_timers.gpu_samples, Vec::new());
-        for sample in &gpu_samples {
-            gpu_time += sample.time_ns;
-        }
 
         self.backend_time.push(backend_profile.total_time.nanoseconds);
         self.compositor_time.push(renderer_timers.cpu_time.nanoseconds);
         self.gpu_time.push(gpu_time);
         self.gpu_frames.push(gpu_time, gpu_samples);
 
         let rect = self.backend_time.draw_graph(self.x_left, self.y_left, "CPU (backend)", debug_renderer);
         self.y_left += rect.size.height + PROFILE_PADDING;
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -250,18 +250,18 @@ impl RenderBackend {
                             match frame {
                                 Some(frame) => {
                                     self.publish_frame(frame, &mut profile_counters);
                                     self.notify_compositor_of_new_scroll_frame(true)
                                 }
                                 None => self.notify_compositor_of_new_scroll_frame(false),
                             }
                         }
-                        ApiMsg::ScrollLayerWithId(origin, id) => {
-                            profile_scope!("ScrollLayerWithScrollId");
+                        ApiMsg::ScrollNodeWithId(origin, id) => {
+                            profile_scope!("ScrollNodeWithScrollId");
                             let frame = {
                                 let counters = &mut profile_counters.texture_cache;
                                 profile_counters.total_time.profile(|| {
                                     if self.frame.scroll_nodes(origin, id) {
                                         Some(self.render(counters))
                                     } else {
                                         None
                                     }
@@ -287,20 +287,19 @@ impl RenderBackend {
                                 })
                             };
 
                             self.publish_frame_and_notify_compositor(frame, &mut profile_counters);
                         }
                         ApiMsg::TranslatePointToLayerSpace(..) => {
                             panic!("unused api - remove from webrender_traits");
                         }
-                        ApiMsg::GetScrollLayerState(tx) => {
-                            profile_scope!("GetScrollLayerState");
-                            tx.send(self.frame.get_scroll_node_state())
-                              .unwrap()
+                        ApiMsg::GetScrollNodeState(tx) => {
+                            profile_scope!("GetScrollNodeState");
+                            tx.send(self.frame.get_scroll_node_state()).unwrap()
                         }
                         ApiMsg::RequestWebGLContext(size, attributes, tx) => {
                             if let Some(ref wrapper) = self.webrender_context_handle {
                                 let dispatcher: Option<Box<GLContextDispatcher>> = if cfg!(target_os = "windows") {
                                     Some(Box::new(WebRenderGLDispatcher {
                                         dispatcher: Arc::clone(&self.main_thread_dispatcher)
                                     }))
                                 } else {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
 use mask_cache::{MaskBounds, MaskCacheInfo};
 use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, mem, usize};
 use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind, StackingContextIndex};
-use webrender_traits::{DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use webrender_traits::{MixBlendMode, ScrollLayerId};
+use webrender_traits::{ClipId, DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
+use webrender_traits::MixBlendMode;
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 pub struct RenderTaskIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum RenderTaskKey {
@@ -28,17 +28,17 @@ pub enum RenderTaskKey {
     HorizontalBlur(i32, PrimitiveIndex),
     /// Allocate a block of space in target for framebuffer copy.
     CopyFramebuffer(StackingContextIndex),
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum MaskCacheKey {
     Primitive(PrimitiveIndex),
-    ScrollLayer(ScrollLayerId),
+    ClipNode(ClipId),
 }
 
 #[derive(Debug, Copy, Clone)]
 pub enum RenderTaskId {
     Static(RenderTaskIndex),
     Dynamic(RenderTaskKey),
 }
 
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -71,16 +71,18 @@ const GPU_TAG_PRIM_BLEND: GpuProfileTag 
 const GPU_TAG_PRIM_HW_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "HwComposite", color: debug_colors::DODGERBLUE };
 const GPU_TAG_PRIM_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "Composite", color: debug_colors::MAGENTA };
 const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "TextRun", color: debug_colors::BLUE };
 const GPU_TAG_PRIM_GRADIENT: GpuProfileTag = GpuProfileTag { label: "Gradient", color: debug_colors::YELLOW };
 const GPU_TAG_PRIM_ANGLE_GRADIENT: GpuProfileTag = GpuProfileTag { label: "AngleGradient", color: debug_colors::POWDERBLUE };
 const GPU_TAG_PRIM_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag { label: "RadialGradient", color: debug_colors::LIGHTPINK };
 const GPU_TAG_PRIM_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "BoxShadow", color: debug_colors::CYAN };
 const GPU_TAG_PRIM_BORDER: GpuProfileTag = GpuProfileTag { label: "Border", color: debug_colors::ORANGE };
+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_PRIM_CACHE_IMAGE: GpuProfileTag = GpuProfileTag { label: "CacheImage", color: debug_colors::SILVER };
 const GPU_TAG_BLUR: GpuProfileTag = GpuProfileTag { label: "Blur", color: debug_colors::VIOLET };
 
 #[derive(Debug, Copy, Clone)]
 pub enum RendererKind {
     Native,
     OSMesa,
 }
@@ -465,16 +467,18 @@ pub struct Renderer {
     ps_rectangle: PrimitiveShader,
     ps_rectangle_clip: PrimitiveShader,
     ps_text_run: PrimitiveShader,
     ps_text_run_subpixel: PrimitiveShader,
     ps_image: PrimitiveShader,
     ps_image_rect: PrimitiveShader,
     ps_yuv_image: PrimitiveShader,
     ps_border: PrimitiveShader,
+    ps_border_corner: PrimitiveShader,
+    ps_border_edge: PrimitiveShader,
     ps_gradient: PrimitiveShader,
     ps_angle_gradient: PrimitiveShader,
     ps_radial_gradient: PrimitiveShader,
     ps_box_shadow: PrimitiveShader,
     ps_cache_image: PrimitiveShader,
 
     ps_blend: LazilyCompiledShader,
     ps_hw_composite: LazilyCompiledShader,
@@ -689,16 +693,30 @@ impl Renderer {
 
         let ps_border = try!{
             PrimitiveShader::new("ps_border",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
+        let ps_border_corner = try!{
+            PrimitiveShader::new("ps_border_corner",
+                                 &mut device,
+                                 &[],
+                                 options.precache_shaders)
+        };
+
+        let ps_border_edge = try!{
+            PrimitiveShader::new("ps_border_edge",
+                                 &mut device,
+                                 &[],
+                                 options.precache_shaders)
+        };
+
         let ps_box_shadow = try!{
             PrimitiveShader::new("ps_box_shadow",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
         let ps_gradient = try!{
@@ -824,17 +842,16 @@ impl Renderer {
             GpuDataTextures::new(&mut device),
         ];
 
         let x0 = 0.0;
         let y0 = 0.0;
         let x1 = 1.0;
         let y1 = 1.0;
 
-        // TODO(gw): Consider separate VBO for quads vs border corners if VS ever shows up in profile!
         let quad_indices: [u16; 6] = [ 0, 1, 2, 2, 1, 3 ];
         let quad_vertices = [
             PackedVertex {
                 pos: [x0, y0],
             },
             PackedVertex {
                 pos: [x1, y0],
             },
@@ -920,16 +937,18 @@ impl Renderer {
             ps_rectangle: ps_rectangle,
             ps_rectangle_clip: ps_rectangle_clip,
             ps_text_run: ps_text_run,
             ps_text_run_subpixel: ps_text_run_subpixel,
             ps_image: ps_image,
             ps_image_rect: ps_image_rect,
             ps_yuv_image: ps_yuv_image,
             ps_border: ps_border,
+            ps_border_corner: ps_border_corner,
+            ps_border_edge: ps_border_edge,
             ps_box_shadow: ps_box_shadow,
             ps_gradient: ps_gradient,
             ps_angle_gradient: ps_angle_gradient,
             ps_radial_gradient: ps_radial_gradient,
             ps_cache_image: ps_cache_image,
             ps_blend: ps_blend,
             ps_hw_composite: ps_hw_composite,
             ps_composite: ps_composite,
@@ -947,23 +966,23 @@ impl Renderer {
             color_render_targets: Vec::new(),
             alpha_render_targets: Vec::new(),
             gpu_profile: gpu_profile,
             prim_vao_id: prim_vao_id,
             blur_vao_id: blur_vao_id,
             clip_vao_id: clip_vao_id,
             gdt_index: 0,
             gpu_data_textures: gpu_data_textures,
-            pipeline_epoch_map: HashMap::with_hasher(Default::default()),
+            pipeline_epoch_map: HashMap::default(),
             main_thread_dispatcher: main_thread_dispatcher,
             cache_texture_id_map: Vec::new(),
             dummy_cache_texture_id: dummy_cache_texture_id,
             dither_matrix_texture_id: dither_matrix_texture_id,
             external_image_handler: None,
-            external_images: HashMap::with_hasher(Default::default()),
+            external_images: HashMap::default(),
             vr_compositor_handler: vr_compositor,
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
         };
 
         let sender = RenderApiSender::new(api_tx, payload_tx);
         Ok((renderer, sender))
     }
@@ -1001,17 +1020,17 @@ impl Renderer {
     /// Returns the Epoch of the current frame in a pipeline.
     pub fn current_epoch(&self, pipeline_id: PipelineId) -> Option<Epoch> {
         self.pipeline_epoch_map.get(&pipeline_id).cloned()
     }
 
     /// Returns a HashMap containing the pipeline ids that have been received by the renderer and
     /// their respective epochs since the last time the method was called.
     pub fn flush_rendered_epochs(&mut self) -> HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>> {
-        mem::replace(&mut self.pipeline_epoch_map, HashMap::with_hasher(Default::default()))
+        mem::replace(&mut self.pipeline_epoch_map, HashMap::default())
     }
 
     /// Processes the result queue.
     ///
     /// Should be called before `render()`, as texture cache updates are done here.
     pub fn update(&mut self) {
         profile_scope!("update");
 
@@ -1365,16 +1384,24 @@ impl Renderer {
             AlphaBatchKind::YuvImage => {
                 let shader = self.ps_yuv_image.get(&mut self.device, transform_kind);
                 (GPU_TAG_PRIM_YUV_IMAGE, shader)
             }
             AlphaBatchKind::Border => {
                 let shader = self.ps_border.get(&mut self.device, transform_kind);
                 (GPU_TAG_PRIM_BORDER, shader)
             }
+            AlphaBatchKind::BorderCorner => {
+                let shader = self.ps_border_corner.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_BORDER_CORNER, shader)
+            }
+            AlphaBatchKind::BorderEdge => {
+                let shader = self.ps_border_edge.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_BORDER_EDGE, shader)
+            }
             AlphaBatchKind::AlignedGradient => {
                 let shader = self.ps_gradient.get(&mut self.device, transform_kind);
                 (GPU_TAG_PRIM_GRADIENT, shader)
             }
             AlphaBatchKind::AngleGradient => {
                 let shader = self.ps_angle_gradient.get(&mut self.device, transform_kind);
                 (GPU_TAG_PRIM_ANGLE_GRADIENT, shader)
             }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -124,18 +124,18 @@ struct CachedImageInfo {
 pub struct ResourceClassCache<K,V> {
     resources: HashMap<K, V, BuildHasherDefault<FnvHasher>>,
     last_access_times: HashMap<K, FrameId, BuildHasherDefault<FnvHasher>>,
 }
 
 impl<K,V> ResourceClassCache<K,V> where K: Clone + Hash + Eq + Debug, V: Resource {
     fn new() -> ResourceClassCache<K,V> {
         ResourceClassCache {
-            resources: HashMap::with_hasher(Default::default()),
-            last_access_times: HashMap::with_hasher(Default::default()),
+            resources: HashMap::default(),
+            last_access_times: HashMap::default(),
         }
     }
 
     fn contains_key(&self, key: &K) -> bool {
         self.resources.contains_key(key)
     }
 
     fn get(&self, key: &K, frame: FrameId) -> &V {
@@ -230,20 +230,20 @@ impl ResourceCache {
                workers: Arc<Mutex<ThreadPool>>,
                blob_image_renderer: Option<Box<BlobImageRenderer>>,
                enable_aa: bool) -> ResourceCache {
         let (glyph_cache_tx, glyph_cache_result_queue) = spawn_glyph_cache_thread(workers);
 
         ResourceCache {
             cached_glyphs: Some(ResourceClassCache::new()),
             cached_images: ResourceClassCache::new(),
-            webgl_textures: HashMap::with_hasher(Default::default()),
-            font_templates: HashMap::with_hasher(Default::default()),
-            image_templates: HashMap::with_hasher(Default::default()),
-            cached_glyph_dimensions: HashMap::with_hasher(Default::default()),
+            webgl_textures: HashMap::default(),
+            font_templates: HashMap::default(),
+            image_templates: HashMap::default(),
+            cached_glyph_dimensions: HashMap::default(),
             texture_cache: texture_cache,
             state: State::Idle,
             enable_aa: enable_aa,
             current_frame_id: FrameId(0),
             pending_image_requests: Vec::new(),
             glyph_cache_tx: glyph_cache_tx,
             glyph_cache_result_queue: glyph_cache_result_queue,
             pending_external_image_update_list: ExternalImageUpdateList::new(),
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -16,18 +16,18 @@ use webrender_traits::{PropertyBinding, 
 pub struct SceneProperties {
     transform_properties: HashMap<PropertyBindingId, LayoutTransform>,
     float_properties: HashMap<PropertyBindingId, f32>,
 }
 
 impl SceneProperties {
     pub fn new() -> SceneProperties {
         SceneProperties {
-            transform_properties: HashMap::with_hasher(Default::default()),
-            float_properties: HashMap::with_hasher(Default::default()),
+            transform_properties: HashMap::default(),
+            float_properties: HashMap::default(),
         }
     }
 
     /// Set the current property list for this display list.
     pub fn set_properties(&mut self, properties: DynamicProperties) {
         self.transform_properties.clear();
         self.float_properties.clear();
 
@@ -97,19 +97,19 @@ pub struct Scene {
     pub display_lists: HashMap<PipelineId, Vec<DisplayItem>, BuildHasherDefault<FnvHasher>>,
     pub properties: SceneProperties,
 }
 
 impl Scene {
     pub fn new() -> Scene {
         Scene {
             root_pipeline_id: None,
-            pipeline_map: HashMap::with_hasher(Default::default()),
-            pipeline_auxiliary_lists: HashMap::with_hasher(Default::default()),
-            display_lists: HashMap::with_hasher(Default::default()),
+            pipeline_map: HashMap::default(),
+            pipeline_auxiliary_lists: HashMap::default(),
+            display_lists: HashMap::default(),
             properties: SceneProperties::new(),
         }
     }
 
     pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) {
         self.root_pipeline_id = Some(pipeline_id);
     }
 
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -460,30 +460,33 @@ impl TextureCacheItem {
         }
     }
 }
 
 struct TextureCacheArena {
     pages_a8: Vec<TexturePage>,
     pages_rgb8: Vec<TexturePage>,
     pages_rgba8: Vec<TexturePage>,
+    pages_rg8: Vec<TexturePage>,
 }
 
 impl TextureCacheArena {
     fn new() -> TextureCacheArena {
         TextureCacheArena {
             pages_a8: Vec::new(),
             pages_rgb8: Vec::new(),
             pages_rgba8: Vec::new(),
+            pages_rg8: Vec::new(),
         }
     }
 
     fn texture_page_for_id(&mut self, id: CacheTextureId) -> Option<&mut TexturePage> {
         for page in self.pages_a8.iter_mut().chain(self.pages_rgb8.iter_mut())
-                                            .chain(self.pages_rgba8.iter_mut()) {
+                                            .chain(self.pages_rgba8.iter_mut())
+                                            .chain(self.pages_rg8.iter_mut()) {
             if page.texture_id == id {
                 return Some(page)
             }
         }
         None
     }
 }
 
@@ -541,17 +544,17 @@ pub struct AllocationResult {
 impl TextureCache {
     pub fn new(mut max_texture_size: u32) -> TextureCache {
         if max_texture_size * max_texture_size > MAX_RGBA_PIXELS_PER_TEXTURE {
             max_texture_size = SQRT_MAX_RGBA_PIXELS_PER_TEXTURE;
         }
 
         TextureCache {
             cache_id_list: CacheTextureIdList::new(),
-            free_texture_levels: HashMap::with_hasher(Default::default()),
+            free_texture_levels: HashMap::default(),
             items: FreeList::new(),
             pending_updates: TextureUpdateList::new(),
             arena: TextureCacheArena::new(),
             max_texture_size: max_texture_size,
         }
     }
 
     pub fn max_texture_size(&self) -> u32 {
@@ -602,16 +605,17 @@ impl TextureCache {
             }
         }
 
         let mode = RenderTargetMode::SimpleRenderTarget;
         let (page_list, page_profile) = match format {
             ImageFormat::A8 => (&mut self.arena.pages_a8, &mut profile.pages_a8),
             ImageFormat::RGBA8 => (&mut self.arena.pages_rgba8, &mut profile.pages_rgba8),
             ImageFormat::RGB8 => (&mut self.arena.pages_rgb8, &mut profile.pages_rgb8),
+            ImageFormat::RG8 => (&mut self.arena.pages_rg8, &mut profile.pages_rg8),
             ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
         };
 
         // TODO(gw): Handle this sensibly (support failing to render items that can't fit?)
         assert!(requested_size.width <= self.max_texture_size);
         assert!(requested_size.height <= self.max_texture_size);
 
         let mut page_id = None; //using ID here to please the borrow checker
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -18,22 +18,21 @@ use render_task::{RenderTaskId, RenderTa
 use render_task::RenderTaskLocation;
 use renderer::BlendMode;
 use resource_cache::ResourceCache;
 use std::{f32, i32, mem, usize};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use texture_cache::TexturePage;
 use util::{TransformedRect, TransformedRectKind};
-use webrender_traits::{AuxiliaryLists, ColorF, DeviceIntPoint, DeviceIntRect};
-use webrender_traits::{DeviceIntSize, DeviceUintPoint};
-use webrender_traits::{DeviceUintSize, FontRenderMode, ImageRendering, LayerPoint, LayerRect};
-use webrender_traits::{LayerToWorldTransform, MixBlendMode, PipelineId, ScrollLayerId};
-use webrender_traits::{TransformStyle, WorldPoint4D, WorldToLayerTransform};
-use webrender_traits::{ExternalImageType};
+use webrender_traits::{AuxiliaryLists, ClipId, ColorF, DeviceIntPoint, DeviceIntRect};
+use webrender_traits::{DeviceIntSize, DeviceUintPoint, DeviceUintSize, ExternalImageType};
+use webrender_traits::{FontRenderMode, ImageRendering, LayerPoint, LayerRect};
+use webrender_traits::{LayerToWorldTransform, MixBlendMode, PipelineId, TransformStyle};
+use webrender_traits::{WorldPoint4D, WorldToLayerTransform};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_INDEX: RenderTaskIndex = RenderTaskIndex(i32::MAX as usize);
 
 
 pub type AuxiliaryListsMap = HashMap<PipelineId,
                                      AuxiliaryLists,
@@ -103,32 +102,32 @@ impl AlphaBatchHelpers for PrimitiveStor
                 }
             }
         }
     }
 }
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
-    pub scroll_layer_id: ScrollLayerId,
+    pub clip_id: ClipId,
     pub prim_index: PrimitiveIndex,
     pub border_radius: f32,
 }
 
 #[derive(Debug)]
 pub enum PrimitiveRunCmd {
     PushStackingContext(StackingContextIndex),
     PopStackingContext,
-    PrimitiveRun(PrimitiveIndex, usize, ScrollLayerId),
+    PrimitiveRun(PrimitiveIndex, usize, ClipId),
 }
 
 #[derive(Debug, Copy, Clone)]
 pub enum PrimitiveFlags {
     None,
-    Scrollbar(ScrollLayerId, f32)
+    Scrollbar(ClipId, f32)
 }
 
 #[derive(Debug, Copy, Clone)]
 pub struct RenderTargetIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub struct RenderPassIndex(isize);
 
@@ -141,17 +140,17 @@ pub struct RenderTaskCollection {
     pub render_task_data: Vec<RenderTaskData>,
     dynamic_tasks: HashMap<(RenderTaskKey, RenderPassIndex), DynamicTaskInfo, BuildHasherDefault<FnvHasher>>,
 }
 
 impl RenderTaskCollection {
     pub fn new(static_render_task_count: usize) -> RenderTaskCollection {
         RenderTaskCollection {
             render_task_data: vec![RenderTaskData::empty(); static_render_task_count],
-            dynamic_tasks: HashMap::with_hasher(Default::default()),
+            dynamic_tasks: HashMap::default(),
         }
     }
 
     fn add(&mut self, task: &RenderTask, pass: RenderPassIndex) -> RenderTaskIndex {
         match task.id {
             RenderTaskId::Static(index) => {
                 self.render_task_data[index.0] = task.write_task_data();
                 index
@@ -219,16 +218,24 @@ pub struct BatchList {
 impl BatchList {
     fn new() -> BatchList {
         BatchList {
             alpha_batches: Vec::new(),
             opaque_batches: Vec::new(),
         }
     }
 
+    fn with_suitable_batch<F>(&mut self,
+                              key: &AlphaBatchKey,
+                              item_bounding_rect: &DeviceIntRect,
+                              f: F) where F: Fn(&mut PrimitiveBatch) {
+        let batch = self.get_suitable_batch(key, item_bounding_rect);
+        f(batch)
+    }
+
     fn get_suitable_batch(&mut self,
                           key: &AlphaBatchKey,
                           item_bounding_rect: &DeviceIntRect) -> &mut PrimitiveBatch {
         let (batches, check_intersections) = match key.blend_mode {
             BlendMode::None => {
                 (&mut self.opaque_batches, false)
             }
             BlendMode::Alpha | BlendMode::PremultipliedAlpha | BlendMode::Subpixel(..) => {
@@ -409,20 +416,39 @@ impl AlphaRenderItem {
                     prim_address: prim_address,
                     sub_index: 0,
                     user_data: [0, 0],
                     z_sort_index: z,
                 };
 
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Border => {
-                        let key = AlphaBatchKey::new(AlphaBatchKind::Border, flags, blend_mode, textures);
-                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                        for border_segment in 0..8 {
-                            batch.add_instance(base_instance.build(border_segment, 0, 0));
+                        let border_cpu = &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
+                        if border_cpu.use_new_border_path {
+                            // TODO(gw): Select correct blend mode for edges and corners!!
+                            let corner_key = AlphaBatchKey::new(AlphaBatchKind::BorderCorner, flags, blend_mode, textures);
+                            let edge_key = AlphaBatchKey::new(AlphaBatchKind::BorderEdge, flags, blend_mode, textures);
+
+                            batch_list.with_suitable_batch(&corner_key, item_bounding_rect, |batch| {
+                                for border_segment in 0..4 {
+                                    batch.add_instance(base_instance.build(border_segment, 0, 0));
+                                }
+                            });
+
+                            batch_list.with_suitable_batch(&edge_key, item_bounding_rect, |batch| {
+                                for border_segment in 0..4 {
+                                    batch.add_instance(base_instance.build(border_segment, 0, 0));
+                                }
+                            });
+                        } else {
+                            let key = AlphaBatchKey::new(AlphaBatchKind::Border, flags, blend_mode, textures);
+                            let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                            for border_segment in 0..8 {
+                                batch.add_instance(base_instance.build(border_segment, 0, 0));
+                            }
                         }
                     }
                     PrimitiveKind::Rectangle => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::Rectangle, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance);
                     }
                     PrimitiveKind::Image => {
@@ -1093,16 +1119,18 @@ pub enum AlphaBatchKind {
     ImageRect,
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
     CacheImage,
+    BorderCorner,
+    BorderEdge,
 }
 
 bitflags! {
     pub flags AlphaBatchKeyFlags: u8 {
         const NEEDS_CLIPPING  = 0b00000001,
         const AXIS_ALIGNED    = 0b00000010,
     }
 }
@@ -1279,44 +1307,44 @@ impl StackingContext {
             composite_ops: composite_ops,
             clip_scroll_groups: Vec::new(),
             should_isolate: transform_style == TransformStyle::Preserve3D, //TODO
             is_page_root: is_page_root,
             is_visible: false,
         }
     }
 
-    pub fn clip_scroll_group(&self, scroll_layer_id: ScrollLayerId) -> ClipScrollGroupIndex {
+    pub fn clip_scroll_group(&self, clip_id: ClipId) -> ClipScrollGroupIndex {
         // Currently there is only one scrolled stacking context per context,
         // but eventually this will be selected from the vector based on the
         // scroll layer of this primitive.
         for group in &self.clip_scroll_groups {
-            if group.1 == scroll_layer_id {
+            if group.1 == clip_id {
                 return *group;
             }
         }
         unreachable!("Looking for non-existent ClipScrollGroup");
     }
 
     pub fn can_contribute_to_scene(&self) -> bool {
         !self.composite_ops.will_make_invisible()
     }
 
-    pub fn has_clip_scroll_group(&self, id: ScrollLayerId) -> bool {
+    pub fn has_clip_scroll_group(&self, id: ClipId) -> bool {
         self.clip_scroll_groups.iter().rev().any(|index| index.1 == id)
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct ClipScrollGroupIndex(pub usize, pub ScrollLayerId);
+pub struct ClipScrollGroupIndex(pub usize, pub ClipId);
 
 #[derive(Debug)]
 pub struct ClipScrollGroup {
     pub stacking_context_index: StackingContextIndex,
-    pub scroll_layer_id: ScrollLayerId,
+    pub clip_id: ClipId,
     pub packed_layer_index: PackedLayerIndex,
     pub xf_rect: Option<TransformedRect>,
 }
 
 impl ClipScrollGroup {
     pub fn is_visible(&self) -> bool {
         self.xf_rect.is_some()
     }
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -296,25 +296,23 @@ impl ComplexClipRegionHelpers for Comple
     }
 }
 
 #[inline]
 fn extract_inner_rect_impl<U>(rect: &TypedRect<f32, U>,
                               radii: &BorderRadius,
                               k: f32) -> Option<TypedRect<f32, U>> {
     // `k` defines how much border is taken into account
+    // We enforce the offsets to be rounded to pixel boundaries
+    // by `ceil`-ing and `floor`-ing them
 
-    let xl = rect.origin.x +
-        k * radii.top_left.width.max(radii.bottom_left.width);
-    let xr = rect.origin.x + rect.size.width -
-        k * radii.top_right.width.max(radii.bottom_right.width);
-    let yt = rect.origin.y +
-        k * radii.top_left.height.max(radii.top_right.height);
-    let yb = rect.origin.y + rect.size.height -
-        k * radii.bottom_left.height.max(radii.bottom_right.height);
+    let xl = (k * radii.top_left.width.max(radii.bottom_left.width)).ceil();
+    let xr = (rect.size.width - k * radii.top_right.width.max(radii.bottom_right.width)).floor();
+    let yt = (k * radii.top_left.height.max(radii.top_right.height)).ceil();
+    let yb = (rect.size.height - k * radii.bottom_left.height.max(radii.bottom_right.height)).floor();
 
     if xl <= xr && yt <= yb {
-        Some(TypedRect::new(TypedPoint2D::new(xl, yt),
+        Some(TypedRect::new(TypedPoint2D::new(rect.origin.x + xl, rect.origin.y + yt),
              TypedSize2D::new(xr-xl, yb-yt)))
     } else {
         None
     }
 }
--- a/gfx/webrender_traits/Cargo.toml
+++ b/gfx/webrender_traits/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender_traits"
-version = "0.32.0"
+version = "0.33.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 webgl = ["offscreen_gl_context"]
--- a/gfx/webrender_traits/src/api.rs
+++ b/gfx/webrender_traits/src/api.rs
@@ -4,19 +4,19 @@
 
 use channel::{self, MsgSender, Payload, PayloadSenderHelperMethods, PayloadSender};
 #[cfg(feature = "webgl")]
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use {AuxiliaryLists, AuxiliaryListsDescriptor, BuiltDisplayList, BuiltDisplayListDescriptor};
-use {ColorF, DeviceIntPoint, DeviceIntSize, DeviceUintRect, DeviceUintSize, FontKey};
+use {ClipId, ColorF, DeviceIntPoint, DeviceIntSize, DeviceUintRect, DeviceUintSize, FontKey};
 use {GlyphDimensions, GlyphKey, ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize};
-use {LayoutTransform, NativeFontHandle, ScrollLayerId, WorldPoint};
+use {LayoutTransform, NativeFontHandle, WorldPoint};
 #[cfg(feature = "webgl")]
 use {WebGLCommand, WebGLContextId};
 
 pub type TileSize = u16;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     AddRawFont(FontKey, Vec<u8>),
@@ -43,20 +43,20 @@ pub enum ApiMsg {
                    AuxiliaryListsDescriptor,
                    bool),
     SetPageZoom(ZoomFactor),
     SetPinchZoom(ZoomFactor),
     SetPan(DeviceIntPoint),
     SetRootPipeline(PipelineId),
     SetWindowParameters(DeviceUintSize, DeviceUintRect),
     Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
-    ScrollLayerWithId(LayoutPoint, ScrollLayerId),
+    ScrollNodeWithId(LayoutPoint, ClipId),
     TickScrollingBounce,
     TranslatePointToLayerSpace(WorldPoint, MsgSender<(LayoutPoint, PipelineId)>),
-    GetScrollLayerState(MsgSender<Vec<ScrollLayerState>>),
+    GetScrollNodeState(MsgSender<Vec<ScrollLayerState>>),
     RequestWebGLContext(DeviceIntSize, GLContextAttributes, MsgSender<Result<(WebGLContextId, GLLimits), String>>),
     ResizeWebGLContext(WebGLContextId, DeviceIntSize),
     WebGLCommand(WebGLContextId, WebGLCommand),
     GenerateFrame(Option<DynamicProperties>),
     // WebVR commands that must be called in the WebGL render thread.
     VRCompositorCommand(WebGLContextId, VRCompositorCommand),
     /// An opaque handle that must be passed to the render notifier. It is used by Gecko
     /// to forward gecko-specific messages to the render thread preserving the ordering
@@ -74,20 +74,20 @@ impl fmt::Debug for ApiMsg {
             ApiMsg::GetGlyphDimensions(..) => "ApiMsg::GetGlyphDimensions",
             ApiMsg::AddImage(..) => "ApiMsg::AddImage",
             ApiMsg::UpdateImage(..) => "ApiMsg::UpdateImage",
             ApiMsg::DeleteImage(..) => "ApiMsg::DeleteImage",
             ApiMsg::CloneApi(..) => "ApiMsg::CloneApi",
             ApiMsg::SetDisplayList(..) => "ApiMsg::SetDisplayList",
             ApiMsg::SetRootPipeline(..) => "ApiMsg::SetRootPipeline",
             ApiMsg::Scroll(..) => "ApiMsg::Scroll",
-            ApiMsg::ScrollLayerWithId(..) => "ApiMsg::ScrollLayerWithId",
+            ApiMsg::ScrollNodeWithId(..) => "ApiMsg::ScrollNodeWithId",
             ApiMsg::TickScrollingBounce => "ApiMsg::TickScrollingBounce",
             ApiMsg::TranslatePointToLayerSpace(..) => "ApiMsg::TranslatePointToLayerSpace",
-            ApiMsg::GetScrollLayerState(..) => "ApiMsg::GetScrollLayerState",
+            ApiMsg::GetScrollNodeState(..) => "ApiMsg::GetScrollNodeState",
             ApiMsg::RequestWebGLContext(..) => "ApiMsg::RequestWebGLContext",
             ApiMsg::ResizeWebGLContext(..) => "ApiMsg::ResizeWebGLContext",
             ApiMsg::WebGLCommand(..) => "ApiMsg::WebGLCommand",
             ApiMsg::GenerateFrame(..) => "ApiMsg::GenerateFrame",
             ApiMsg::VRCompositorCommand(..) => "ApiMsg::VRCompositorCommand",
             ApiMsg::ExternalEvent(..) => "ApiMsg::ExternalEvent",
             ApiMsg::ShutDown => "ApiMsg::ShutDown",
             ApiMsg::SetPageZoom(..) => "ApiMsg::SetPageZoom",
@@ -329,18 +329,18 @@ impl RenderApi {
     ///
     /// WebRender looks for the layer closest to the user
     /// which has `ScrollPolicy::Scrollable` set.
     pub fn scroll(&self, scroll_location: ScrollLocation, cursor: WorldPoint, phase: ScrollEventPhase) {
         let msg = ApiMsg::Scroll(scroll_location, cursor, phase);
         self.api_sender.send(msg).unwrap();
     }
 
-    pub fn scroll_layer_with_id(&self, new_scroll_origin: LayoutPoint, id: ScrollLayerId) {
-        let msg = ApiMsg::ScrollLayerWithId(new_scroll_origin, id);
+    pub fn scroll_node_with_id(&self, new_scroll_origin: LayoutPoint, id: ClipId) {
+        let msg = ApiMsg::ScrollNodeWithId(new_scroll_origin, id);
         self.api_sender.send(msg).unwrap();
     }
 
     pub fn set_page_zoom(&self, page_zoom: ZoomFactor) {
         let msg = ApiMsg::SetPageZoom(page_zoom);
         self.api_sender.send(msg).unwrap();
     }
 
@@ -370,19 +370,19 @@ impl RenderApi {
     pub fn translate_point_to_layer_space(&self, point: &WorldPoint)
                                           -> (LayoutPoint, PipelineId) {
         let (tx, rx) = channel::msg_channel().unwrap();
         let msg = ApiMsg::TranslatePointToLayerSpace(*point, tx);
         self.api_sender.send(msg).unwrap();
         rx.recv().unwrap()
     }
 
-    pub fn get_scroll_layer_state(&self) -> Vec<ScrollLayerState> {
+    pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
         let (tx, rx) = channel::msg_channel().unwrap();
-        let msg = ApiMsg::GetScrollLayerState(tx);
+        let msg = ApiMsg::GetScrollNodeState(tx);
         self.api_sender.send(msg).unwrap();
         rx.recv().unwrap()
     }
 
     pub fn request_webgl_context(&self, size: &DeviceIntSize, attributes: GLContextAttributes)
                                  -> Result<(WebGLContextId, GLLimits), String> {
         let (tx, rx) = channel::msg_channel().unwrap();
         let msg = ApiMsg::RequestWebGLContext(*size, attributes, tx);
@@ -452,17 +452,17 @@ pub enum ScrollEventPhase {
     /// down, if a touchpad is in use. (If false, the event is a touchpad fling.)
     Move(bool),
     /// The user ended scrolling.
     End,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct ScrollLayerState {
-    pub id: ScrollLayerId,
+    pub id: ClipId,
     pub scroll_offset: LayoutPoint,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub enum ScrollLocation {
     /// Scroll by a certain amount.
     Delta(LayoutPoint),
     /// Scroll to very top of element.
--- a/gfx/webrender_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -9,17 +9,17 @@ use {ColorF, FontKey, ImageKey, Pipeline
 use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
 use {PropertyBinding};
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct DisplayItem {
     pub item: SpecificDisplayItem,
     pub rect: LayoutRect,
     pub clip: ClipRegion,
-    pub scroll_layer_id: ScrollLayerId,
+    pub clip_id: ClipId,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum SpecificDisplayItem {
     Clip(ClipDisplayItem),
     Rectangle(RectangleDisplayItem),
     Text(TextDisplayItem),
     Image(ImageDisplayItem),
@@ -38,18 +38,18 @@ pub enum SpecificDisplayItem {
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange {
     pub start: usize,
     pub length: usize,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ClipDisplayItem {
-    pub id: ScrollLayerId,
-    pub parent_id: ScrollLayerId,
+    pub id: ClipId,
+    pub parent_id: ClipId,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
@@ -498,49 +498,49 @@ impl ComplexClipRegion {
         ComplexClipRegion {
             rect: rect,
             radii: radii,
         }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
-pub enum ScrollLayerId {
+pub enum ClipId {
     Clip(u64, PipelineId),
     ClipExternalId(u64, PipelineId),
     ReferenceFrame(u64, PipelineId),
 }
 
-impl ScrollLayerId {
-    pub fn root_scroll_layer(pipeline_id: PipelineId) -> ScrollLayerId {
-        ScrollLayerId::Clip(0, pipeline_id)
+impl ClipId {
+    pub fn root_scroll_node(pipeline_id: PipelineId) -> ClipId {
+        ClipId::Clip(0, pipeline_id)
     }
 
-    pub fn root_reference_frame(pipeline_id: PipelineId) -> ScrollLayerId {
-        ScrollLayerId::ReferenceFrame(0, pipeline_id)
+    pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
+        ClipId::ReferenceFrame(0, pipeline_id)
     }
 
-    pub fn new(id: u64, pipeline_id: PipelineId) -> ScrollLayerId {
-        ScrollLayerId::ClipExternalId(id, pipeline_id)
+    pub fn new(id: u64, pipeline_id: PipelineId) -> ClipId {
+        ClipId::ClipExternalId(id, pipeline_id)
     }
 
     pub fn pipeline_id(&self) -> PipelineId {
         match *self {
-            ScrollLayerId::Clip(_, pipeline_id) |
-            ScrollLayerId::ClipExternalId(_, pipeline_id) |
-            ScrollLayerId::ReferenceFrame(_, pipeline_id) => pipeline_id,
+            ClipId::Clip(_, pipeline_id) |
+            ClipId::ClipExternalId(_, pipeline_id) |
+            ClipId::ReferenceFrame(_, pipeline_id) => pipeline_id,
         }
     }
 
     pub fn is_reference_frame(&self) -> bool {
         match *self {
-            ScrollLayerId::ReferenceFrame(..) => true,
+            ClipId::ReferenceFrame(..) => true,
             _ => false,
         }
     }
 
     pub fn external_id(&self) -> Option<u64> {
         match *self {
-            ScrollLayerId::ClipExternalId(id, _) => Some(id),
+            ClipId::ClipExternalId(id, _) => Some(id),
             _ => None,
         }
     }
 }
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -1,24 +1,23 @@
 /* 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 app_units::Au;
 use std::mem;
 use std::slice;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
-use {ClipDisplayItem, ClipRegion, ColorF, ComplexClipRegion, DisplayItem, ExtendMode, FilterOp};
-use {FontKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem, GradientStop};
+use {ClipDisplayItem, ClipId, ClipRegion, ColorF, ComplexClipRegion, DisplayItem, ExtendMode};
+use {FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem, GradientStop};
 use {IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering, ItemRange};
-use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
-use {TransformStyle, MixBlendMode, PipelineId};
+use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, MixBlendMode, PipelineId};
 use {PropertyBinding, PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
-use {RectangleDisplayItem, ScrollLayerId, ScrollPolicy, SpecificDisplayItem, StackingContext};
-use {TextDisplayItem, WebGLContextId, WebGLDisplayItem, YuvColorSpace, YuvImageDisplayItem};
+use {RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem, StackingContext, TextDisplayItem};
+use {TransformStyle, WebGLContextId, WebGLDisplayItem, YuvColorSpace, YuvImageDisplayItem};
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AuxiliaryLists {
     /// The concatenation of: gradient stops, complex clip regions, filters, and glyph instances,
     /// in that order.
     data: Vec<u8>,
     descriptor: AuxiliaryListsDescriptor,
 }
@@ -77,71 +76,71 @@ impl BuiltDisplayList {
     }
 
     pub fn descriptor(&self) -> &BuiltDisplayListDescriptor {
         &self.descriptor
     }
 
     pub fn all_display_items(&self) -> &[DisplayItem] {
         unsafe {
-            convert_blob_to_pod(&self.data[0..self.descriptor.display_list_items_size])
+            convert_blob_to_pod(&self.data)
         }
     }
 
     pub fn into_display_items(self) -> Vec<DisplayItem> {
         unsafe {
             convert_vec_blob_to_pod(self.data)
         }
     }
 
 }
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub list: Vec<DisplayItem>,
     auxiliary_lists_builder: AuxiliaryListsBuilder,
     pub pipeline_id: PipelineId,
-    clip_stack: Vec<ScrollLayerId>,
-    next_scroll_layer_id: u64,
+    clip_stack: Vec<ClipId>,
+    next_clip_id: u64,
 }
 
 impl DisplayListBuilder {
     pub fn new(pipeline_id: PipelineId) -> DisplayListBuilder {
         DisplayListBuilder {
             list: Vec::new(),
             auxiliary_lists_builder: AuxiliaryListsBuilder::new(),
             pipeline_id: pipeline_id,
-            clip_stack: vec![ScrollLayerId::root_scroll_layer(pipeline_id)],
+            clip_stack: vec![ClipId::root_scroll_node(pipeline_id)],
 
             // We start at 1 here, because the root scroll id is always 0.
-            next_scroll_layer_id: 1,
+            next_clip_id: 1,
         }
     }
 
     pub fn print_display_list(&mut self) {
         for item in &self.list {
             println!("{:?}", item);
         }
     }
 
     fn push_item(&mut self, item: SpecificDisplayItem, rect: LayoutRect, clip: ClipRegion) {
         self.list.push(DisplayItem {
             item: item,
             rect: rect,
             clip: clip,
-            scroll_layer_id: *self.clip_stack.last().unwrap(),
+            clip_id: *self.clip_stack.last().unwrap(),
         });
     }
 
     fn push_new_empty_item(&mut self, item: SpecificDisplayItem) {
         self.list.push(DisplayItem {
             item: item,
             rect: LayoutRect::zero(),
             clip: ClipRegion::simple(&LayoutRect::zero()),
-            scroll_layer_id: *self.clip_stack.last().unwrap(),
+            clip_id: *self.clip_stack.last().unwrap(),
         });
     }
 
     pub fn push_rect(&mut self,
                      rect: LayoutRect,
                      clip: ClipRegion,
                      color: ColorF) {
         let item = SpecificDisplayItem::Rectangle(RectangleDisplayItem {
@@ -192,17 +191,17 @@ impl DisplayListBuilder {
             context_id: context_id,
         });
         self.push_item(item, rect, clip);
     }
 
     pub fn push_text(&mut self,
                      rect: LayoutRect,
                      clip: ClipRegion,
-                     glyphs: Vec<GlyphInstance>,
+                     glyphs: &[GlyphInstance],
                      font_key: FontKey,
                      color: ColorF,
                      size: Au,
                      blur_radius: Au,
                      glyph_options: Option<GlyphOptions>) {
         // Sanity check - anything with glyphs bigger than this
         // is probably going to consume too much memory to render
         // efficiently anyway. This is specifically to work around
@@ -448,53 +447,53 @@ impl DisplayListBuilder {
 
     pub fn pop_stacking_context(&mut self) {
         self.push_new_empty_item(SpecificDisplayItem::PopStackingContext);
     }
 
     pub fn define_clip(&mut self,
                        content_rect: LayoutRect,
                        clip: ClipRegion,
-                       id: Option<ScrollLayerId>)
-                       -> ScrollLayerId {
+                       id: Option<ClipId>)
+                       -> ClipId {
         let id = match id {
             Some(id) => id,
             None => {
-                self.next_scroll_layer_id += 1;
-                ScrollLayerId::Clip(self.next_scroll_layer_id - 1, self.pipeline_id)
+                self.next_clip_id += 1;
+                ClipId::Clip(self.next_clip_id - 1, self.pipeline_id)
             }
         };
 
         let item = SpecificDisplayItem::Clip(ClipDisplayItem {
             id: id,
             parent_id: *self.clip_stack.last().unwrap(),
         });
 
         self.push_item(item, content_rect, clip);
         id
     }
 
-    pub fn push_scroll_layer(&mut self,
-                             clip: ClipRegion,
-                             content_rect: LayoutRect,
-                             id: Option<ScrollLayerId>) {
+    pub fn push_clip_node(&mut self,
+                          clip: ClipRegion,
+                          content_rect: LayoutRect,
+                          id: Option<ClipId>) {
         let id = self.define_clip(content_rect, clip, id);
         self.clip_stack.push(id);
     }
 
-    pub fn push_clip_id(&mut self, id: ScrollLayerId) {
+    pub fn push_clip_id(&mut self, id: ClipId) {
         self.clip_stack.push(id);
     }
 
     pub fn pop_clip_id(&mut self) {
         self.clip_stack.pop();
         assert!(self.clip_stack.len() > 0);
     }
 
-    pub fn pop_scroll_layer(&mut self) {
+    pub fn pop_clip_node(&mut self) {
         self.pop_clip_id();
     }
 
     pub fn push_iframe(&mut self, rect: LayoutRect, clip: ClipRegion, pipeline_id: PipelineId) {
         let item = SpecificDisplayItem::Iframe(IframeDisplayItem { pipeline_id: pipeline_id });
         self.push_item(item, rect, clip);
     }
 
@@ -523,17 +522,17 @@ impl DisplayListBuilder {
                 }
                 Iframe(_) | Clip(_) => {
                     // We don't support relocating these
                     panic!();
                 }
                 _ => {}
             }
             i.clip.complex = self.auxiliary_lists_builder.add_complex_clip_regions(aux.complex_clip_regions(&i.clip.complex));
-            i.scroll_layer_id = *self.clip_stack.last().unwrap();
+            i.clip_id = *self.clip_stack.last().unwrap();
             self.list.push(i);
         }
     }
 
     pub fn new_clip_region(&mut self,
                            rect: &LayoutRect,
                            complex: Vec<ComplexClipRegion>,
                            image_mask: Option<ImageMask>)
--- a/gfx/webrender_traits/src/image.rs
+++ b/gfx/webrender_traits/src/image.rs
@@ -13,36 +13,39 @@ impl ImageKey {
     pub fn new(key0: u32, key1: u32) -> ImageKey {
         ImageKey(key0, key1)
     }
 }
 
 /// An arbitrary identifier for an external image provided by the
 /// application. It must be a unique identifier for each external
 /// image.
+#[repr(C)]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct ExternalImageId(pub u64);
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ImageFormat {
     Invalid  = 0,
     A8       = 1,
     RGB8     = 2,
     RGBA8    = 3,
     RGBAF32  = 4,
+    RG8      = 5,
 }
 
 impl ImageFormat {
     pub fn bytes_per_pixel(self) -> Option<u32> {
         match self {
             ImageFormat::A8 => Some(1),
             ImageFormat::RGB8 => Some(3),
             ImageFormat::RGBA8 => Some(4),
             ImageFormat::RGBAF32 => Some(16),
+            ImageFormat::RG8 => Some(2),
             ImageFormat::Invalid => None,
         }
     }
 }
 
 #[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDescriptor {
     pub format: ImageFormat,