Bug 1353484 - Update webrender to 3dd68f54e12bd5abf8ef41de4d4ec851620f7e4e. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 10 Apr 2017 19:11:29 -0400
changeset 560019 ef7ba682ef43bcd585969cd305491ae529be00ea
parent 560018 9c83023e46b0f108d068a77590b257db80cc0a1f
child 560020 868a2e38ff27e812fba78e76cdf3f1f5e72a1dda
push id53290
push userkgupta@mozilla.com
push dateMon, 10 Apr 2017 23:17:06 +0000
reviewersjrmuizel
bugs1353484
milestone55.0a1
Bug 1353484 - Update webrender to 3dd68f54e12bd5abf8ef41de4d4ec851620f7e4e. r?jrmuizel MozReview-Commit-ID: 8pSMcZVLZmD
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_angle_gradient.fs.glsl
gfx/webrender/res/ps_angle_gradient.glsl
gfx/webrender/res/ps_angle_gradient.vs.glsl
gfx/webrender/res/ps_border.fs.glsl
gfx/webrender/res/ps_gradient.fs.glsl
gfx/webrender/res/ps_gradient.vs.glsl
gfx/webrender/res/ps_radial_gradient.fs.glsl
gfx/webrender/res/ps_radial_gradient.glsl
gfx/webrender/res/ps_radial_gradient.vs.glsl
gfx/webrender/src/CLIPPING.md
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/platform/macos/font.rs
gfx/webrender/src/platform/windows/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/record.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/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/channel.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/font.rs
gfx/webrender_traits/src/lib.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: 7463ae5908ca1d4065763a8753af5d72a6f78b85
+Latest Commit: 3dd68f54e12bd5abf8ef41de4d4ec851620f7e4e
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.30.0"
+version = "0.31.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"]
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -226,26 +226,28 @@ ClipArea fetch_clip_area(int index) {
         area.inner_rect = task.data2;
     }
 
     return area;
 }
 
 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_2(index);
+    ivec2 uv = get_fetch_uv_4(index);
 
-    gradient.start_end_point = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
-    gradient.extend_mode = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
+    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;
 }
 
 struct GradientStop {
     vec4 color;
     vec4 offset;
 };
@@ -259,25 +261,27 @@ GradientStop fetch_gradient_stop(int ind
     stop.offset = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
 
     return stop;
 }
 
 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;
 
-    ivec2 uv = get_fetch_uv_2(index);
+    ivec2 uv = get_fetch_uv_4(index);
 
-    gradient.start_end_center = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
-    gradient.start_end_radius_ratio_xy_extend_mode = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
+    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));
 
     return gradient;
 }
 
 struct Glyph {
     vec4 offset;
 };
 
@@ -758,13 +762,14 @@ float do_clip() {
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 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_factor = 4.0 / 255.0;
-    float noise = texelFetch(sDither, pos, 0).r * noise_factor;
+    float noise_normalized = (texelFetch(sDither, pos, 0).r * 255.0 + 0.5) / 64.0;
+    float noise = (noise_normalized - 0.5) / 256.0; // scale down to the unit length
+
     return color + vec4(noise, noise, noise, 0);
 }
 #endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/ps_angle_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.fs.glsl
@@ -1,20 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 uniform sampler2D sGradients;
 
 void main(void) {
+    vec2 pos = mod(vPos, vTileRepeat);
+
+    if (pos.x >= vTileSize.x ||
+        pos.y >= vTileSize.y) {
+        discard;
+    }
+
+    // Normalized offset of this vertex within the gradient, before clamp/repeat.
+    float offset = dot(pos - vStartPoint, vScaledDir);
+
     vec2 texture_size = vec2(textureSize(sGradients, 0));
 
     // Either saturate or modulo the offset depending on repeat mode, then scale to number of
     // gradient color entries (texture width / 2).
-    float x = mix(clamp(vOffset, 0.0, 1.0), fract(vOffset), vGradientRepeat) * 0.5 * texture_size.x;
+    float x = mix(clamp(offset, 0.0, 1.0), fract(offset), vGradientRepeat) * 0.5 * texture_size.x;
 
     x = 2.0 * floor(x) + 0.5 + fract(x);
 
     // Use linear filtering to mix in the low bits (vGradientIndex + 1) with the high
     // bits (vGradientIndex)
     float y = vGradientIndex * 2.0 + 0.5 + 1.0 / 256.0;
     oFragColor = dither(texture(sGradients, vec2(x, y) / texture_size));
 }
--- a/gfx/webrender/res/ps_angle_gradient.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.glsl
@@ -1,7 +1,11 @@
 /* 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 vec2 vScaledDir;
+flat varying vec2 vStartPoint;
 flat varying float vGradientIndex;
 flat varying float vGradientRepeat;
-varying float vOffset;
+flat varying vec2 vTileSize;
+flat varying vec2 vTileRepeat;
+varying vec2 vPos;
--- a/gfx/webrender/res/ps_angle_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.vs.glsl
@@ -8,27 +8,26 @@ void main(void) {
     Gradient gradient = fetch_gradient(prim.prim_index);
 
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task);
 
-    // Snap the start/end points to device pixel units.
-    // I'm not sure this is entirely correct, but the
-    // old render path does this, and it is needed to
-    // make the angle gradient ref tests pass. It might
-    // be better to fix this higher up in DL construction
-    // and not snap here?
-    vec2 start_point = floor(0.5 + gradient.start_end_point.xy * uDevicePixelRatio) / uDevicePixelRatio;
-    vec2 end_point = floor(0.5 + gradient.start_end_point.zw * uDevicePixelRatio) / uDevicePixelRatio;
+    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;
 
-    vec2 dir = end_point - start_point;
-    // Normalized offset of this vertex within the gradient, before clamp/repeat.
-    vOffset = dot(vi.local_pos - start_point, dir) / dot(dir, dir);
+    vStartPoint = start_point;
+    vScaledDir = dir / dot(dir, dir);
+
+    vTileSize = gradient.tile_size_repeat.xy;
+    vTileRepeat = gradient.tile_size_repeat.zw;
 
     // V coordinate of gradient row in lookup texture.
     vGradientIndex = float(prim.sub_index);
 
     // Whether to repeat the gradient instead of clamping.
     vGradientRepeat = float(int(gradient.extend_mode.x) == EXTEND_MODE_REPEAT);
 }
--- a/gfx/webrender/res/ps_border.fs.glsl
+++ b/gfx/webrender/res/ps_border.fs.glsl
@@ -343,16 +343,17 @@ void draw_solid_border(float distanceFro
         oFragColor *= vec4(1.0, 1.0, 1.0, alpha);
       }
 
       break;
     }
     default:
       oFragColor = vHorizontalColor;
       discard_pixels_in_rounded_borders(localPos);
+      break;
   }
 }
 
 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);
 }
 
@@ -422,16 +423,17 @@ void main(void) {
                                  (local_pos.y - vLocalRect.y) -
                                  0.5 * (vLocalRect.z + vLocalRect.w);
 #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:
@@ -454,17 +456,23 @@ void main(void) {
           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:
-          discard;
         default:
-          discard;
+          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
 }
--- a/gfx/webrender/res/ps_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_gradient.fs.glsl
@@ -7,14 +7,10 @@ void main(void) {
     float alpha = 0.0;
     vec2 local_pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
 #else
     float alpha = 1.0;
     vec2 local_pos = vPos;
 #endif
 
     alpha = min(alpha, do_clip());
-
-    // TODO(gw): Re-enable the gradient dither once we get the
-    //           reftests passing.
-    //oFragColor = dither(vColor * vec4(1.0, 1.0, 1.0, alpha));
-    oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
+    oFragColor = dither(vColor * vec4(1.0, 1.0, 1.0, alpha));
 }
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_gradient.vs.glsl
@@ -2,26 +2,28 @@
 /* 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();
     Gradient gradient = fetch_gradient(prim.prim_index);
 
+    vec4 abs_start_end_point = gradient.start_end_point + prim.local_rect.p0.xyxy;
+
     GradientStop g0 = fetch_gradient_stop(prim.sub_index + 0);
     GradientStop g1 = fetch_gradient_stop(prim.sub_index + 1);
 
     RectWithSize segment_rect;
     vec2 axis;
     vec4 adjusted_color_g0 = g0.color;
     vec4 adjusted_color_g1 = g1.color;
-    if (gradient.start_end_point.y == gradient.start_end_point.w) {
+    if (abs_start_end_point.y == abs_start_end_point.w) {
         // Calculate the x coord of the gradient stops
-        vec2 g01_x = mix(gradient.start_end_point.xx, gradient.start_end_point.zz,
+        vec2 g01_x = mix(abs_start_end_point.xx, abs_start_end_point.zz,
                          vec2(g0.offset.x, g1.offset.x));
 
         // The gradient stops might exceed the geometry rect so clamp them
         vec2 g01_x_clamped = clamp(g01_x,
                                    prim.local_rect.p0.xx,
                                    prim.local_rect.p0.xx + prim.local_rect.size.xx);
 
         // Calculate the segment rect using the clamped coords
@@ -30,17 +32,17 @@ void main(void) {
         axis = vec2(1.0, 0.0);
 
         // Adjust the stop colors by how much they were clamped
         vec2 adjusted_offset = (g01_x_clamped - g01_x.xx) / (g01_x.y - g01_x.x);
         adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
         adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
     } else {
         // Calculate the y coord of the gradient stops
-        vec2 g01_y = mix(gradient.start_end_point.yy, gradient.start_end_point.ww,
+        vec2 g01_y = mix(abs_start_end_point.yy, abs_start_end_point.ww,
                          vec2(g0.offset.x, g1.offset.x));
 
         // The gradient stops might exceed the geometry rect so clamp them
         vec2 g01_y_clamped = clamp(g01_y,
                                    prim.local_rect.p0.yy,
                                    prim.local_rect.p0.yy + prim.local_rect.size.yy);
 
         // Calculate the segment rect using the clamped coords
--- a/gfx/webrender/res/ps_radial_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.fs.glsl
@@ -1,17 +1,24 @@
 /* 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/. */
 
 uniform sampler2D sGradients;
 
 void main(void) {
+    vec2 pos = mod(vPos, vTileRepeat);
+
+    if (pos.x >= vTileSize.x ||
+        pos.y >= vTileSize.y) {
+        discard;
+    }
+
     vec2 cd = vEndCenter - vStartCenter;
-    vec2 pd = vPos - vStartCenter;
+    vec2 pd = pos - vStartCenter;
     float rd = vEndRadius - vStartRadius;
 
     // Solve for t in length(t * cd - pd) = vStartRadius + t * rd
     // using a quadratic equation in form of At^2 - 2Bt + C = 0
     float A = dot(cd, cd) - rd * rd;
     float B = dot(pd, cd) + vStartRadius * rd;
     float C = dot(pd, pd) - vStartRadius * vStartRadius;
 
--- a/gfx/webrender/res/ps_radial_gradient.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.glsl
@@ -3,9 +3,11 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 flat varying float vGradientIndex;
 flat varying float vGradientRepeat;
 flat varying vec2 vStartCenter;
 flat varying vec2 vEndCenter;
 flat varying float vStartRadius;
 flat varying float vEndRadius;
+flat varying vec2 vTileSize;
+flat varying vec2 vTileRepeat;
 varying vec2 vPos;
--- a/gfx/webrender/res/ps_radial_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.vs.glsl
@@ -8,34 +8,34 @@ void main(void) {
     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);
 
-    vPos = vi.local_pos;
+    vPos = vi.local_pos - prim.local_rect.p0;
 
-    // Snap the start/end points to device pixel units.
-    // I'm not sure this is entirely correct, but the
-    // old render path does this, and it is needed to
-    // make the angle gradient ref tests pass. It might
-    // be better to fix this higher up in DL construction
-    // and not snap here?
-    vStartCenter = floor(0.5 + gradient.start_end_center.xy * uDevicePixelRatio) / uDevicePixelRatio;
-    vEndCenter = floor(0.5 + gradient.start_end_center.zw * uDevicePixelRatio) / uDevicePixelRatio;
+    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;
 
+    vTileSize = gradient.tile_size_repeat.xy;
+    vTileRepeat = gradient.tile_size_repeat.zw;
+
     // Transform all coordinates by the y scale so the
     // fragment shader can work with circles
     float ratio_xy = gradient.start_end_radius_ratio_xy_extend_mode.z;
     vPos.y *= ratio_xy;
     vStartCenter.y *= ratio_xy;
     vEndCenter.y *= ratio_xy;
+    vTileSize.y *= ratio_xy;
+    vTileRepeat.y *= ratio_xy;
 
     // V coordinate of gradient row in lookup texture.
     vGradientIndex = float(prim.sub_index);
 
     // Whether to repeat the gradient instead of clamping.
     vGradientRepeat = float(int(gradient.start_end_radius_ratio_xy_extend_mode.w) == EXTEND_MODE_REPEAT);
 }
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/CLIPPING.md
@@ -0,0 +1,98 @@
+# 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))
+
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/border.rs
@@ -0,0 +1,202 @@
+/* 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 tiling::PrimitiveFlags;
+use webrender_traits::{BorderSide, BorderStyle, BorderWidths, NormalBorder};
+use webrender_traits::{ClipRegion, LayerPoint, LayerRect, LayerSize, ScrollLayerId};
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum BorderCornerKind {
+    None,
+    Solid,
+    Complex,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum BorderEdgeKind {
+    None,
+    Solid,
+    Complex,
+}
+
+pub trait NormalBorderHelpers {
+    fn get_corner(&self,
+                  edge0: &BorderSide,
+                  width0: f32,
+                  edge1: &BorderSide,
+                  width1: f32,
+                  radius: &LayerSize) -> BorderCornerKind;
+
+    fn get_edge(&self,
+                edge: &BorderSide,
+                width: f32) -> (BorderEdgeKind, f32);
+}
+
+impl NormalBorderHelpers for NormalBorder {
+    fn get_corner(&self,
+                  edge0: &BorderSide,
+                  width0: f32,
+                  edge1: &BorderSide,
+                  width1: f32,
+                  radius: &LayerSize) -> BorderCornerKind {
+        // If either width is zero, a corner isn't formed.
+        if width0 == 0.0 || width1 == 0.0 {
+            return BorderCornerKind::None;
+        }
+
+        // If both edges are transparent, no corner is formed.
+        if edge0.color.a == 0.0 && edge1.color.a == 0.0 {
+            return BorderCornerKind::None;
+        }
+
+        match (edge0.style, edge1.style) {
+            // If either edge is none or hidden, no corner is needed.
+            (BorderStyle::None, _) | (_, BorderStyle::None) => BorderCornerKind::None,
+            (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
+                }
+            }
+
+            // 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,
+        }
+    }
+
+    fn get_edge(&self,
+                edge: &BorderSide,
+                width: f32) -> (BorderEdgeKind, f32) {
+        if width == 0.0 {
+            return (BorderEdgeKind::None, 0.0);
+        }
+
+        match edge.style {
+            BorderStyle::None |
+            BorderStyle::Hidden => (BorderEdgeKind::None, 0.0),
+
+            BorderStyle::Solid |
+            BorderStyle::Inset |
+            BorderStyle::Outset => (BorderEdgeKind::Solid, width),
+
+            BorderStyle::Double |
+            BorderStyle::Dotted |
+            BorderStyle::Dashed |
+            BorderStyle::Groove |
+            BorderStyle::Ridge => (BorderEdgeKind::Complex, width),
+        }
+    }
+}
+
+impl FrameBuilder {
+    // 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,
+                             rect: &LayerRect,
+                             border: &NormalBorder,
+                             widths: &BorderWidths,
+                             scroll_layer_id: ScrollLayerId,
+                             clip_region: &ClipRegion) -> bool {
+        // 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);
+
+        if tl == BorderCornerKind::Complex ||
+           tr == BorderCornerKind::Complex ||
+           br == BorderCornerKind::Complex ||
+           bl == BorderCornerKind::Complex {
+            return false;
+        }
+
+        // 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 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);
+        }
+        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
+    }
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -113,25 +113,28 @@ pub struct ClipScrollNode {
 
     /// Whether or not this node is a reference frame.
     pub node_type: NodeType,
 }
 
 impl ClipScrollNode {
     pub fn new(pipeline_id: PipelineId,
                parent_id: ScrollLayerId,
-               local_viewport_rect: &LayerRect,
-               content_size: LayerSize,
+               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 {
             scrolling: ScrollingState::new(),
-            content_size: content_size,
-            local_viewport_rect: *local_viewport_rect,
-            local_clip_rect: *local_viewport_rect,
+            content_size: content_rect.size,
+            local_viewport_rect: local_viewport_rect,
+            local_clip_rect: local_viewport_rect,
             combined_local_viewport_rect: LayerRect::zero(),
             world_viewport_transform: LayerToWorldTransform::identity(),
             world_content_transform: LayerToWorldTransform::identity(),
             parent: Some(parent_id),
             children: Vec::new(),
             pipeline_id: pipeline_id,
             node_type: NodeType::Clip(clip_info),
         }
@@ -208,17 +211,17 @@ impl ClipScrollNode {
                                          (-origin.y).max(-scrollable_height).min(0.0).round());
         if new_offset == self.scrolling.offset {
             return false;
         }
 
         self.scrolling.offset = new_offset;
         self.scrolling.bouncing_back = false;
         self.scrolling.started_bouncing_back = false;
-        return true;
+        true
     }
 
     pub fn update_transform(&mut self,
                             parent_reference_frame_transform: &LayerToWorldTransform,
                             parent_combined_viewport_rect: &ScrollLayerRect,
                             parent_accumulated_scroll_offset: LayerPoint) {
 
         let local_transform = match self.node_type {
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -287,24 +287,24 @@ impl ClipScrollTree {
             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) {
+            let scrolling_state = match old_states.get(scroll_layer_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(scroll_layer_id) {
                 node.set_scroll_origin(&pending_offset);
             }
         }
 
     }
 
     pub fn add_reference_frame(&mut self,
                                rect: &LayerRect,
@@ -313,19 +313,19 @@ impl ClipScrollTree {
                                parent_id: Option<ScrollLayerId>)
                                -> ScrollLayerId {
 
         let reference_frame_id =
             ScrollLayerId::ReferenceFrame(self.current_reference_frame_id, pipeline_id);
         self.current_reference_frame_id += 1;
 
         let node = ClipScrollNode::new_reference_frame(parent_id,
-                                                       &rect,
+                                                       rect,
                                                        rect.size,
-                                                       &transform,
+                                                       transform,
                                                        pipeline_id);
         self.add_node(node, reference_frame_id);
         reference_frame_id
     }
 
     pub fn add_node(&mut self, node: ClipScrollNode, id: ScrollLayerId) {
         // When the parent node is None this means we are adding the root.
         match node.parent {
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -671,20 +671,20 @@ pub struct GpuProfiler<T> {
     next_frame: usize,
 }
 
 impl<T> GpuProfiler<T> {
     pub fn new(gl: &Rc<gl::Gl>) -> GpuProfiler<T> {
         GpuProfiler {
             next_frame: 0,
             frames: [
-                      GpuFrameProfile::new(gl.clone()),
-                      GpuFrameProfile::new(gl.clone()),
-                      GpuFrameProfile::new(gl.clone()),
-                      GpuFrameProfile::new(gl.clone()),
+                      GpuFrameProfile::new(Rc::clone(gl)),
+                      GpuFrameProfile::new(Rc::clone(gl)),
+                      GpuFrameProfile::new(Rc::clone(gl)),
+                      GpuFrameProfile::new(Rc::clone(gl)),
                     ],
         }
     }
 
     pub fn build_samples(&mut self) -> Option<(FrameId, Vec<GpuSample<T>>)> {
         let frame = &mut self.frames[self.next_frame];
         if frame.is_valid() {
             Some((frame.frame_id, frame.build_samples()))
@@ -716,22 +716,22 @@ pub struct GpuMarker{
 }
 
 impl GpuMarker {
     pub fn new(gl: &Rc<gl::Gl>, message: &str) -> GpuMarker {
         match gl.get_type() {
             gl::GlType::Gl =>  {
                 gl.push_group_marker_ext(message);
                 GpuMarker{
-                    gl: gl.clone(),
+                    gl: Rc::clone(gl),
                 }
             }
             gl::GlType::Gles => {
                 GpuMarker{
-                    gl: gl.clone(),
+                    gl: Rc::clone(gl),
                 }
             }
         }
     }
 
     pub fn fire(gl: &gl::Gl, message: &str) {
         match gl.get_type() {
             gl::GlType::Gl =>  {
@@ -968,17 +968,17 @@ impl Device {
                           shader_type: gl::GLenum,
                           shader_preamble: &[String])
                           -> Result<gl::GLuint, ShaderError> {
         debug!("compile {:?}", name);
 
         let mut s = String::new();
         s.push_str(get_shader_version(gl));
         for prefix in shader_preamble {
-            s.push_str(&prefix);
+            s.push_str(prefix);
         }
         s.push_str(source_str);
 
         let id = gl.create_shader(shader_type);
         let mut source = Vec::new();
         source.extend_from_slice(s.as_bytes());
         gl.shader_source(id, &[&source[..]]);
         gl.compile_shader(id);
@@ -1103,17 +1103,17 @@ impl Device {
 
         for id in id_list {
             let texture_id = TextureId {
                 name: id,
                 target: target.to_gl_target(),
             };
 
             let texture = Texture {
-                gl: self.gl.clone(),
+                gl: Rc::clone(&self.gl),
                 id: id,
                 width: 0,
                 height: 0,
                 format: ImageFormat::Invalid,
                 filter: TextureFilter::Nearest,
                 mode: RenderTargetMode::None,
                 fbo_ids: vec![],
             };
@@ -1446,17 +1446,17 @@ impl Device {
             include.push_str(&src);
         }
 
         if let Some(shared_src) = get_optional_shader_source(base_filename, &self.resource_override_path) {
             include.push_str(&shared_src);
         }
 
         let program = Program {
-            gl: self.gl.clone(),
+            gl: Rc::clone(&self.gl),
             name: base_filename.to_owned(),
             id: pid,
             u_transform: -1,
             u_device_pixel_ratio: -1,
             vs_source: get_shader_source(&vs_name, &self.resource_override_path),
             fs_source: get_shader_source(&fs_name, &self.resource_override_path),
             prefix: prefix,
             vs_id: None,
@@ -1790,17 +1790,17 @@ impl Device {
         let vao_id = vao_ids[0];
 
         self.gl.bind_vertex_array(vao_id);
 
         format.bind(self.gl(), main_vbo_id, instance_vbo_id, vertex_offset, instance_stride);
         ibo_id.bind(self.gl()); // force it to be a part of VAO
 
         let vao = VAO {
-            gl: self.gl.clone(),
+            gl: Rc::clone(&self.gl),
             id: vao_id,
             ibo_id: ibo_id,
             main_vbo_id: main_vbo_id,
             instance_vbo_id: instance_vbo_id,
             instance_stride: instance_stride,
             owns_indices: owns_indices,
             owns_vertices: owns_vertices,
             owns_instances: owns_instances,
@@ -1846,44 +1846,44 @@ impl Device {
                                        vertices: &[V],
                                        usage_hint: VertexUsageHint) {
         debug_assert!(self.inside_frame);
 
         let vao = self.vaos.get(&vao_id).unwrap();
         debug_assert_eq!(self.bound_vao, vao_id);
 
         vao.main_vbo_id.bind(self.gl());
-        gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, &vertices, usage_hint.to_gl());
+        gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, vertices, usage_hint.to_gl());
     }
 
     pub fn update_vao_instances<V>(&mut self,
                                    vao_id: VAOId,
                                    instances: &[V],
                                    usage_hint: VertexUsageHint) {
         debug_assert!(self.inside_frame);
 
         let vao = self.vaos.get(&vao_id).unwrap();
         debug_assert_eq!(self.bound_vao, vao_id);
         debug_assert_eq!(vao.instance_stride as usize, mem::size_of::<V>());
 
         vao.instance_vbo_id.bind(self.gl());
-        gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, &instances, usage_hint.to_gl());
+        gl::buffer_data(self.gl(), gl::ARRAY_BUFFER, instances, usage_hint.to_gl());
     }
 
     pub fn update_vao_indices<I>(&mut self,
                                  vao_id: VAOId,
                                  indices: &[I],
                                  usage_hint: VertexUsageHint) {
         debug_assert!(self.inside_frame);
 
         let vao = self.vaos.get(&vao_id).unwrap();
         debug_assert_eq!(self.bound_vao, vao_id);
 
         vao.ibo_id.bind(self.gl());
-        gl::buffer_data(self.gl(), gl::ELEMENT_ARRAY_BUFFER, &indices, usage_hint.to_gl());
+        gl::buffer_data(self.gl(), gl::ELEMENT_ARRAY_BUFFER, indices, usage_hint.to_gl());
     }
 
     pub fn draw_triangles_u16(&mut self, first_vertex: i32, index_count: i32) {
         debug_assert!(self.inside_frame);
         self.gl.draw_elements(gl::TRIANGLES,
                                index_count,
                                gl::UNSIGNED_SHORT,
                                first_vertex as u32 * 2);
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -11,17 +11,17 @@ use internal_types::{RendererFrame};
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 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::subtract_rect;
+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::{ScrollLayerState, ScrollLocation, ScrollPolicy, SpecificDisplayItem};
 use webrender_traits::{StackingContext, TileOffset, WorldPoint};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
@@ -212,17 +212,17 @@ fn clip_intersection(original_rect: &Lay
                      -> Option<LayerRect> {
     if region.image_mask.is_some() {
         return None;
     }
     let clips = aux_lists.complex_clip_regions(&region.complex);
     let base_rect = region.main.intersection(original_rect);
     clips.iter().fold(base_rect, |inner_combined, ccr| {
         inner_combined.and_then(|combined| {
-            ccr.get_inner_rect().and_then(|ir| ir.intersection(&combined))
+            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()),
@@ -336,37 +336,35 @@ impl Frame {
 
             let mut traversal = DisplayListTraversal::new_skipping_first(display_list);
             self.flatten_stacking_context(&mut traversal,
                                           root_pipeline_id,
                                           &mut context,
                                           scroll_layer_id,
                                           LayerPoint::zero(),
                                           0,
-                                          &root_bounds,
-                                          &root_stacking_context);
+                                          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,
                         item: &ClipDisplayItem,
-                        reference_frame_relative_offset: LayerPoint,
+                        content_rect: &LayerRect,
                         clip: &ClipRegion) {
-        let clip_rect = clip.main.translate(&reference_frame_relative_offset);
         context.builder.add_clip_scroll_node(item.id,
                                              parent_id,
                                              pipeline_id,
-                                             &clip_rect,
-                                             &item.content_size,
+                                             &content_rect,
                                              clip,
                                              &mut self.clip_scroll_tree);
 
     }
 
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut DisplayListTraversal<'a>,
                                     pipeline_id: PipelineId,
@@ -434,28 +432,29 @@ impl Frame {
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y);
         }
 
         // TODO(gw): Int with overflow etc
         context.builder.push_stacking_context(&reference_frame_relative_offset,
                                               pipeline_id,
                                               level == 0,
-                                              composition_operations);
+                                              composition_operations,
+                                              stacking_context.transform_style);
 
         // 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,
-                                                        &bounds,
+                                                        bounds,
                                                         &ClipRegion::simple(&background_rect),
                                                         &bg_color,
                                                         PrimitiveFlags::None);
                 }
             }
         }
 
         self.flatten_items(traversal,
@@ -487,17 +486,16 @@ impl Frame {
     }
 
     fn flatten_iframe<'a>(&mut self,
                           pipeline_id: PipelineId,
                           parent_id: ScrollLayerId,
                           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,
         };
 
         let display_list = context.scene.display_lists.get(&pipeline_id);
         let display_list = match display_list {
             Some(display_list) => display_list,
@@ -528,30 +526,29 @@ impl Frame {
                                                  &transform,
                                                  &mut self.clip_scroll_tree);
 
         let iframe_scroll_layer_id = ScrollLayerId::root_scroll_layer(pipeline_id);
         context.builder.add_clip_scroll_node(
             iframe_scroll_layer_id,
             iframe_reference_frame_id,
             pipeline_id,
-            &LayerRect::new(LayerPoint::zero(), iframe_rect.size),
-            &iframe_stacking_context_bounds.size,
-            &ClipRegion::simple(&iframe_stacking_context_bounds),
+            &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,
                                       LayerPoint::zero(),
                                       0,
-                                      &iframe_stacking_context_bounds,
-                                      &iframe_stacking_context);
+                                      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,
@@ -611,17 +608,17 @@ impl Frame {
                                              text_info.glyphs,
                                              text_info.glyph_options);
                 }
                 SpecificDisplayItem::Rectangle(ref info) => {
                     let auxiliary_lists = self.pipeline_auxiliary_lists
                                               .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) {
+                    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,
                                                             &opaque_rect,
                                                             &ClipRegion::simple(&item.clip.main),
                                                             &info.color,
@@ -643,29 +640,33 @@ impl Frame {
                 }
                 SpecificDisplayItem::Gradient(ref info) => {
                     context.builder.add_gradient(scroll_layer_id,
                                                  item.rect,
                                                  &item.clip,
                                                  info.gradient.start_point,
                                                  info.gradient.end_point,
                                                  info.gradient.stops,
-                                                 info.gradient.extend_mode);
+                                                 info.gradient.extend_mode,
+                                                 info.tile_size,
+                                                 info.tile_spacing);
                 }
                 SpecificDisplayItem::RadialGradient(ref info) => {
                     context.builder.add_radial_gradient(scroll_layer_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.gradient.extend_mode,
+                                                        info.tile_size,
+                                                        info.tile_spacing);
                 }
                 SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
                     context.builder.add_box_shadow(scroll_layer_id,
                                                    &box_shadow_info.box_bounds,
                                                    &item.clip,
                                                    &box_shadow_info.offset,
                                                    &box_shadow_info.color,
                                                    box_shadow_info.blur_radius,
@@ -692,21 +693,22 @@ impl Frame {
                 SpecificDisplayItem::Iframe(ref info) => {
                     self.flatten_iframe(info.pipeline_id,
                                         scroll_layer_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,
                                       &info,
-                                      reference_frame_relative_offset,
+                                      &content_rect,
                                       &item.clip);
                 }
                 SpecificDisplayItem::PopStackingContext => return,
             }
         }
     }
 
     /// Decomposes an image display item that is repeated into an image per individual repetition.
@@ -739,17 +741,17 @@ impl Frame {
         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) {
+            ).intersection(item_rect) {
                 self.decompose_image_row(scroll_layer_id, context, &row_rect, item_clip, info, image_size, tile_size);
             }
         }
     }
 
     fn decompose_image_row(&mut self,
                            scroll_layer_id: ScrollLayerId,
                            context: &mut FlattenContext,
@@ -769,17 +771,17 @@ impl Frame {
         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) {
+            ).intersection(item_rect) {
                 self.decompose_tiled_image(scroll_layer_id, context, &decomposed_rect, item_clip, info, image_size, tile_size);
             }
         }
     }
 
     fn decompose_tiled_image(&mut self,
                              scroll_layer_id: ScrollLayerId,
                              context: &mut FlattenContext,
@@ -958,20 +960,20 @@ 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) {
+        if let Some(prim_rect) = prim_rect.intersection(item_rect) {
             context.builder.add_image(scroll_layer_id,
                                       prim_rect,
-                                      &item_clip,
+                                      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
@@ -21,24 +21,25 @@ 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, rect_from_points_f, subtract_rect};
-use util::{RectHelpers, TransformedRectKind};
-use webrender_traits::{BorderDetails, BorderDisplayItem, BorderSide, BorderStyle};
+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};
 
 #[derive(Debug, Clone)]
 struct ImageBorderSegment {
     geom_rect: LayerRect,
     sub_rect: TexelRect,
     stretch_size: LayerSize,
     tile_spacing: LayerSize,
 }
@@ -214,32 +215,34 @@ impl FrameBuilder {
 
         ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, scroll_layer_id)
     }
 
     pub fn push_stacking_context(&mut self,
                                  reference_frame_offset: &LayerPoint,
                                  pipeline_id: PipelineId,
                                  is_page_root: bool,
-                                 composite_ops: CompositeOps) {
+                                 composite_ops: CompositeOps,
+                                 transform_style: TransformStyle) {
         if let Some(parent_index) = self.stacking_context_stack.last() {
             let parent_is_root = self.stacking_context_store[parent_index.0].is_page_root;
 
             if composite_ops.mix_blend_mode.is_some() && !parent_is_root {
                 // the parent stacking context of a stacking context with mix-blend-mode
                 // must be drawn with a transparent background, unless the parent stacking context
                 // is the root of the page
                 self.stacking_context_store[parent_index.0].should_isolate = true;
             }
         }
 
         let stacking_context_index = StackingContextIndex(self.stacking_context_store.len());
         self.stacking_context_store.push(StackingContext::new(pipeline_id,
                                                               *reference_frame_offset,
                                                               is_page_root,
+                                                              transform_style,
                                                               composite_ops));
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
         self.stacking_context_stack.push(stacking_context_index);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
         self.stacking_context_stack.pop();
@@ -304,38 +307,36 @@ impl FrameBuilder {
         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,
                                    clip_scroll_tree.root_reference_frame_id,
                                    pipeline_id,
-                                   &viewport_rect,
-                                   content_size,
+                                   &LayerRect::new(LayerPoint::zero(), *content_size),
                                    &ClipRegion::simple(&viewport_rect),
                                    clip_scroll_tree);
         topmost_scroll_layer_id
     }
 
     pub fn add_clip_scroll_node(&mut self,
                                 new_node_id: ScrollLayerId,
                                 parent_id: ScrollLayerId,
                                 pipeline_id: PipelineId,
-                                local_viewport_rect: &LayerRect,
-                                content_size: &LayerSize,
+                                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,
                                        parent_id,
-                                       local_viewport_rect,
-                                       *content_size,
+                                       content_rect,
+                                       &clip_region.main,
                                        clip_info);
 
         clip_scroll_tree.add_node(node, new_node_id);
         self.packed_layers.push(PackedLayer::empty());
     }
 
     pub fn pop_reference_frame(&mut self) {
         self.reference_frame_stack.pop();
@@ -368,36 +369,16 @@ impl FrameBuilder {
                     prim_index: prim_index,
                     scroll_layer_id: scroll_layer_id,
                     border_radius: border_radius,
                 });
             }
         }
     }
 
-    pub fn supported_style(&mut self, border: &BorderSide) -> bool {
-        match border.style {
-            BorderStyle::Solid |
-            BorderStyle::None |
-            BorderStyle::Dotted |
-            BorderStyle::Dashed |
-            BorderStyle::Inset |
-            BorderStyle::Ridge |
-            BorderStyle::Groove |
-            BorderStyle::Outset |
-            BorderStyle::Double => {
-                return true;
-            }
-            _ => {
-                println!("TODO: Other border styles {:?}", border.style);
-                return false;
-            }
-        }
-    }
-
     pub fn add_border(&mut self,
                       scroll_layer_id: ScrollLayerId,
                       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,
@@ -537,106 +518,40 @@ impl FrameBuilder {
                                    &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;
 
-                if !self.supported_style(left) || !self.supported_style(right) ||
-                   !self.supported_style(top) || !self.supported_style(bottom) {
-                    println!("Unsupported border style, not rendering border");
-                    return;
-                }
-
                 // 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 tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
-                let tl_inner = tl_outer + LayerPoint::new(radius.top_left.width.max(border_item.widths.left),
-                                                          radius.top_left.height.max(border_item.widths.top));
-
-                let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
-                let tr_inner = tr_outer + LayerPoint::new(-radius.top_right.width.max(border_item.widths.right),
-                                                          radius.top_right.height.max(border_item.widths.top));
-
-                let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
-                let bl_inner = bl_outer + LayerPoint::new(radius.bottom_left.width.max(border_item.widths.left),
-                                                          -radius.bottom_left.height.max(border_item.widths.bottom));
-
-                let br_outer = LayerPoint::new(rect.origin.x + rect.size.width,
-                                               rect.origin.y + rect.size.height);
-                let br_inner = br_outer - LayerPoint::new(radius.bottom_right.width.max(border_item.widths.right),
-                                                          radius.bottom_right.height.max(border_item.widths.bottom));
-
-                // 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).
-                // TODO(gw): Detect some more simple cases and handle those with simpler shaders too.
-                // TODO(gw): Consider whether it's only worth doing this for large rectangles (since
-                //           it takes a little more CPU time to handle multiple rectangles compared
-                //           to a single border primitive).
-                if left.style == BorderStyle::Solid {
-                    let same_color = left_color == top_color &&
-                                     left_color == right_color &&
-                                     left_color == bottom_color;
-                    let same_style = left.style == top.style &&
-                                     left.style == right.style &&
-                                     left.style == bottom.style;
-
-                    if same_color && same_style && radius.is_zero() {
-                        let rects = [
-                            LayerRect::new(rect.origin,
-                                           LayerSize::new(rect.size.width, border_item.widths.top)),
-                            LayerRect::new(LayerPoint::new(tl_outer.x, tl_inner.y),
-                                           LayerSize::new(border_item.widths.left,
-                                                          rect.size.height - border_item.widths.top - border_item.widths.bottom)),
-                            LayerRect::new(tr_inner,
-                                           LayerSize::new(border_item.widths.right,
-                                                          rect.size.height - border_item.widths.top - border_item.widths.bottom)),
-                            LayerRect::new(LayerPoint::new(bl_outer.x, bl_inner.y),
-                                           LayerSize::new(rect.size.width, border_item.widths.bottom))
-                        ];
-
-                        for rect in &rects {
-                            self.add_solid_rectangle(scroll_layer_id,
-                                                     rect,
-                                                     clip_region,
-                                                     &top_color,
-                                                     PrimitiveFlags::None);
-                        }
-
-                        return;
-                    }
-                }
-
-                //Note: while similar to `ComplexClipRegion::get_inner_rect()` in spirit,
-                // this code is a bit more complex and can not there for be merged.
-                let inner_rect = rect_from_points_f(tl_inner.x.max(bl_inner.x),
-                                                    tl_inner.y.max(tr_inner.y),
-                                                    tr_inner.x.min(br_inner.x),
-                                                    bl_inner.y.min(br_inner.y));
-
                 let prim_cpu = BorderPrimitiveCpu {
-                    inner_rect: LayerRect::from_untyped(&inner_rect),
                 };
 
                 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 ],
@@ -657,58 +572,74 @@ impl FrameBuilder {
                 self.add_primitive(scroll_layer_id,
                                    &rect,
                                    clip_region,
                                    None,
                                    PrimitiveContainer::Border(prim_cpu, prim_gpu));
             }
             BorderDetails::Gradient(ref border) => {
                 for segment in create_segments(border.outset) {
+                    let segment_rel = segment.origin - rect.origin;
+
                     self.add_gradient(scroll_layer_id,
                                       segment,
                                       clip_region,
-                                      border.gradient.start_point,
-                                      border.gradient.end_point,
+                                      border.gradient.start_point - segment_rel,
+                                      border.gradient.end_point - segment_rel,
                                       border.gradient.stops,
-                                      border.gradient.extend_mode);
+                                      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,
                                              segment,
                                              clip_region,
-                                             border.gradient.start_center,
+                                             border.gradient.start_center - segment_rel,
                                              border.gradient.start_radius,
-                                             border.gradient.end_center,
+                                             border.gradient.end_center - segment_rel,
                                              border.gradient.end_radius,
                                              border.gradient.ratio_xy,
                                              border.gradient.stops,
-                                             border.gradient.extend_mode);
+                                             border.gradient.extend_mode,
+                                             segment.size,
+                                             LayerSize::zero());
                 }
             }
         }
     }
 
     pub fn add_gradient(&mut self,
                         scroll_layer_id: ScrollLayerId,
                         rect: LayerRect,
                         clip_region: &ClipRegion,
                         start_point: LayerPoint,
                         end_point: LayerPoint,
                         stops: ItemRange,
-                        extend_mode: ExtendMode) {
-        // Fast path for clamped, axis-aligned gradients:
-        let aligned = extend_mode == ExtendMode::Clamp &&
-                      (start_point.x == end_point.x &&
-                       start_point.x.min(end_point.x) <= rect.min_x() &&
-                       start_point.x.max(end_point.x) >= rect.max_x()) ||
-                       (start_point.y == end_point.y &&
-                       start_point.y.min(end_point.y) <= rect.min_y() &&
-                       start_point.y.max(end_point.y) >= rect.max_y());
+                        extend_mode: ExtendMode,
+                        tile_size: LayerSize,
+                        tile_spacing: LayerSize) {
+        let tile_repeat = tile_size + tile_spacing;
+        let is_not_tiled = tile_repeat.width >= rect.size.width &&
+                           tile_repeat.height >= rect.size.height;
+
+        let aligned_and_fills_rect = (start_point.x == end_point.x &&
+                                      start_point.y.min(end_point.y) <= 0.0 &&
+                                      start_point.y.max(end_point.y) >= rect.size.height) ||
+                                     (start_point.y == end_point.y &&
+                                      start_point.x.min(end_point.x) <= 0.0 &&
+                                      start_point.x.max(end_point.x) >= rect.size.width);
+
+        // Fast path for clamped, axis-aligned gradients, with gradient lines intersecting all of rect:
+        let aligned = extend_mode == ExtendMode::Clamp && is_not_tiled && aligned_and_fills_rect;
+
         // Try to ensure that if the gradient is specified in reverse, then so long as the stops
         // are also supplied in reverse that the rendered result will be equivalent. To do this,
         // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
         // just designate the reference orientation as start < end. Aligned gradient rendering
         // manages to produce the same result regardless of orientation, so don't worry about
         // reversing in that case.
         let reverse_stops = !aligned &&
                             (start_point.x > end_point.x ||
@@ -730,17 +661,19 @@ impl FrameBuilder {
         } else {
             (start_point, end_point)
         };
 
         let gradient_gpu = GradientPrimitiveGpu {
             start_point: sp,
             end_point: ep,
             extend_mode: pack_as_float(extend_mode as u32),
-            padding: [0.0, 0.0, 0.0],
+            tile_size: tile_size,
+            tile_repeat: tile_repeat,
+            padding: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
         };
 
         let prim = if aligned {
             PrimitiveContainer::AlignedGradient(gradient_cpu, gradient_gpu)
         } else {
             PrimitiveContainer::AngleGradient(gradient_cpu, gradient_gpu)
         };
 
@@ -756,30 +689,35 @@ impl FrameBuilder {
                                rect: LayerRect,
                                clip_region: &ClipRegion,
                                start_center: LayerPoint,
                                start_radius: f32,
                                end_center: LayerPoint,
                                end_radius: f32,
                                ratio_xy: f32,
                                stops: ItemRange,
-                               extend_mode: ExtendMode) {
+                               extend_mode: ExtendMode,
+                               tile_size: LayerSize,
+                               tile_spacing: LayerSize) {
         let radial_gradient_cpu = RadialGradientPrimitiveCpu {
             stops_range: stops,
             extend_mode: extend_mode,
             cache_dirty: true,
         };
 
         let radial_gradient_gpu = RadialGradientPrimitiveGpu {
             start_center: start_center,
             end_center: end_center,
             start_radius: start_radius,
             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,
                            &rect,
                            clip_region,
                            None,
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu, radial_gradient_gpu));
     }
@@ -797,67 +735,49 @@ impl FrameBuilder {
         if color.a == 0.0 {
             return
         }
 
         if size.0 <= 0 {
             return
         }
 
-        let (render_mode, glyphs_per_run) = if blur_radius == Au(0) {
-            // TODO(gw): Use a proper algorithm to select
-            // whether this item should be rendered with
-            // subpixel AA!
-            let render_mode = if self.config.enable_subpixel_aa {
-                FontRenderMode::Subpixel
-            } else {
-                FontRenderMode::Alpha
-            };
-
-            (render_mode, 8)
+        // TODO(gw): Use a proper algorithm to select
+        // whether this item should be rendered with
+        // subpixel AA!
+        let render_mode = if blur_radius == Au(0) &&
+                             self.config.enable_subpixel_aa {
+            FontRenderMode::Subpixel
         } else {
-            // TODO(gw): Support breaking up text shadow when
-            // the size of the text run exceeds the dimensions
-            // of the render target texture.
-            (FontRenderMode::Alpha, glyph_range.length)
+            FontRenderMode::Alpha
         };
 
-        let text_run_count = (glyph_range.length + glyphs_per_run - 1) / glyphs_per_run;
-        for run_index in 0..text_run_count {
-            let start = run_index * glyphs_per_run;
-            let end = cmp::min(start + glyphs_per_run, glyph_range.length);
-            let sub_range = ItemRange {
-                start: glyph_range.start + start,
-                length: end - start,
-            };
+        let prim_cpu = TextRunPrimitiveCpu {
+            font_key: font_key,
+            logical_font_size: size,
+            blur_radius: blur_radius,
+            glyph_range: glyph_range,
+            cache_dirty: true,
+            glyph_instances: Vec::new(),
+            color_texture_id: SourceTexture::Invalid,
+            color: *color,
+            render_mode: render_mode,
+            glyph_options: glyph_options,
+            resource_address: GpuStoreAddress(0),
+        };
 
-            let prim_cpu = TextRunPrimitiveCpu {
-                font_key: font_key,
-                logical_font_size: size,
-                blur_radius: blur_radius,
-                glyph_range: sub_range,
-                cache_dirty: true,
-                glyph_instances: Vec::new(),
-                color_texture_id: SourceTexture::Invalid,
-                color: *color,
-                render_mode: render_mode,
-                glyph_options: glyph_options,
-                resource_address: GpuStoreAddress(0),
-            };
+        let prim_gpu = TextRunPrimitiveGpu {
+            color: *color,
+        };
 
-            let prim_gpu = TextRunPrimitiveGpu {
-                color: *color,
-            };
-
-            self.add_primitive(scroll_layer_id,
-                               &rect,
-                               clip_region,
-                               None,
-                               PrimitiveContainer::TextRun(prim_cpu, prim_gpu));
-        }
+        self.add_primitive(scroll_layer_id,
+                           &rect,
+                           clip_region,
+                           None,
+                           PrimitiveContainer::TextRun(prim_cpu, prim_gpu));
     }
 
     pub fn add_box_shadow(&mut self,
                           scroll_layer_id: ScrollLayerId,
                           box_bounds: &LayerRect,
                           clip_region: &ClipRegion,
                           box_offset: &LayerPoint,
                           color: &ColorF,
@@ -867,17 +787,17 @@ impl FrameBuilder {
                           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,
+                                     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
@@ -962,22 +882,27 @@ impl FrameBuilder {
                 }
             }
             BoxShadowKind::Shadow(rects) => {
                 let inverted = match clip_mode {
                     BoxShadowClipMode::Outset | BoxShadowClipMode::None => 0.0,
                     BoxShadowClipMode::Inset => 1.0,
                 };
 
-                // If we have a border radius, we'll need to apply
-                // a clip-out mask.
+                // 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,
-                                             ClipMode::ClipOut))
+                                             extra_clip_mode))
                 } else {
                     None
                 };
 
                 let prim_gpu = BoxShadowPrimitiveGpu {
                     src_rect: *box_bounds,
                     bs_rect: bs_rect,
                     color: *color,
@@ -1201,91 +1126,81 @@ impl FrameBuilder {
 
                     if composite_count == 0 && stacking_context.should_isolate {
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let item = AlphaRenderItem::HardwareComposite(stacking_context_index,
                                                                       current_task.id,
                                                                       HardwareCompositeOp::PremultipliedAlpha,
                                                                       next_z);
                         next_z += 1;
-                        prev_task.as_alpha_batch().alpha_items.push(item);
+                        prev_task.as_alpha_batch().items.push(item);
                         prev_task.children.push(current_task);
                         current_task = prev_task;
                     }
 
                     for filter in &stacking_context.composite_ops.filters {
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let item = AlphaRenderItem::Blend(stacking_context_index,
                                                           current_task.id,
                                                           *filter,
                                                           next_z);
                         next_z += 1;
-                        prev_task.as_alpha_batch().alpha_items.push(item);
+                        prev_task.as_alpha_batch().items.push(item);
                         prev_task.children.push(current_task);
                         current_task = prev_task;
                     }
                     if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
                         let readback_task =
                             RenderTask::new_readback(stacking_context_index,
                                                      stacking_context.bounding_rect);
 
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let item = AlphaRenderItem::Composite(stacking_context_index,
                                                               readback_task.id,
                                                               current_task.id,
                                                               mix_blend_mode,
                                                               next_z);
                         next_z += 1;
-                        prev_task.as_alpha_batch().alpha_items.push(item);
+                        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) => {
                     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);
-                    let xf_rect = match &self.clip_scroll_group_store[group_index.0].xf_rect {
-                        &Some(ref xf_rect) => xf_rect,
-                        &None => continue,
-                    };
+                    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() {
                             let prim_metadata = self.prim_store.get_metadata(prim_index);
 
                             // Add any dynamic render tasks needed to render this primitive
                             if let Some(ref render_task) = prim_metadata.render_task {
                                 current_task.children.push(render_task.clone());
                             }
                             if let Some(ref clip_task) = prim_metadata.clip_task {
                                 current_task.children.push(clip_task.clone());
                             }
 
-                            let needs_clipping = prim_metadata.clip_task.is_some();
-                            let needs_blending = xf_rect.kind == TransformedRectKind::Complex ||
-                                                 !prim_metadata.is_opaque ||
-                                                 needs_clipping;
-
-                            let items = if needs_blending {
-                                &mut current_task.as_alpha_batch().alpha_items
-                            } else {
-                                &mut current_task.as_alpha_batch().opaque_items
-                            };
-                            items.push(AlphaRenderItem::Primitive(group_index, prim_index, next_z));
+                            let item = AlphaRenderItem::Primitive(group_index, prim_index, next_z);
+                            current_task.as_alpha_batch().items.push(item);
                             next_z += 1;
                         }
                     }
                 }
             }
         }
 
         debug_assert!(alpha_task_stack.is_empty());
@@ -1444,22 +1359,22 @@ impl<'a> LayerRectCalculationAndCullingP
 
     fn run(&mut self) {
         self.recalculate_clip_scroll_groups();
         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) =>
+            match *cmd {
+                PrimitiveRunCmd::PushStackingContext(stacking_context_index) =>
                     self.handle_push_stacking_context(stacking_context_index),
-                &PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count, scroll_layer_id) =>
+                PrimitiveRunCmd::PrimitiveRun(prim_index, prim_count, scroll_layer_id) =>
                     self.handle_primitive_run(prim_index, prim_count, scroll_layer_id),
-                &PrimitiveRunCmd::PopStackingContext => self.handle_pop_stacking_context(),
+                PrimitiveRunCmd::PopStackingContext => self.handle_pop_stacking_context(),
             }
         }
 
         mem::replace(&mut self.frame_builder.cmds, commands);
     }
 
     fn recalculate_clip_scroll_nodes(&mut self) {
         for (_, ref mut node) in self.clip_scroll_tree.nodes.iter_mut() {
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -192,29 +192,29 @@ pub struct PackedTexel {
     pub b: u8,
     pub g: u8,
     pub r: u8,
     pub a: u8,
 }
 
 impl PackedTexel {
     pub fn high_bytes(color: &ColorF) -> PackedTexel {
-        Self::extract_bytes(color, COLOR_FLOAT_TO_FIXED)
+        Self::extract_bytes(color, 8)
     }
 
     pub fn low_bytes(color: &ColorF) -> PackedTexel {
-        Self::extract_bytes(color, COLOR_FLOAT_TO_FIXED_WIDE)
+        Self::extract_bytes(color, 0)
     }
 
-    fn extract_bytes(color: &ColorF, multiplier: f32) -> PackedTexel {
+    fn extract_bytes(color: &ColorF, shift_by: i32) -> PackedTexel {
         PackedTexel {
-            b: ((0.5 + color.b * multiplier).floor() as u32 & 0xff) as u8,
-            g: ((0.5 + color.g * multiplier).floor() as u32 & 0xff) as u8,
-            r: ((0.5 + color.r * multiplier).floor() as u32 & 0xff) as u8,
-            a: ((0.5 + color.a * multiplier).floor() as u32 & 0xff) as u8,
+            b: ((0.5 + color.b * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u32 >> shift_by & 0xff) as u8,
+            g: ((0.5 + color.g * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u32 >> shift_by & 0xff) as u8,
+            r: ((0.5 + color.r * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u32 >> shift_by & 0xff) as u8,
+            a: ((0.5 + color.a * COLOR_FLOAT_TO_FIXED_WIDE).floor() as u32 >> shift_by & 0xff) as u8,
         }
     }
 }
 
 #[derive(Debug, Clone, Copy)]
 #[repr(C)]
 pub struct PackedVertex {
     pub pos: [f32; 2],
@@ -387,13 +387,13 @@ pub enum LowLevelFilterOp {
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub enum HardwareCompositeOp {
     PremultipliedAlpha,
 }
 
 impl HardwareCompositeOp {
     pub fn to_blend_mode(&self) -> BlendMode {
-        match self {
-            &HardwareCompositeOp::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
+        match *self {
+            HardwareCompositeOp::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
         }
     }
 }
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -1,22 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-//! A GPU based Webrender.
+//! A GPU based renderer for the web.
 //!
 //! It serves as an experimental render backend for [Servo](https://servo.org/),
 //! but it can also be used as such in a standalone application.
 //!
 //! # External dependencies
-//! Webrender currently depends on [FreeType](https://www.freetype.org/)
+//! WebRender currently depends on [FreeType](https://www.freetype.org/)
 //!
 //! # Api Structure
-//! The main entry point to webrender is the `webrender::renderer::Renderer`.
+//! The main entry point to WebRender is the `webrender::renderer::Renderer`.
 //!
 //! By calling `Renderer::new(...)` you get a `Renderer`, as well as a `RenderApiSender`.
 //! Your `Renderer` is responsible to render the previously processed frames onto the screen.
 //!
 //! By calling `yourRenderApiSenderInstance.create_api()`, you'll get a `RenderApi` instance,
 //! which is responsible for the processing of new frames. A worker thread is used internally to
 //! untie the workload from the application thread and therefore be able
 //! to make better use of multicore systems.
@@ -41,16 +41,17 @@ 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;
 mod frame_builder;
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -1,17 +1,17 @@
 /* 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 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::{MatrixHelpers, TransformedRect};
+use util::{ComplexClipRegionHelpers, MatrixHelpers, TransformedRect};
 use webrender_traits::{AuxiliaryLists, BorderRadius, ClipRegion, ComplexClipRegion, ImageMask};
 use webrender_traits::{DeviceIntRect, LayerToWorldTransform};
 use webrender_traits::{LayerRect, LayerPoint, LayerSize};
 
 const MAX_CLIP: f32 = 1000000.0;
 
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq)]
@@ -34,19 +34,19 @@ pub enum ClipSource {
     // from the clip region as part of the mask. This is true
     // for clip/scroll nodes, but false for primitives, where
     // the clip rect is handled in local space.
     Region(ClipRegion, RegionMode),
 }
 
 impl ClipSource {
     pub fn image_mask(&self) -> Option<ImageMask> {
-        match self {
-            &ClipSource::Complex(..) => None,
-            &ClipSource::Region(ref region, _) => region.image_mask,
+        match *self {
+            ClipSource::Complex(..) => None,
+            ClipSource::Region(ref region, _) => region.image_mask,
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub struct ClipAddressRange {
     pub start: GpuStoreAddress,
     item_count: usize,
@@ -68,17 +68,17 @@ impl Geometry {
             bounding_rect: DeviceIntRect::zero(),
         }
     }
 
     fn update(&mut self,
               transform: &LayerToWorldTransform,
               device_pixel_ratio: f32) {
         let transformed = TransformedRect::new(&self.local_rect,
-                                               &transform,
+                                               transform,
                                                device_pixel_ratio);
         self.bounding_rect = transformed.bounding_rect;
     }
 }
 
 /// Depending on the complexity of the clip, we may either
 /// know the outer and/or inner rect, or neither or these.
 /// In the case of a clip-out, we currently set the mask
@@ -118,21 +118,21 @@ impl MaskCacheInfo {
         }
 
         let mut image = None;
         let mut clip_count = 0;
 
         // Work out how much clip data space we need to allocate
         // and if we have an image mask.
         for clip in clips {
-            match clip {
-                &ClipSource::Complex(..) => {
+            match *clip {
+                ClipSource::Complex(..) => {
                     clip_count += 1;
                 },
-                &ClipSource::Region(ref region, region_mode) => {
+                ClipSource::Region(ref region, region_mode) => {
                     if let Some(info) = region.image_mask {
                         debug_assert!(image.is_none());     // TODO(gw): Support >1 image mask!
                         image = Some((info, clip_store.alloc(MASK_DATA_GPU_SIZE)));
                     }
 
                     clip_count += region.complex.length;
                     if region_mode == RegionMode::IncludeRect {
                         clip_count += 1;
@@ -174,35 +174,35 @@ impl MaskCacheInfo {
                                                      LayerSize::new(2.0 * MAX_CLIP, 2.0 * MAX_CLIP)));
             let mut local_inner: Option<LayerRect> = None;
             let mut has_clip_out = false;
 
             self.effective_clip_count = 0;
             self.is_aligned = is_aligned;
 
             for source in sources {
-                match source {
-                    &ClipSource::Complex(rect, radius, mode) => {
+                match *source {
+                    ClipSource::Complex(rect, radius, mode) => {
                         // Once we encounter a clip-out, we just assume the worst
                         // case clip mask size, for now.
                         if mode == ClipMode::ClipOut {
                             has_clip_out = true;
                         }
                         debug_assert!(self.effective_clip_count < self.clip_range.item_count);
                         let address = self.clip_range.start + self.effective_clip_count * CLIP_DATA_GPU_SIZE;
                         self.effective_clip_count += 1;
 
                         let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE);
                         let data = ClipData::uniform(rect, radius, mode);
                         PrimitiveStore::populate_clip_data(slice, data);
                         local_rect = local_rect.and_then(|r| r.intersection(&rect));
                         local_inner = ComplexClipRegion::new(rect, BorderRadius::uniform(radius))
-                                                        .get_inner_rect();
+                                                        .get_inner_rect_safe();
                     }
-                    &ClipSource::Region(ref region, region_mode) => {
+                    ClipSource::Region(ref region, region_mode) => {
                         local_rect = local_rect.and_then(|r| r.intersection(&region.main));
                         local_inner = match region.image_mask {
                             Some(ref mask) if !mask.repeat => {
                                 local_rect = local_rect.and_then(|r| r.intersection(&mask.rect));
                                 None
                             },
                             Some(_) => None,
                             None => local_rect,
@@ -223,18 +223,18 @@ impl MaskCacheInfo {
                         let address = self.clip_range.start + self.effective_clip_count * CLIP_DATA_GPU_SIZE;
                         self.effective_clip_count += clips.len();
 
                         let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE * clips.len());
                         for (clip, chunk) in clips.iter().zip(slice.chunks_mut(CLIP_DATA_GPU_SIZE)) {
                             let data = ClipData::from_clip_region(clip);
                             PrimitiveStore::populate_clip_data(chunk, data);
                             local_rect = local_rect.and_then(|r| r.intersection(&clip.rect));
-                            local_inner = local_inner.and_then(|r| clip.get_inner_rect()
-                                                                       .and_then(|ref inner| r.intersection(&inner)));
+                            local_inner = local_inner.and_then(|r| clip.get_inner_rect_safe()
+                                                                       .and_then(|ref inner| r.intersection(inner)));
                         }
                     }
                 }
             }
 
             // Work out the type of mask geometry we have, based on the
             // list of clip sources above.
             if has_clip_out {
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -143,17 +143,17 @@ impl FontContext {
         if self.cg_fonts.contains_key(font_key) {
             return
         }
 
         self.cg_fonts.insert((*font_key).clone(), native_font_handle);
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
-        if let Some(cg_font) = self.cg_fonts.remove(font_key) {
+        if let Some(_) = self.cg_fonts.remove(font_key) {
             // Unstable Rust has a retain() method on HashMap that will
             // let us do this in-place. https://github.com/rust-lang/rust/issues/36648
             let ct_font_keys = self.ct_fonts.keys()
                                             .filter(|k| k.0 == *font_key)
                                             .cloned()
                                             .collect::<Vec<_>>();
             for ct_font_key in ct_font_keys {
                 self.ct_fonts.remove(&ct_font_key);
@@ -215,17 +215,17 @@ impl FontContext {
             FontRenderMode::Subpixel => {
                 self.gamma_lut.preblend_bgra(pixels, width, height, color_lut);
             },
             _ => {} // Again, give mono untouched since only the alpha matters.
         }
     }
 
     #[allow(dead_code)]
-    fn print_glyph_data(&mut self, data: &Vec<u8>, width: usize, height: usize) {
+    fn print_glyph_data(&mut self, data: &[u8], width: usize, height: usize) {
         // Rust doesn't have step_by support on stable :(
         println!("Width is: {:?} height: {:?}", width, height);
         for i in 0..height {
             let current_height = i * width * 4;
 
             for pixel in data[current_height .. current_height + (width * 4)].chunks(4) {
                 let b = pixel[0];
                 let g = pixel[1];
@@ -371,14 +371,14 @@ impl FontContext {
 
                 Some(RasterizedGlyph {
                     width: metrics.rasterized_width,
                     height: metrics.rasterized_height,
                     bytes: rasterized_pixels,
                 })
             }
             None => {
-                return Some(RasterizedGlyph::blank());
+                Some(RasterizedGlyph::blank())
             }
         }
     }
 }
 
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -145,17 +145,17 @@ impl FontContext {
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         self.fonts.remove(font_key);
     }
 
     // Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite
     // doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture
     #[allow(dead_code)]
-    fn print_glyph_data(&self, data: &Vec<u8>, width: usize, height: usize) {
+    fn print_glyph_data(&self, data: &[u8], width: usize, height: usize) {
         // Rust doesn't have step_by support on stable :(
         for i in 0..height {
             let current_height = i * width * 3;
 
             for pixel in data[current_height .. current_height + (width * 3)].chunks(3) {
                 let r = pixel[0];
                 let g = pixel[1];
                 let b = pixel[2];
@@ -213,17 +213,17 @@ impl FontContext {
         let analysis = self.create_glyph_analysis(key, render_mode, None);
 
         let texture_type = dwrite_texture_type(render_mode);
         get_glyph_dimensions_with_analysis(analysis, texture_type)
     }
 
     // DWRITE gives us values in RGB. WR doesn't really touch it after. Note, CG returns in BGR
     // TODO: Decide whether all fonts should return RGB or BGR
-    fn convert_to_rgba(&self, pixels: &Vec<u8>, render_mode: FontRenderMode) -> Vec<u8> {
+    fn convert_to_rgba(&self, pixels: &[u8], render_mode: FontRenderMode) -> Vec<u8> {
         match render_mode {
             FontRenderMode::Mono => {
                 let mut rgba_pixels: Vec<u8> = vec![0; pixels.len() * 4];
                 for i in 0..pixels.len() {
                     rgba_pixels[i*4+0] = pixels[i];
                     rgba_pixels[i*4+1] = pixels[i];
                     rgba_pixels[i*4+2] = pixels[i];
                     rgba_pixels[i*4+3] = pixels[i];
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -123,16 +123,22 @@ pub struct PrimitiveMetadata {
     // that the box-shadow shader needs to run on. For
     // text-shadow, this creates a render task chain
     // that implements a 2-pass separable blur on a
     // text run.
     pub render_task: Option<RenderTask>,
     pub clip_task: Option<RenderTask>,
 }
 
+impl PrimitiveMetadata {
+    pub fn needs_clipping(&self) -> bool {
+        self.clip_task.is_some()
+    }
+}
+
 #[derive(Debug, Clone)]
 #[repr(C)]
 pub struct RectanglePrimitive {
     pub color: ColorF,
 }
 
 #[derive(Debug)]
 pub enum ImagePrimitiveKind {
@@ -180,17 +186,16 @@ impl YuvImagePrimitiveGpu {
             color_space: color_space as u32 as f32,
             padding: 0.0,
         }
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct BorderPrimitiveCpu {
-    pub inner_rect: LayerRect,
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 pub struct BorderPrimitiveGpu {
     pub style: [f32; 4],
     pub widths: [f32; 4],
     pub colors: [ColorF; 4],
@@ -225,18 +230,20 @@ pub struct GradientStopGpu {
     padding: [f32; 3],
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 pub struct GradientPrimitiveGpu {
     pub start_point: LayerPoint,
     pub end_point: LayerPoint,
+    pub tile_size: LayerSize,
+    pub tile_repeat: LayerSize,
     pub extend_mode: f32,
-    pub padding: [f32; 3],
+    pub padding: [f32; 7],
 }
 
 #[derive(Debug)]
 pub struct GradientPrimitiveCpu {
     pub stops_range: ItemRange,
     pub extend_mode: ExtendMode,
     pub reverse_stops: bool,
     pub cache_dirty: bool,
@@ -246,16 +253,19 @@ pub struct GradientPrimitiveCpu {
 #[repr(C)]
 pub struct RadialGradientPrimitiveGpu {
     pub start_center: LayerPoint,
     pub end_center: LayerPoint,
     pub start_radius: f32,
     pub end_radius: f32,
     pub ratio_xy: f32,
     pub extend_mode: f32,
+    pub tile_size: LayerSize,
+    pub tile_repeat: LayerSize,
+    pub padding: [f32; 4],
 }
 
 #[derive(Debug)]
 pub struct RadialGradientPrimitiveCpu {
     pub stops_range: ItemRange,
     pub extend_mode: ExtendMode,
     pub cache_dirty: bool,
 }
@@ -713,17 +723,17 @@ impl PrimitiveStore {
                     render_task: None,
                     clip_task: None,
                 };
 
                 self.cpu_borders.push(border_cpu);
                 metadata
             }
             PrimitiveContainer::AlignedGradient(gradient_cpu, gradient_gpu) => {
-                let gpu_address = self.gpu_data32.push(gradient_gpu);
+                let gpu_address = self.gpu_data64.push(gradient_gpu);
                 let gpu_stops_address = self.gpu_data32.alloc(gradient_cpu.stops_range.length);
 
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::AlignedGradient,
@@ -734,17 +744,17 @@ impl PrimitiveStore {
                     render_task: None,
                     clip_task: None,
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::AngleGradient(gradient_cpu, gradient_gpu) => {
-                let gpu_address = self.gpu_data32.push(gradient_gpu);
+                let gpu_address = self.gpu_data64.push(gradient_gpu);
                 let gpu_gradient_address = self.gpu_gradient_data.alloc(1);
 
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::AngleGradient,
@@ -755,17 +765,17 @@ impl PrimitiveStore {
                     render_task: None,
                     clip_task: None,
                 };
 
                 self.cpu_gradients.push(gradient_cpu);
                 metadata
             }
             PrimitiveContainer::RadialGradient(radial_gradient_cpu, radial_gradient_gpu) => {
-                let gpu_address = self.gpu_data32.push(radial_gradient_gpu);
+                let gpu_address = self.gpu_data64.push(radial_gradient_gpu);
                 let gpu_gradient_address = self.gpu_gradient_data.alloc(1);
 
                 let metadata = PrimitiveMetadata {
                     // TODO: calculate if the gradient is actually opaque
                     is_opaque: false,
                     clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::RadialGradient,
@@ -1063,17 +1073,17 @@ impl PrimitiveStore {
 
         if let Some(ref mut clip_info) = metadata.clip_cache_info {
             clip_info.update(&metadata.clips,
                              layer_transform,
                              &mut self.gpu_data32,
                              device_pixel_ratio,
                              auxiliary_lists);
             for clip in &metadata.clips {
-                if let &ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, _) = clip {
+                if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, _) = *clip {
                     resource_cache.request_image(mask.image, ImageRendering::Auto, None);
                     prim_needs_resolve = true;
                 }
             }
         }
 
         match metadata.prim_kind {
             PrimitiveKind::Rectangle |
@@ -1332,40 +1342,24 @@ pub struct GpuBlock32 {
 impl Default for GpuBlock32 {
     fn default() -> GpuBlock32 {
         GpuBlock32 {
             data: unsafe { mem::uninitialized() }
         }
     }
 }
 
-impl From<GradientPrimitiveGpu> for GpuBlock32 {
-    fn from(data: GradientPrimitiveGpu) -> GpuBlock32 {
-        unsafe {
-            mem::transmute::<GradientPrimitiveGpu, GpuBlock32>(data)
-        }
-    }
-}
-
 impl From<GradientStopGpu> for GpuBlock32 {
     fn from(data: GradientStopGpu) -> GpuBlock32 {
         unsafe {
             mem::transmute::<GradientStopGpu, GpuBlock32>(data)
         }
     }
 }
 
-impl From<RadialGradientPrimitiveGpu> for GpuBlock32 {
-    fn from(data: RadialGradientPrimitiveGpu) -> GpuBlock32 {
-        unsafe {
-            mem::transmute::<RadialGradientPrimitiveGpu, GpuBlock32>(data)
-        }
-    }
-}
-
 impl From<YuvImagePrimitiveGpu> for GpuBlock16 {
     fn from(data: YuvImagePrimitiveGpu) -> GpuBlock16 {
         unsafe {
             mem::transmute::<YuvImagePrimitiveGpu, GpuBlock16>(data)
         }
     }
 }
 
@@ -1402,16 +1396,32 @@ pub struct GpuBlock64 {
 impl Default for GpuBlock64 {
     fn default() -> GpuBlock64 {
         GpuBlock64 {
             data: unsafe { mem::uninitialized() }
         }
     }
 }
 
+impl From<GradientPrimitiveGpu> for GpuBlock64 {
+    fn from(data: GradientPrimitiveGpu) -> GpuBlock64 {
+        unsafe {
+            mem::transmute::<GradientPrimitiveGpu, GpuBlock64>(data)
+        }
+    }
+}
+
+impl From<RadialGradientPrimitiveGpu> for GpuBlock64 {
+    fn from(data: RadialGradientPrimitiveGpu) -> GpuBlock64 {
+        unsafe {
+            mem::transmute::<RadialGradientPrimitiveGpu, GpuBlock64>(data)
+        }
+    }
+}
+
 impl From<BoxShadowPrimitiveGpu> for GpuBlock64 {
     fn from(data: BoxShadowPrimitiveGpu) -> GpuBlock64 {
         unsafe {
             mem::transmute::<BoxShadowPrimitiveGpu, GpuBlock64>(data)
         }
     }
 }
 
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -58,25 +58,25 @@ impl ApiRecordingReceiver for BinaryReco
     fn write_payload(&mut self, _: u32, data: &[u8]) {
         // signal payload with a 0 length
         self.file.write_u32::<LittleEndian>(0).ok();
         self.write_length_and_data(data);
     }
 }
 
 pub fn should_record_msg(msg: &ApiMsg) -> bool {
-    match msg {
-        &ApiMsg::AddRawFont(..) |
-        &ApiMsg::AddNativeFont(..) |
-        &ApiMsg::DeleteFont(..) |
-        &ApiMsg::AddImage(..) |
-        &ApiMsg::GenerateFrame(..) |
-        &ApiMsg::UpdateImage(..) |
-        &ApiMsg::DeleteImage(..) |
-        &ApiMsg::SetDisplayList(..) |
-        &ApiMsg::SetRootPipeline(..) |
-        &ApiMsg::Scroll(..) |
-        &ApiMsg::TickScrollingBounce |
-        &ApiMsg::WebGLCommand(..) =>
+    match *msg {
+        ApiMsg::AddRawFont(..) |
+        ApiMsg::AddNativeFont(..) |
+        ApiMsg::DeleteFont(..) |
+        ApiMsg::AddImage(..) |
+        ApiMsg::GenerateFrame(..) |
+        ApiMsg::UpdateImage(..) |
+        ApiMsg::DeleteImage(..) |
+        ApiMsg::SetDisplayList(..) |
+        ApiMsg::SetRootPipeline(..) |
+        ApiMsg::Scroll(..) |
+        ApiMsg::TickScrollingBounce |
+        ApiMsg::WebGLCommand(..) =>
             true,
         _ => false
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -296,17 +296,17 @@ impl RenderBackend {
                             profile_scope!("GetScrollLayerState");
                             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: self.main_thread_dispatcher.clone()
+                                        dispatcher: Arc::clone(&self.main_thread_dispatcher)
                                     }))
                                 } else {
                                     None
                                 };
 
                                 let result = wrapper.new_context(size, attributes, dispatcher);
 
                                 match result {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -55,18 +55,17 @@ pub enum AlphaRenderItem {
     Blend(StackingContextIndex, RenderTaskId, LowLevelFilterOp, i32),
     Composite(StackingContextIndex, RenderTaskId, RenderTaskId, MixBlendMode, i32),
     HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
 }
 
 #[derive(Debug, Clone)]
 pub struct AlphaRenderTask {
     screen_origin: DeviceIntPoint,
-    pub opaque_items: Vec<AlphaRenderItem>,
-    pub alpha_items: Vec<AlphaRenderItem>,
+    pub items: Vec<AlphaRenderItem>,
     pub isolate_clear: bool,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 pub enum MaskSegment {
     // This must match the SEGMENT_ values in clip_shared.glsl!
     All = 0,
@@ -148,18 +147,17 @@ impl RenderTask {
                            isolate_clear: bool,
                            location: RenderTaskLocation) -> RenderTask {
         RenderTask {
             id: RenderTaskId::Static(task_index),
             children: Vec::new(),
             location: location,
             kind: RenderTaskKind::Alpha(AlphaRenderTask {
                 screen_origin: screen_origin,
-                alpha_items: Vec::new(),
-                opaque_items: Vec::new(),
+                items: Vec::new(),
                 isolate_clear: isolate_clear,
             }),
         }
     }
 
     pub fn new_prim_cache(key: PrimitiveCacheKey,
                           size: DeviceIntSize,
                           prim_index: PrimitiveIndex) -> RenderTask {
@@ -193,24 +191,24 @@ impl RenderTask {
         // is in the intersection of all of all the outer bounds,
         // and if it's completely inside the intersection of all of the inner bounds.
 
         // TODO(gw): If we encounter a clip with unknown bounds, we'll just use
         // the original rect. This is overly conservative, but can
         // be optimized later.
         let mut result = Some(actual_rect);
         for &(_, ref clip) in clips {
-            match clip.bounds.as_ref().unwrap() {
-                &MaskBounds::OuterInner(ref outer, _) |
-                &MaskBounds::Outer(ref outer) => {
+            match *clip.bounds.as_ref().unwrap() {
+                MaskBounds::OuterInner(ref outer, _) |
+                MaskBounds::Outer(ref outer) => {
                     result = result.and_then(|rect| {
                         rect.intersection(&outer.bounding_rect)
                     });
                 }
-                &MaskBounds::None => {
+                MaskBounds::None => {
                     result = Some(actual_rect);
                     break;
                 }
             }
         }
 
         let task_rect = match result {
             None => return MaskResult::Outside,
@@ -218,20 +216,20 @@ impl RenderTask {
         };
 
         // Accumulate inner rects. As soon as we encounter
         // a clip mask where we don't have or don't know
         // the inner rect, this will become None.
         let inner_rect = clips.iter()
                               .fold(Some(task_rect), |current, clip| {
             current.and_then(|rect| {
-                let inner_rect = match clip.1.bounds.as_ref().unwrap() {
-                    &MaskBounds::Outer(..) |
-                    &MaskBounds::None => DeviceIntRect::zero(),
-                    &MaskBounds::OuterInner(_, ref inner) => inner.bounding_rect
+                let inner_rect = match *clip.1.bounds.as_ref().unwrap() {
+                    MaskBounds::Outer(..) |
+                    MaskBounds::None => DeviceIntRect::zero(),
+                    MaskBounds::OuterInner(_, ref inner) => inner.bounding_rect
                 };
                 rect.intersection(&inner_rect)
             })
         });
 
         // TODO(gw): This optimization is very conservative for now.
         //           For now, only draw optimized geometry if it is
         //           a single aligned rect mask with rounded corners.
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -37,17 +37,17 @@ use std::marker::PhantomData;
 use std::mem;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::TextureCache;
 use threadpool::ThreadPool;
-use tiling::{AlphaBatchKind, BlurCommand, Frame, PrimitiveBatch, PrimitiveBatchData, RenderTarget};
+use tiling::{AlphaBatchKind, BlurCommand, Frame, PrimitiveBatch, RenderTarget};
 use tiling::{AlphaRenderTarget, CacheClipInstance, PrimitiveInstance, ColorRenderTarget, RenderTargetKind};
 use time::precise_time_ns;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use util::TransformedRectKind;
 use webgl_types::GLContextHandleWrapper;
 use webrender_traits::{ColorF, Epoch, PipelineId, RenderNotifier, RenderDispatcher};
 use webrender_traits::{ExternalImageId, ExternalImageType, ImageData, ImageFormat, RenderApiSender};
 use webrender_traits::{DeviceIntRect, DevicePoint, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
@@ -584,17 +584,17 @@ impl Renderer {
         let (result_tx, result_rx) = channel();
 
         register_thread_with_profiler("Compositor".to_owned());
 
         let notifier = Arc::new(Mutex::new(None));
 
         let file_watch_handler = FileWatcher {
             result_tx: result_tx.clone(),
-            notifier: notifier.clone(),
+            notifier: Arc::clone(&notifier),
         };
 
         let mut device = Device::new(gl,
                                      options.resource_override_path.clone(),
                                      Box::new(file_watch_handler));
         // device-pixel ratio doesn't matter here - we are just creating resources.
         device.begin_frame(1.0);
 
@@ -852,21 +852,21 @@ impl Renderer {
         device.update_vao_main_vertices(prim_vao_id, &quad_vertices, VertexUsageHint::Static);
 
         let blur_vao_id = device.create_vao_with_new_instances(VertexFormat::Blur, mem::size_of::<BlurCommand>() as i32, prim_vao_id);
         let clip_vao_id = device.create_vao_with_new_instances(VertexFormat::Clip, mem::size_of::<CacheClipInstance>() as i32, prim_vao_id);
 
         device.end_frame();
 
         let main_thread_dispatcher = Arc::new(Mutex::new(None));
-        let backend_notifier = notifier.clone();
-        let backend_main_thread_dispatcher = main_thread_dispatcher.clone();
+        let backend_notifier = Arc::clone(&notifier);
+        let backend_main_thread_dispatcher = Arc::clone(&main_thread_dispatcher);
 
         let vr_compositor = Arc::new(Mutex::new(None));
-        let backend_vr_compositor = vr_compositor.clone();
+        let backend_vr_compositor = Arc::clone(&vr_compositor);
 
         // We need a reference to the webrender context from the render backend in order to share
         // texture ids
         let context_handle = match options.renderer_kind {
             RendererKind::Native => GLContextHandleWrapper::current_native_handle(),
             RendererKind::OSMesa => GLContextHandleWrapper::current_osmesa_handle(),
         };
 
@@ -976,17 +976,17 @@ impl Renderer {
     ///
     /// The RenderNotifier will be called when processing e.g. of a (scrolling) frame is done,
     /// and therefore the screen should be updated.
     pub fn set_render_notifier(&self, notifier: Box<RenderNotifier>) {
         let mut notifier_arc = self.notifier.lock().unwrap();
         *notifier_arc = Some(notifier);
     }
 
-    /// Sets the new MainThreadDispatcher.
+    /// Sets the new main thread dispatcher.
     ///
     /// Allows to dispatch functions to the main thread's event loop.
     pub fn set_main_thread_dispatcher(&self, dispatcher: Box<RenderDispatcher>) {
         let mut dispatcher_arc = self.main_thread_dispatcher.lock().unwrap();
         *dispatcher_arc = Some(dispatcher);
     }
 
     /// Sets the VRCompositorHandler.
@@ -1001,17 +1001,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>> {
-        return mem::replace(&mut self.pipeline_epoch_map, HashMap::with_hasher(Default::default()));
+        mem::replace(&mut self.pipeline_epoch_map, HashMap::with_hasher(Default::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");
 
@@ -1311,173 +1311,168 @@ impl Renderer {
         self.device.draw_indexed_triangles_instanced_u16(6, data.len() as i32);
         self.profile_counters.vertices.add(6 * data.len());
         self.profile_counters.draw_calls.inc();
     }
 
     fn submit_batch(&mut self,
                     batch: &PrimitiveBatch,
                     projection: &Matrix4D<f32>,
-                    render_task_data: &Vec<RenderTaskData>,
+                    render_task_data: &[RenderTaskData],
                     cache_texture: TextureId,
                     render_target: Option<(TextureId, i32)>,
                     target_dimensions: DeviceUintSize) {
         let transform_kind = batch.key.flags.transform_kind();
         let needs_clipping = batch.key.flags.needs_clipping();
         debug_assert!(!needs_clipping ||
                       batch.key.blend_mode == BlendMode::Alpha ||
                       batch.key.blend_mode == BlendMode::PremultipliedAlpha);
 
-        match batch.data {
-            PrimitiveBatchData::Instances(ref data) => {
-                let (marker, shader) = match batch.key.kind {
-                    AlphaBatchKind::Composite => unreachable!(),
-                    AlphaBatchKind::HardwareComposite => {
-                        let shader = self.ps_hw_composite.get(&mut self.device);
-                        (GPU_TAG_PRIM_HW_COMPOSITE, shader)
-                    }
-                    AlphaBatchKind::Blend => {
-                        let shader = self.ps_blend.get(&mut self.device);
-                        (GPU_TAG_PRIM_BLEND, shader)
-                    }
-                    AlphaBatchKind::Rectangle => {
-                        let shader = if needs_clipping {
-                            self.ps_rectangle_clip.get(&mut self.device, transform_kind)
-                        } else {
-                            self.ps_rectangle.get(&mut self.device, transform_kind)
-                        };
-                        (GPU_TAG_PRIM_RECT, shader)
-                    }
-                    AlphaBatchKind::TextRun => {
-                        let shader = match batch.key.blend_mode {
-                            BlendMode::Subpixel(..) => self.ps_text_run_subpixel.get(&mut self.device, transform_kind),
-                            BlendMode::Alpha | BlendMode::PremultipliedAlpha | BlendMode::None => self.ps_text_run.get(&mut self.device, transform_kind),
-                        };
-                        (GPU_TAG_PRIM_TEXT_RUN, shader)
-                    }
-                    AlphaBatchKind::Image => {
-                        let shader = self.ps_image.get(&mut self.device, transform_kind);
-                        (GPU_TAG_PRIM_IMAGE, shader)
-                    }
-                    AlphaBatchKind::ImageRect => {
-                        let shader = self.ps_image_rect.get(&mut self.device, transform_kind);
-                        (GPU_TAG_PRIM_IMAGE_RECT, shader)
-                    }
-                    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::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)
-                    }
-                    AlphaBatchKind::RadialGradient => {
-                        let shader = self.ps_radial_gradient.get(&mut self.device, transform_kind);
-                        (GPU_TAG_PRIM_RADIAL_GRADIENT, shader)
-                    }
-                    AlphaBatchKind::BoxShadow => {
-                        let shader = self.ps_box_shadow.get(&mut self.device, transform_kind);
-                        (GPU_TAG_PRIM_BOX_SHADOW, shader)
-                    }
-                    AlphaBatchKind::CacheImage => {
-                        let shader = self.ps_cache_image.get(&mut self.device, transform_kind);
-                        (GPU_TAG_PRIM_CACHE_IMAGE, shader)
-                    }
+        let (marker, shader) = match batch.key.kind {
+            AlphaBatchKind::Composite => {
+                let shader = self.ps_composite.get(&mut self.device);
+                (GPU_TAG_PRIM_COMPOSITE, shader)
+            }
+            AlphaBatchKind::HardwareComposite => {
+                let shader = self.ps_hw_composite.get(&mut self.device);
+                (GPU_TAG_PRIM_HW_COMPOSITE, shader)
+            }
+            AlphaBatchKind::Blend => {
+                let shader = self.ps_blend.get(&mut self.device);
+                (GPU_TAG_PRIM_BLEND, shader)
+            }
+            AlphaBatchKind::Rectangle => {
+                let shader = if needs_clipping {
+                    self.ps_rectangle_clip.get(&mut self.device, transform_kind)
+                } else {
+                    self.ps_rectangle.get(&mut self.device, transform_kind)
+                };
+                (GPU_TAG_PRIM_RECT, shader)
+            }
+            AlphaBatchKind::TextRun => {
+                let shader = match batch.key.blend_mode {
+                    BlendMode::Subpixel(..) => self.ps_text_run_subpixel.get(&mut self.device, transform_kind),
+                    BlendMode::Alpha | BlendMode::PremultipliedAlpha | BlendMode::None => self.ps_text_run.get(&mut self.device, transform_kind),
                 };
-
-                let shader = shader.unwrap();
+                (GPU_TAG_PRIM_TEXT_RUN, shader)
+            }
+            AlphaBatchKind::Image => {
+                let shader = self.ps_image.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_IMAGE, shader)
+            }
+            AlphaBatchKind::ImageRect => {
+                let shader = self.ps_image_rect.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_IMAGE_RECT, shader)
+            }
+            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::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)
+            }
+            AlphaBatchKind::RadialGradient => {
+                let shader = self.ps_radial_gradient.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_RADIAL_GRADIENT, shader)
+            }
+            AlphaBatchKind::BoxShadow => {
+                let shader = self.ps_box_shadow.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_BOX_SHADOW, shader)
+            }
+            AlphaBatchKind::CacheImage => {
+                let shader = self.ps_cache_image.get(&mut self.device, transform_kind);
+                (GPU_TAG_PRIM_CACHE_IMAGE, shader)
+            }
+        };
 
-                let _gm = self.gpu_profile.add_marker(marker);
-                let vao = self.prim_vao_id;
-                self.draw_instanced_batch(data,
-                                          vao,
-                                          shader,
-                                          &batch.key.textures,
-                                          projection);
-            }
-            PrimitiveBatchData::Composite(ref instance) => {
-                let _gm = self.gpu_profile.add_marker(GPU_TAG_PRIM_COMPOSITE);
-                let vao = self.prim_vao_id;
-                let shader = self.ps_composite.get(&mut self.device).unwrap();
+        // Handle special case readback for composites.
+        if batch.key.kind == AlphaBatchKind::Composite {
+            // composites can't be grouped together because
+            // they may overlap and affect each other.
+            debug_assert!(batch.instances.len() == 1);
+            let instance = &batch.instances[0];
+
+            // TODO(gw): This code branch is all a bit hacky. We rely
+            // on pulling specific values from the render target data
+            // and also cloning the single primitive instance to be
+            // able to pass to draw_instanced_batch(). We should
+            // think about a cleaner way to achieve this!
 
-                // TODO(gw): This code branch is all a bit hacky. We rely
-                // on pulling specific values from the render target data
-                // and also cloning the single primitive instance to be
-                // able to pass to draw_instanced_batch(). We should
-                // think about a cleaner way to achieve this!
+            // Before submitting the composite batch, do the
+            // framebuffer readbacks that are needed for each
+            // composite operation in this batch.
+            let cache_texture_dimensions = self.device.get_texture_dimensions(cache_texture);
 
-                // Before submitting the composite batch, do the
-                // framebuffer readbacks that are needed for each
-                // composite operation in this batch.
-                let cache_texture_dimensions = self.device.get_texture_dimensions(cache_texture);
+            let backdrop = &render_task_data[instance.task_index as usize];
+            let readback = &render_task_data[instance.user_data[0] as usize];
+            let source = &render_task_data[instance.user_data[1] as usize];
 
-                let backdrop = &render_task_data[instance.task_index as usize];
-                let readback = &render_task_data[instance.user_data[0] as usize];
-                let source = &render_task_data[instance.user_data[1] as usize];
+            // Bind the FBO to blit the backdrop to.
+            // Called per-instance in case the layer (and therefore FBO)
+            // changes. The device will skip the GL call if the requested
+            // target is already bound.
+            let cache_draw_target = (cache_texture, readback.data[4] as i32);
+            self.device.bind_draw_target(Some(cache_draw_target), Some(cache_texture_dimensions));
 
-                // Bind the FBO to blit the backdrop to.
-                // Called per-instance in case the layer (and therefore FBO)
-                // changes. The device will skip the GL call if the requested
-                // target is already bound.
-                let cache_draw_target = (cache_texture, readback.data[4] as i32);
-                self.device.bind_draw_target(Some(cache_draw_target), Some(cache_texture_dimensions));
+            let src_x = backdrop.data[0] - backdrop.data[4] + source.data[4];
+            let src_y = backdrop.data[1] - backdrop.data[5] + source.data[5];
 
-                let src_x = backdrop.data[0] - backdrop.data[4] + source.data[4];
-                let src_y = backdrop.data[1] - backdrop.data[5] + source.data[5];
-
-                let dest_x = readback.data[0];
-                let dest_y = readback.data[1];
+            let dest_x = readback.data[0];
+            let dest_y = readback.data[1];
 
-                let width = readback.data[2];
-                let height = readback.data[3];
+            let width = readback.data[2];
+            let height = readback.data[3];
 
-                let mut src = DeviceIntRect::new(DeviceIntPoint::new(src_x as i32, src_y as i32),
-                                                 DeviceIntSize::new(width as i32, height as i32));
-                let mut dest = DeviceIntRect::new(DeviceIntPoint::new(dest_x as i32, dest_y as i32),
-                                                  DeviceIntSize::new(width as i32, height as i32));
+            let mut src = DeviceIntRect::new(DeviceIntPoint::new(src_x as i32, src_y as i32),
+                                             DeviceIntSize::new(width as i32, height as i32));
+            let mut dest = DeviceIntRect::new(DeviceIntPoint::new(dest_x as i32, dest_y as i32),
+                                              DeviceIntSize::new(width as i32, height as i32));
 
-                // Need to invert the y coordinates and flip the image vertically when
-                // reading back from the framebuffer.
-                if render_target.is_none() {
-                    src.origin.y = target_dimensions.height as i32 - src.size.height - src.origin.y;
-                    dest.origin.y += dest.size.height;
-                    dest.size.height = -dest.size.height;
-                }
+            // Need to invert the y coordinates and flip the image vertically when
+            // reading back from the framebuffer.
+            if render_target.is_none() {
+                src.origin.y = target_dimensions.height as i32 - src.size.height - src.origin.y;
+                dest.origin.y += dest.size.height;
+                dest.size.height = -dest.size.height;
+            }
+
+            self.device.blit_render_target(render_target,
+                                           Some(src),
+                                           dest);
 
-                self.device.blit_render_target(render_target,
-                                               Some(src),
-                                               dest);
-
-                // Restore draw target to current pass render target + layer.
-                self.device.bind_draw_target(render_target, Some(target_dimensions));
+            // Restore draw target to current pass render target + layer.
+            self.device.bind_draw_target(render_target, Some(target_dimensions));
+        }
 
-                self.draw_instanced_batch(&[instance.clone()],
-                                          vao,
-                                          shader,
-                                          &batch.key.textures,
-                                          projection);
-            }
-        }
+        let shader = shader.unwrap();
+        let _gm = self.gpu_profile.add_marker(marker);
+        let vao = self.prim_vao_id;
+        self.draw_instanced_batch(&batch.instances,
+                                  vao,
+                                  shader,
+                                  &batch.key.textures,
+                                  projection);
     }
 
     fn draw_color_target(&mut self,
                          render_target: Option<(TextureId, i32)>,
                          target: &ColorRenderTarget,
                          target_size: DeviceUintSize,
                          color_cache_texture: TextureId,
                          clear_color: Option<[f32; 4]>,
-                         render_task_data: &Vec<RenderTaskData>,
+                         render_task_data: &[RenderTaskData],
                          projection: &Matrix4D<f32>) {
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(render_target, Some(target_size));
             self.device.disable_depth();
             self.device.enable_depth_write();
             self.device.set_blend(false);
             self.device.set_blend_mode_alpha();
@@ -1569,28 +1564,28 @@ impl Renderer {
         let _gm2 = GpuMarker::new(self.device.rc_gl(), "alpha batches");
         self.device.set_blend(false);
         let mut prev_blend_mode = BlendMode::None;
 
         self.device.set_depth_func(DepthFunction::Less);
         self.device.enable_depth();
         self.device.enable_depth_write();
 
-        for batch in &target.alpha_batcher.opaque_batches {
+        for batch in &target.alpha_batcher.batch_list.opaque_batches {
             self.submit_batch(batch,
                               &projection,
                               render_task_data,
                               color_cache_texture,
                               render_target,
                               target_size);
         }
 
         self.device.disable_depth_write();
 
-        for batch in &target.alpha_batcher.alpha_batches {
+        for batch in &target.alpha_batcher.batch_list.alpha_batches {
             if batch.key.blend_mode != prev_blend_mode {
                 match batch.key.blend_mode {
                     BlendMode::None => {
                         self.device.set_blend(false);
                     }
                     BlendMode::Alpha => {
                         self.device.set_blend(true);
                         self.device.set_blend_mode_alpha();
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -255,25 +255,24 @@ impl ResourceCache {
 
     pub fn max_texture_size(&self) -> u32 {
         self.texture_cache.max_texture_size()
     }
 
     fn should_tile(&self, descriptor: &ImageDescriptor, data: &ImageData) -> bool {
         let limit = self.max_texture_size();
         let size_check = descriptor.width > limit || descriptor.height > limit;
-        return match data {
-            &ImageData::Raw(_) => { size_check }
-            &ImageData::Blob(_) => { size_check }
-            &ImageData::External(info) => {
+        match *data {
+            ImageData::Raw(_) | ImageData::Blob(_) => { size_check }
+            ImageData::External(info) => {
                 // External handles already represent existing textures so it does
                 // not make sense to tile them into smaller ones.
                 info.image_type == ExternalImageType::ExternalBuffer && size_check
             },
-        };
+        }
     }
 
     pub fn add_font_template(&mut self, font_key: FontKey, template: FontTemplate) {
         // Push the new font to the glyph cache thread, and also store
         // it locally for glyph metric requests.
         self.glyph_cache_tx
             .send(GlyphCacheMsg::AddFont(font_key, template.clone()))
             .unwrap();
@@ -310,19 +309,19 @@ impl ResourceCache {
     }
 
     pub fn update_image_template(&mut self,
                                  image_key: ImageKey,
                                  descriptor: ImageDescriptor,
                                  data: ImageData,
                                  dirty_rect: Option<DeviceUintRect>) {
         let resource = if let Some(image) = self.image_templates.get(&image_key) {
-            assert!(image.descriptor.width == descriptor.width);
-            assert!(image.descriptor.height == descriptor.height);
-            assert!(image.descriptor.format == descriptor.format);
+            assert_eq!(image.descriptor.width, descriptor.width);
+            assert_eq!(image.descriptor.height, descriptor.height);
+            assert_eq!(image.descriptor.format, descriptor.format);
 
             let next_epoch = Epoch(image.epoch.0 + 1);
 
             let mut tiling = image.tiling;
             if tiling.is_none() && self.should_tile(&descriptor, &data) {
                 tiling = Some(DEFAULT_TILE_SIZE);
             }
 
@@ -344,27 +343,24 @@ impl ResourceCache {
         self.image_templates.insert(image_key, resource);
     }
 
     pub fn delete_image_template(&mut self, image_key: ImageKey) {
         let value = self.image_templates.remove(&image_key);
 
         // If the key is associated to an external image, pass the external id to renderer for cleanup.
         if let Some(image) = value {
-            match image.data {
-                ImageData::External(ext_image) => {
-                    match ext_image.image_type {
-                        ExternalImageType::Texture2DHandle |
-                        ExternalImageType::TextureRectHandle => {
-                            self.pending_external_image_update_list.push(ext_image.id);
-                        }
-                        _ => {}
+            if let ImageData::External(ext_image) = image.data {
+                match ext_image.image_type {
+                    ExternalImageType::Texture2DHandle |
+                    ExternalImageType::TextureRectHandle => {
+                        self.pending_external_image_update_list.push(ext_image.id);
                     }
+                    _ => {}
                 }
-                _ => {}
             }
 
             return;
         }
 
         println!("Delete the non-exist key:{:?}", image_key);
     }
 
@@ -401,17 +397,17 @@ impl ResourceCache {
                 let same_epoch = match self.cached_images.resources.get(&request) {
                     Some(entry) => entry.epoch == template.epoch,
                     None => false,
                 };
 
                 if !same_epoch && self.blob_image_requests.insert(request) {
                     renderer.request_blob_image(
                         key,
-                        data.clone(),
+                        Arc::clone(data),
                         &BlobImageDescriptor {
                             width: template.descriptor.width,
                             height: template.descriptor.height,
                             format: template.descriptor.format,
                             // TODO(nical): figure out the scale factor (should change with zoom).
                             scale_factor: 1.0,
                         },
                         template.dirty_rect,
@@ -841,17 +837,17 @@ fn spawn_glyph_cache_thread(workers: Arc
     thread::Builder::new().name("GlyphCache".to_string()).spawn(move|| {
         let mut glyph_cache = None;
         let mut current_frame_id = FrameId(0);
 
         register_thread_with_profiler("GlyphCache".to_string());
 
         let barrier = Arc::new(Barrier::new(worker_count));
         for i in 0..worker_count {
-            let barrier = barrier.clone();
+            let barrier = Arc::clone(&barrier);
             workers.lock().unwrap().execute(move || {
                 register_thread_with_profiler(format!("Glyph Worker {}", i));
                 barrier.wait();
             });
         }
 
         // Maintain a set of glyphs that have been requested this
         // frame. This ensures the glyph thread won't rasterize
@@ -877,17 +873,17 @@ fn spawn_glyph_cache_thread(workers: Arc
                     profile_scope!("AddFont");
 
                     // Add a new font to the font context in each worker thread.
                     // Use a barrier to ensure that each worker in the pool handles
                     // one of these messages, to ensure that the new font gets
                     // added to each worker thread.
                     let barrier = Arc::new(Barrier::new(worker_count));
                     for _ in 0..worker_count {
-                        let barrier = barrier.clone();
+                        let barrier = Arc::clone(&barrier);
                         let font_template = font_template.clone();
                         workers.lock().unwrap().execute(move || {
                             FONT_CONTEXT.with(|font_context| {
                                 let mut font_context = font_context.borrow_mut();
                                 match font_template {
                                     FontTemplate::Raw(ref bytes) => {
                                         font_context.add_raw_font(&font_key, &**bytes);
                                     }
@@ -903,17 +899,17 @@ fn spawn_glyph_cache_thread(workers: Arc
                     }
                 }
                 GlyphCacheMsg::DeleteFont(font_key) => {
                     profile_scope!("DeleteFont");
 
                     // Delete a font from the font context in each worker thread.
                     let barrier = Arc::new(Barrier::new(worker_count));
                     for _ in 0..worker_count {
-                        let barrier = barrier.clone();
+                        let barrier = Arc::clone(&barrier);
                         workers.lock().unwrap().execute(move || {
                             FONT_CONTEXT.with(|font_context| {
                                 let mut font_context = font_context.borrow_mut();
                                 font_context.delete_font(&font_key);
                             });
                             barrier.wait();
                         });
                     }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -22,99 +22,36 @@ 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::{WorldPoint4D, WorldToLayerTransform};
+use webrender_traits::{TransformStyle, WorldPoint4D, WorldToLayerTransform};
 use webrender_traits::{ExternalImageType};
 
 // 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,
                                      BuildHasherDefault<FnvHasher>>;
 
 trait AlphaBatchHelpers {
-    fn get_batch_kind(&self, metadata: &PrimitiveMetadata) -> AlphaBatchKind;
     fn get_color_textures(&self, metadata: &PrimitiveMetadata) -> [SourceTexture; 3];
-    fn get_blend_mode(&self, needs_blending: bool, metadata: &PrimitiveMetadata) -> BlendMode;
-    fn add_prim_to_batch(&self,
-                         prim_index: PrimitiveIndex,
-                         batch: &mut PrimitiveBatch,
-                         packed_layer_index: PackedLayerIndex,
-                         task_index: RenderTaskIndex,
-                         render_tasks: &RenderTaskCollection,
-                         pass_index: RenderPassIndex,
-                         z_sort_index: i32);
-    fn add_blend_to_batch(&self,
-                          stacking_context_index: StackingContextIndex,
-                          batch: &mut PrimitiveBatch,
-                          task_index: RenderTaskIndex,
-                          src_task_index: RenderTaskIndex,
-                          filter: LowLevelFilterOp,
-                          z_sort_index: i32);
-    fn add_hardware_composite_to_batch(&self,
-                                       stacking_context_index: StackingContextIndex,
-                                       batch: &mut PrimitiveBatch,
-                                       task_index: RenderTaskIndex,
-                                       src_task_index: RenderTaskIndex,
-                                       z_sort_index: i32);
+    fn get_blend_mode(&self,
+                      needs_blending: bool,
+                      metadata: &PrimitiveMetadata) -> BlendMode;
 }
 
 impl AlphaBatchHelpers for PrimitiveStore {
-    fn get_batch_kind(&self, metadata: &PrimitiveMetadata) -> AlphaBatchKind {
-        let batch_kind = match metadata.prim_kind {
-            PrimitiveKind::Border => AlphaBatchKind::Border,
-            PrimitiveKind::BoxShadow => AlphaBatchKind::BoxShadow,
-            PrimitiveKind::Image => {
-                let image_cpu = &self.cpu_images[metadata.cpu_prim_index.0];
-
-                match image_cpu.color_texture_id {
-                    SourceTexture::External(ext_image) => {
-                        match ext_image.image_type {
-                            ExternalImageType::Texture2DHandle => AlphaBatchKind::Image,
-                            ExternalImageType::TextureRectHandle => AlphaBatchKind::ImageRect,
-                            _ => {
-                                panic!("Non-texture handle type should be handled in other way.");
-                            }
-                        }
-                    }
-                    _ => {
-                        AlphaBatchKind::Image
-                    }
-                }
-            }
-            PrimitiveKind::YuvImage => AlphaBatchKind::YuvImage,
-            PrimitiveKind::Rectangle => AlphaBatchKind::Rectangle,
-            PrimitiveKind::AlignedGradient => AlphaBatchKind::AlignedGradient,
-            PrimitiveKind::AngleGradient => AlphaBatchKind::AngleGradient,
-            PrimitiveKind::RadialGradient => AlphaBatchKind::RadialGradient,
-            PrimitiveKind::TextRun => {
-                let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-                if text_run_cpu.blur_radius.0 == 0 {
-                    AlphaBatchKind::TextRun
-                } else {
-                    // Select a generic primitive shader that can blit the
-                    // results of the cached text blur to the framebuffer,
-                    // applying tile clipping etc.
-                    AlphaBatchKind::CacheImage
-                }
-            }
-        };
-
-        batch_kind
-    }
-
     fn get_color_textures(&self, metadata: &PrimitiveMetadata) -> [SourceTexture; 3] {
         let invalid = SourceTexture::Invalid;
         match metadata.prim_kind {
             PrimitiveKind::Border |
             PrimitiveKind::BoxShadow |
             PrimitiveKind::Rectangle |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
@@ -162,264 +99,16 @@ impl AlphaBatchHelpers for PrimitiveStor
                 if needs_blending {
                     BlendMode::Alpha
                 } else {
                     BlendMode::None
                 }
             }
         }
     }
-
-    fn add_blend_to_batch(&self,
-                          stacking_context_index: StackingContextIndex,
-                          batch: &mut PrimitiveBatch,
-                          task_index: RenderTaskIndex,
-                          src_task_index: RenderTaskIndex,
-                          filter: LowLevelFilterOp,
-                          z_sort_index: i32) {
-        let (filter_mode, amount) = match filter {
-            LowLevelFilterOp::Blur(..) => (0, 0.0),
-            LowLevelFilterOp::Contrast(amount) => (1, amount.to_f32_px()),
-            LowLevelFilterOp::Grayscale(amount) => (2, amount.to_f32_px()),
-            LowLevelFilterOp::HueRotate(angle) => (3, (angle as f32) / ANGLE_FLOAT_TO_FIXED),
-            LowLevelFilterOp::Invert(amount) => (4, amount.to_f32_px()),
-            LowLevelFilterOp::Saturate(amount) => (5, amount.to_f32_px()),
-            LowLevelFilterOp::Sepia(amount) => (6, amount.to_f32_px()),
-            LowLevelFilterOp::Brightness(amount) => (7, amount.to_f32_px()),
-            LowLevelFilterOp::Opacity(amount) => (8, amount.to_f32_px()),
-        };
-
-        let amount = (amount * 65535.0).round() as i32;
-
-        batch.items.push(PrimitiveBatchItem::StackingContext(stacking_context_index));
-
-        match batch.data {
-            PrimitiveBatchData::Instances(ref mut data) => {
-                data.push(PrimitiveInstance {
-                    global_prim_id: -1,
-                    prim_address: GpuStoreAddress(0),
-                    task_index: task_index.0 as i32,
-                    clip_task_index: -1,
-                    layer_index: -1,
-                    sub_index: filter_mode,
-                    user_data: [src_task_index.0 as i32, amount],
-                    z_sort_index: z_sort_index,
-                });
-            }
-            _ => unreachable!(),
-        }
-    }
-
-    fn add_hardware_composite_to_batch(&self,
-                                       stacking_context_index: StackingContextIndex,
-                                       batch: &mut PrimitiveBatch,
-                                       task_index: RenderTaskIndex,
-                                       src_task_index: RenderTaskIndex,
-                                       z_sort_index: i32) {
-        batch.items.push(PrimitiveBatchItem::StackingContext(stacking_context_index));
-
-        match batch.data {
-            PrimitiveBatchData::Instances(ref mut data) => {
-                data.push(PrimitiveInstance {
-                    global_prim_id: -1,
-                    prim_address: GpuStoreAddress(0),
-                    task_index: task_index.0 as i32,
-                    clip_task_index: -1,
-                    layer_index: -1,
-                    sub_index: -1,
-                    user_data: [src_task_index.0 as i32, 0],
-                    z_sort_index: z_sort_index,
-                });
-            }
-            _ => unreachable!(),
-        }
-    }
-
-    fn add_prim_to_batch(&self,
-                         prim_index: PrimitiveIndex,
-                         batch: &mut PrimitiveBatch,
-                         packed_layer_index: PackedLayerIndex,
-                         task_index: RenderTaskIndex,
-                         render_tasks: &RenderTaskCollection,
-                         child_pass_index: RenderPassIndex,
-                         z_sort_index: i32) {
-        let metadata = self.get_metadata(prim_index);
-        let packed_layer_index = packed_layer_index.0 as i32;
-        let global_prim_id = prim_index.0 as i32;
-        let prim_address = metadata.gpu_prim_index;
-        let clip_task_index = match metadata.clip_task {
-            Some(ref clip_task) => {
-                render_tasks.get_task_index(&clip_task.id, child_pass_index)
-            }
-            None => {
-                OPAQUE_TASK_INDEX
-            }
-        };
-        let task_index = task_index.0 as i32;
-        let clip_task_index = clip_task_index.0 as i32;
-        batch.items.push(PrimitiveBatchItem::Primitive(prim_index));
-
-        match &mut batch.data {
-            &mut PrimitiveBatchData::Composite(..) => unreachable!(),
-            &mut PrimitiveBatchData::Instances(ref mut data) => {
-                match batch.key.kind {
-                    AlphaBatchKind::Composite => unreachable!(),
-                    AlphaBatchKind::HardwareComposite => unreachable!(),
-                    AlphaBatchKind::Blend => unreachable!(),
-                    AlphaBatchKind::Rectangle => {
-                        data.push(PrimitiveInstance {
-                            task_index: task_index,
-                            clip_task_index: clip_task_index,
-                            layer_index: packed_layer_index,
-                            global_prim_id: global_prim_id,
-                            prim_address: prim_address,
-                            sub_index: 0,
-                            user_data: [0, 0],
-                            z_sort_index: z_sort_index,
-                        });
-                    }
-                    AlphaBatchKind::TextRun => {
-                        let text_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-
-                        for glyph_index in 0..metadata.gpu_data_count {
-                            data.push(PrimitiveInstance {
-                                task_index: task_index,
-                                clip_task_index: clip_task_index,
-                                layer_index: packed_layer_index,
-                                global_prim_id: global_prim_id,
-                                prim_address: prim_address,
-                                sub_index: metadata.gpu_data_address.0 + glyph_index,
-                                user_data: [ text_cpu.resource_address.0 + glyph_index, 0 ],
-                                z_sort_index: z_sort_index,
-                            });
-                        }
-                    }
-                    AlphaBatchKind::Image |
-                    AlphaBatchKind::ImageRect => {
-                        let image_cpu = &self.cpu_images[metadata.cpu_prim_index.0];
-
-                        data.push(PrimitiveInstance {
-                            task_index: task_index,
-                            clip_task_index: clip_task_index,
-                            layer_index: packed_layer_index,
-                            global_prim_id: global_prim_id,
-                            prim_address: prim_address,
-                            sub_index: 0,
-                            user_data: [ image_cpu.resource_address.0, 0 ],
-                            z_sort_index: z_sort_index,
-                        });
-                    }
-                    AlphaBatchKind::YuvImage => {
-                        let image_yuv_cpu = &self.cpu_yuv_images[metadata.cpu_prim_index.0];
-
-                        data.push(PrimitiveInstance {
-                            task_index: task_index,
-                            clip_task_index: clip_task_index,
-                            layer_index: packed_layer_index,
-                            global_prim_id: global_prim_id,
-                            prim_address: prim_address,
-                            sub_index: 0,
-                            user_data: [ image_yuv_cpu.yuv_resource_address.0, 0 ],
-                            z_sort_index: z_sort_index,
-                        });
-                    }
-                    AlphaBatchKind::Border => {
-                        for border_segment in 0..8 {
-                            data.push(PrimitiveInstance {
-                                task_index: task_index,
-                                clip_task_index: clip_task_index,
-                                layer_index: packed_layer_index,
-                                global_prim_id: global_prim_id,
-                                prim_address: prim_address,
-                                sub_index: border_segment,
-                                user_data: [ 0, 0 ],
-                                z_sort_index: z_sort_index,
-                            });
-                        }
-                    }
-                    AlphaBatchKind::AlignedGradient => {
-                        for part_index in 0..(metadata.gpu_data_count - 1) {
-                            data.push(PrimitiveInstance {
-                                task_index: task_index,
-                                clip_task_index: clip_task_index,
-                                layer_index: packed_layer_index,
-                                global_prim_id: global_prim_id,
-                                prim_address: prim_address,
-                                sub_index: metadata.gpu_data_address.0 + part_index,
-                                user_data: [ 0, 0 ],
-                                z_sort_index: z_sort_index,
-                            });
-                        }
-                    }
-                    AlphaBatchKind::AngleGradient => {
-                        data.push(PrimitiveInstance {
-                            task_index: task_index,
-                            clip_task_index: clip_task_index,
-                            layer_index: packed_layer_index,
-                            global_prim_id: global_prim_id,
-                            prim_address: prim_address,
-                            sub_index: metadata.gpu_data_address.0,
-                            user_data: [ metadata.gpu_data_count, 0 ],
-                            z_sort_index: z_sort_index,
-                        });
-                    }
-                    AlphaBatchKind::RadialGradient => {
-                        data.push(PrimitiveInstance {
-                            task_index: task_index,
-                            clip_task_index: clip_task_index,
-                            layer_index: packed_layer_index,
-                            global_prim_id: global_prim_id,
-                            prim_address: prim_address,
-                            sub_index: metadata.gpu_data_address.0,
-                            user_data: [ metadata.gpu_data_count, 0 ],
-                            z_sort_index: z_sort_index,
-                        });
-                    }
-                    AlphaBatchKind::BoxShadow => {
-                        let cache_task_id = &metadata.render_task.as_ref().unwrap().id;
-                        let cache_task_index = render_tasks.get_task_index(cache_task_id,
-                                                                           child_pass_index);
-
-                        for rect_index in 0..metadata.gpu_data_count {
-                            data.push(PrimitiveInstance {
-                                task_index: task_index,
-                                clip_task_index: clip_task_index,
-                                layer_index: packed_layer_index,
-                                global_prim_id: global_prim_id,
-                                prim_address: prim_address,
-                                sub_index: metadata.gpu_data_address.0 + rect_index,
-                                user_data: [ cache_task_index.0 as i32, 0 ],
-                                z_sort_index: z_sort_index,
-                            });
-                        }
-                    }
-                    AlphaBatchKind::CacheImage => {
-                        // Find the render task index for the render task
-                        // that this primitive depends on. Pass it to the
-                        // shader so that it can sample from the cache texture
-                        // at the correct location.
-                        let cache_task_id = &metadata.render_task.as_ref().unwrap().id;
-                        let cache_task_index = render_tasks.get_task_index(cache_task_id,
-                                                                           child_pass_index);
-
-                        data.push(PrimitiveInstance {
-                            task_index: task_index,
-                            clip_task_index: clip_task_index,
-                            layer_index: packed_layer_index,
-                            global_prim_id: global_prim_id,
-                            prim_address: prim_address,
-                            sub_index: 0,
-                            user_data: [ cache_task_index.0 as i32, 0 ],
-                            z_sort_index: z_sort_index,
-                        });
-                    }
-                }
-            }
-        }
-    }
 }
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
     pub scroll_layer_id: ScrollLayerId,
     pub prim_index: PrimitiveIndex,
     pub border_radius: f32,
 }
@@ -514,280 +203,352 @@ impl Default for PrimitiveGeometry {
             local_rect: unsafe { mem::uninitialized() },
             local_clip_rect: unsafe { mem::uninitialized() },
         }
     }
 }
 
 struct AlphaBatchTask {
     task_id: RenderTaskId,
-    opaque_items: Vec<AlphaRenderItem>,
-    alpha_items: Vec<AlphaRenderItem>,
+    items: Vec<AlphaRenderItem>,
+}
+
+pub struct BatchList {
+    pub alpha_batches: Vec<PrimitiveBatch>,
+    pub opaque_batches: Vec<PrimitiveBatch>,
+}
+
+impl BatchList {
+    fn new() -> BatchList {
+        BatchList {
+            alpha_batches: Vec::new(),
+            opaque_batches: Vec::new(),
+        }
+    }
+
+    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(..) => {
+                (&mut self.alpha_batches, true)
+            }
+        };
+
+        let mut selected_batch_index = None;
+
+        // Composites always get added to their own batch.
+        // This is because the result of a composite can affect
+        // the input to the next composite. Perhaps we can
+        // optimize this in the future.
+        if key.kind != AlphaBatchKind::Composite {
+            'outer: for (batch_index, batch) in batches.iter()
+                                                       .enumerate()
+                                                       .rev()
+                                                       .take(10) {
+                if batch.key.is_compatible_with(key) {
+                    selected_batch_index = Some(batch_index);
+                    break;
+                }
+
+                // check for intersections
+                if check_intersections {
+                    for item_rect in &batch.item_rects {
+                        if item_rect.intersects(item_bounding_rect) {
+                            break 'outer;
+                        }
+                    }
+                }
+            }
+        }
+
+        if selected_batch_index.is_none() {
+            let new_batch = PrimitiveBatch::new(key.clone());
+            selected_batch_index = Some(batches.len());
+            batches.push(new_batch);
+        }
+
+        let batch = &mut batches[selected_batch_index.unwrap()];
+        batch.item_rects.push(*item_bounding_rect);
+
+        batch
+    }
 }
 
 /// Encapsulates the logic of building batches for items that are blended.
 pub struct AlphaBatcher {
-    pub alpha_batches: Vec<PrimitiveBatch>,
-    pub opaque_batches: Vec<PrimitiveBatch>,
+    pub batch_list: BatchList,
     tasks: Vec<AlphaBatchTask>,
 }
 
+impl AlphaRenderItem {
+    fn add_to_batch(&self,
+                    batch_list: &mut BatchList,
+                    ctx: &RenderTargetContext,
+                    render_tasks: &RenderTaskCollection,
+                    child_pass_index: RenderPassIndex,
+                    task_index: RenderTaskIndex) {
+        match *self {
+            AlphaRenderItem::Blend(stacking_context_index, src_id, filter, z) => {
+                let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
+                let key = AlphaBatchKey::new(AlphaBatchKind::Blend,
+                                             AlphaBatchKeyFlags::empty(),
+                                             BlendMode::Alpha,
+                                             BatchTextures::no_texture());
+                let src_task_index = render_tasks.get_static_task_index(&src_id);
+
+                let (filter_mode, amount) = match filter {
+                    LowLevelFilterOp::Blur(..) => (0, 0.0),
+                    LowLevelFilterOp::Contrast(amount) => (1, amount.to_f32_px()),
+                    LowLevelFilterOp::Grayscale(amount) => (2, amount.to_f32_px()),
+                    LowLevelFilterOp::HueRotate(angle) => (3, (angle as f32) / ANGLE_FLOAT_TO_FIXED),
+                    LowLevelFilterOp::Invert(amount) => (4, amount.to_f32_px()),
+                    LowLevelFilterOp::Saturate(amount) => (5, amount.to_f32_px()),
+                    LowLevelFilterOp::Sepia(amount) => (6, amount.to_f32_px()),
+                    LowLevelFilterOp::Brightness(amount) => (7, amount.to_f32_px()),
+                    LowLevelFilterOp::Opacity(amount) => (8, amount.to_f32_px()),
+                };
+
+                let amount = (amount * 65535.0).round() as i32;
+                let batch = batch_list.get_suitable_batch(&key, &stacking_context.bounding_rect);
+
+                batch.add_instance(PrimitiveInstance {
+                    global_prim_id: -1,
+                    prim_address: GpuStoreAddress(0),
+                    task_index: task_index.0 as i32,
+                    clip_task_index: -1,
+                    layer_index: -1,
+                    sub_index: filter_mode,
+                    user_data: [src_task_index.0 as i32, amount],
+                    z_sort_index: z,
+                });
+            }
+            AlphaRenderItem::HardwareComposite(stacking_context_index, src_id, composite_op, z) => {
+                let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
+                let src_task_index = render_tasks.get_static_task_index(&src_id);
+                let key = AlphaBatchKey::new(AlphaBatchKind::HardwareComposite,
+                                             AlphaBatchKeyFlags::empty(),
+                                             composite_op.to_blend_mode(),
+                                             BatchTextures::no_texture());
+                let batch = batch_list.get_suitable_batch(&key, &stacking_context.bounding_rect);
+                batch.add_instance(PrimitiveInstance {
+                    global_prim_id: -1,
+                    prim_address: GpuStoreAddress(0),
+                    task_index: task_index.0 as i32,
+                    clip_task_index: -1,
+                    layer_index: -1,
+                    sub_index: -1,
+                    user_data: [src_task_index.0 as i32, 0],
+                    z_sort_index: z,
+                });
+            }
+            AlphaRenderItem::Composite(stacking_context_index,
+                                       backdrop_id,
+                                       src_id,
+                                       mode,
+                                       z) => {
+                let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
+                let key = AlphaBatchKey::new(AlphaBatchKind::Composite,
+                                             AlphaBatchKeyFlags::empty(),
+                                             BlendMode::Alpha,
+                                             BatchTextures::no_texture());
+                let batch = batch_list.get_suitable_batch(&key, &stacking_context.bounding_rect);
+                let backdrop_task = render_tasks.get_task_index(&backdrop_id, child_pass_index);
+                let src_task_index = render_tasks.get_static_task_index(&src_id);
+                batch.add_instance(PrimitiveInstance {
+                    global_prim_id: -1,
+                    prim_address: GpuStoreAddress(0),
+                    task_index: task_index.0 as i32,
+                    clip_task_index: -1,
+                    layer_index: -1,
+                    sub_index: mode as u32 as i32,
+                    user_data: [ backdrop_task.0 as i32,
+                                 src_task_index.0 as i32 ],
+                    z_sort_index: z,
+                });
+            }
+            AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, z) => {
+                let group = &ctx.clip_scroll_group_store[clip_scroll_group_index.0];
+                let prim_metadata = ctx.prim_store.get_metadata(prim_index);
+                let transform_kind = group.xf_rect.as_ref().unwrap().kind;
+                let needs_clipping = prim_metadata.needs_clipping();
+                let mut flags = AlphaBatchKeyFlags::empty();
+                if needs_clipping {
+                    flags |= NEEDS_CLIPPING;
+                }
+                if transform_kind == TransformedRectKind::AxisAligned {
+                    flags |= AXIS_ALIGNED;
+                }
+                let textures = BatchTextures {
+                    colors: ctx.prim_store.get_color_textures(prim_metadata),
+                };
+                let item_bounding_rect = ctx.prim_store.cpu_bounding_rects[prim_index.0].as_ref().unwrap();
+                let clip_task_index = match prim_metadata.clip_task {
+                    Some(ref clip_task) => {
+                        render_tasks.get_task_index(&clip_task.id, child_pass_index)
+                    }
+                    None => {
+                        OPAQUE_TASK_INDEX
+                    }
+                }.0 as i32;
+                let packed_layer_index = ctx.clip_scroll_group_store[clip_scroll_group_index.0]
+                                            .packed_layer_index.0 as i32;
+                let global_prim_id = prim_index.0 as i32;
+                let prim_address = prim_metadata.gpu_prim_index;
+                let task_index = task_index.0 as i32;
+                let needs_blending = !prim_metadata.is_opaque ||
+                                     needs_clipping ||
+                                     transform_kind == TransformedRectKind::Complex;
+                let blend_mode = ctx.prim_store.get_blend_mode(needs_blending, prim_metadata);
+                let base_instance = PrimitiveInstance {
+                    task_index: task_index,
+                    clip_task_index: clip_task_index,
+                    layer_index: packed_layer_index,
+                    global_prim_id: global_prim_id,
+                    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));
+                        }
+                    }
+                    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 => {
+                        let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
+
+                        let batch_kind = match image_cpu.color_texture_id {
+                            SourceTexture::External(ext_image) => {
+                                match ext_image.image_type {
+                                    ExternalImageType::Texture2DHandle => AlphaBatchKind::Image,
+                                    ExternalImageType::TextureRectHandle => AlphaBatchKind::ImageRect,
+                                    _ => {
+                                        panic!("Non-texture handle type should be handled in other way.");
+                                    }
+                                }
+                            }
+                            _ => {
+                                AlphaBatchKind::Image
+                            }
+                        };
+
+                        let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                        batch.add_instance(base_instance.build(0, image_cpu.resource_address.0, 0));
+                    }
+                    PrimitiveKind::TextRun => {
+                        let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
+                        let batch_kind = if text_cpu.blur_radius.0 == 0 {
+                            AlphaBatchKind::TextRun
+                        } else {
+                            // Select a generic primitive shader that can blit the
+                            // results of the cached text blur to the framebuffer,
+                            // applying tile clipping etc.
+                            AlphaBatchKind::CacheImage
+                        };
+                        let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+
+                        for glyph_index in 0..prim_metadata.gpu_data_count {
+                            batch.add_instance(base_instance.build(prim_metadata.gpu_data_address.0 + glyph_index,
+                                                                   text_cpu.resource_address.0 + glyph_index,
+                                                                   0));
+                        }
+                    }
+                    PrimitiveKind::AlignedGradient => {
+                        let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                        for part_index in 0..(prim_metadata.gpu_data_count - 1) {
+                            batch.add_instance(base_instance.build(prim_metadata.gpu_data_address.0 + part_index, 0, 0));
+                        }
+                    }
+                    PrimitiveKind::AngleGradient => {
+                        let key = AlphaBatchKey::new(AlphaBatchKind::AngleGradient, flags, blend_mode, textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                        batch.add_instance(base_instance.build(prim_metadata.gpu_data_address.0,
+                                                               prim_metadata.gpu_data_count,
+                                                               0));
+                    }
+                    PrimitiveKind::RadialGradient => {
+                        let key = AlphaBatchKey::new(AlphaBatchKind::RadialGradient, flags, blend_mode, textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+                        batch.add_instance(base_instance.build(prim_metadata.gpu_data_address.0,
+                                                               prim_metadata.gpu_data_count,
+                                                               0));
+                    }
+                    PrimitiveKind::YuvImage => {
+                        let image_yuv_cpu = &ctx.prim_store.cpu_yuv_images[prim_metadata.cpu_prim_index.0];
+                        let key = AlphaBatchKey::new(AlphaBatchKind::YuvImage, flags, blend_mode, textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+
+                        batch.add_instance(base_instance.build(0,
+                                                               image_yuv_cpu.yuv_resource_address.0,
+                                                               0));
+                    }
+                    PrimitiveKind::BoxShadow => {
+                        let cache_task_id = &prim_metadata.render_task.as_ref().unwrap().id;
+                        let cache_task_index = render_tasks.get_task_index(cache_task_id,
+                                                                           child_pass_index);
+
+                        let key = AlphaBatchKey::new(AlphaBatchKind::BoxShadow, flags, blend_mode, textures);
+                        let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
+
+                        for rect_index in 0..prim_metadata.gpu_data_count {
+                            batch.add_instance(base_instance.build(prim_metadata.gpu_data_address.0 + rect_index,
+                                                                   cache_task_index.0 as i32,
+                                                                   0));
+                        }
+                    }
+
+                }
+            }
+        }
+    }
+}
+
 impl AlphaBatcher {
     fn new() -> AlphaBatcher {
         AlphaBatcher {
-            alpha_batches: Vec::new(),
-            opaque_batches: Vec::new(),
             tasks: Vec::new(),
+            batch_list: BatchList::new(),
         }
     }
 
     fn add_task(&mut self, task: AlphaBatchTask) {
         self.tasks.push(task);
     }
 
     fn build(&mut self,
              ctx: &RenderTargetContext,
              render_tasks: &RenderTaskCollection,
              child_pass_index: RenderPassIndex) {
-        let mut alpha_batches: Vec<PrimitiveBatch> = vec![];
-        let mut opaque_batches: Vec<PrimitiveBatch> = vec![];
-
-        for task in &mut self.tasks {
+        for task in &self.tasks {
             let task_index = render_tasks.get_static_task_index(&task.task_id);
-            let mut existing_opaque_batch_index = 0;
-
-            for item in &task.alpha_items {
-                let (batch_key, item_bounding_rect) = match item {
-                    &AlphaRenderItem::Blend(stacking_context_index, ..) => {
-                        let stacking_context =
-                            &ctx.stacking_context_store[stacking_context_index.0];
-                        (AlphaBatchKey::new(AlphaBatchKind::Blend,
-                                            AlphaBatchKeyFlags::empty(),
-                                            BlendMode::Alpha,
-                                            BatchTextures::no_texture()),
-                         &stacking_context.bounding_rect)
-                    }
-                    &AlphaRenderItem::HardwareComposite(stacking_context_index, _, composite_op, ..) => {
-                        let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
-                        (AlphaBatchKey::new(AlphaBatchKind::HardwareComposite,
-                                            AlphaBatchKeyFlags::empty(),
-                                            composite_op.to_blend_mode(),
-                                            BatchTextures::no_texture()),
-                         &stacking_context.bounding_rect)
-                    }
-                    &AlphaRenderItem::Composite(stacking_context_index,
-                                                backdrop_id,
-                                                src_id,
-                                                info,
-                                                z) => {
-                        // Composites always get added to their own batch.
-                        // This is because the result of a composite can affect
-                        // the input to the next composite. Perhaps we can
-                        // optimize this in the future.
-                        let batch = PrimitiveBatch::new_composite(stacking_context_index,
-                                                                  task_index,
-                                                                  render_tasks.get_task_index(&backdrop_id, child_pass_index),
-                                                                  render_tasks.get_static_task_index(&src_id),
-                                                                  info,
-                                                                  z);
-                        alpha_batches.push(batch);
-                        continue;
-                    }
-                    &AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, _) => {
-                        let group = &ctx.clip_scroll_group_store[clip_scroll_group_index.0];
-                        let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                        let transform_kind = group.xf_rect.as_ref().unwrap().kind;
-                        let needs_clipping = prim_metadata.clip_task.is_some();
-                        let needs_blending = transform_kind == TransformedRectKind::Complex ||
-                                             !prim_metadata.is_opaque ||
-                                             needs_clipping;
-                        let blend_mode = ctx.prim_store.get_blend_mode(needs_blending, prim_metadata);
-                        let needs_clipping_flag = if needs_clipping {
-                            NEEDS_CLIPPING
-                        } else {
-                            AlphaBatchKeyFlags::empty()
-                        };
-                        let flags = match transform_kind {
-                            TransformedRectKind::AxisAligned => AXIS_ALIGNED | needs_clipping_flag,
-                            _ => needs_clipping_flag,
-                        };
-                        let batch_kind = ctx.prim_store.get_batch_kind(prim_metadata);
-
-                        let textures = BatchTextures {
-                            colors: ctx.prim_store.get_color_textures(prim_metadata),
-                        };
-
-                        (AlphaBatchKey::new(batch_kind,
-                                            flags,
-                                            blend_mode,
-                                            textures),
-                         ctx.prim_store.cpu_bounding_rects[prim_index.0].as_ref().unwrap())
-                    }
-                };
-
-                let mut alpha_batch_index = None;
-                'outer: for (batch_index, batch) in alpha_batches.iter()
-                                                         .enumerate()
-                                                         .rev()
-                                                         .take(10) {
-                    if batch.key.is_compatible_with(&batch_key) {
-                        alpha_batch_index = Some(batch_index);
-                        break;
-                    }
-
-                    // check for intersections
-                    for item in &batch.items {
-                        let intersects = match *item {
-                            PrimitiveBatchItem::StackingContext(stacking_context_index) => {
-                                let stacking_context =
-                                    &ctx.stacking_context_store[stacking_context_index.0];
-                                stacking_context.bounding_rect.intersects(item_bounding_rect)
-                            }
-                            PrimitiveBatchItem::Primitive(prim_index) => {
-                                let bounding_rect = &ctx.prim_store.cpu_bounding_rects[prim_index.0];
-                                bounding_rect.as_ref().unwrap().intersects(item_bounding_rect)
-                            }
-                        };
-
-                        if intersects {
-                            break 'outer;
-                        }
-                    }
-                }
 
-                if alpha_batch_index.is_none() {
-                    let new_batch = match item {
-                        &AlphaRenderItem::Composite(..) => unreachable!(),
-                        &AlphaRenderItem::HardwareComposite(..) => {
-                            PrimitiveBatch::new_instances(AlphaBatchKind::HardwareComposite, batch_key)
-                        }
-                        &AlphaRenderItem::Blend(..) => {
-                            PrimitiveBatch::new_instances(AlphaBatchKind::Blend, batch_key)
-                        }
-                        &AlphaRenderItem::Primitive(_, prim_index, _) => {
-                            let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                            let batch_kind = ctx.prim_store.get_batch_kind(prim_metadata);
-                            PrimitiveBatch::new_instances(batch_kind, batch_key)
-                        }
-                    };
-                    alpha_batch_index = Some(alpha_batches.len());
-                    alpha_batches.push(new_batch);
-                }
-
-                let batch = &mut alpha_batches[alpha_batch_index.unwrap()];
-                match item {
-                    &AlphaRenderItem::Composite(..) => unreachable!(),
-                    &AlphaRenderItem::Blend(stacking_context_index, src_id, info, z) => {
-                        ctx.prim_store.add_blend_to_batch(stacking_context_index,
-                                                          batch,
-                                                          task_index,
-                                                          render_tasks.get_static_task_index(&src_id),
-                                                          info,
-                                                          z);
-                    }
-                    &AlphaRenderItem::HardwareComposite(stacking_context_index, src_id, _, z) => {
-                        ctx.prim_store.add_hardware_composite_to_batch(
-                            stacking_context_index,
-                            batch,
-                            task_index,
-                            render_tasks.get_static_task_index(&src_id),
-                            z);
-                    }
-                    &AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, z) => {
-                        let packed_layer = ctx.clip_scroll_group_store[clip_scroll_group_index.0]
-                                              .packed_layer_index;
-                        ctx.prim_store.add_prim_to_batch(prim_index,
-                                                         batch,
-                                                         packed_layer,
-                                                         task_index,
-                                                         render_tasks,
-                                                         child_pass_index,
-                                                         z);
-                    }
-                }
-            }
-
-            for item in task.opaque_items.iter().rev() {
-                let batch_key = match item {
-                    &AlphaRenderItem::Composite(..) => unreachable!(),
-                    &AlphaRenderItem::Blend(..) => unreachable!(),
-                    &AlphaRenderItem::HardwareComposite(..) => unreachable!(),
-                    &AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, _) => {
-                        let group = &ctx.clip_scroll_group_store[clip_scroll_group_index.0];
-                        let transform_kind = group.xf_rect.as_ref().unwrap().kind;
-                        let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                        let needs_clipping = prim_metadata.clip_task.is_some();
-                        let needs_blending = transform_kind == TransformedRectKind::Complex ||
-                                             !prim_metadata.is_opaque ||
-                                             needs_clipping;
-                        let blend_mode = ctx.prim_store.get_blend_mode(needs_blending, prim_metadata);
-                        let needs_clipping_flag = if needs_clipping {
-                            NEEDS_CLIPPING
-                        } else {
-                            AlphaBatchKeyFlags::empty()
-                        };
-                        let flags = match transform_kind {
-                            TransformedRectKind::AxisAligned => AXIS_ALIGNED | needs_clipping_flag,
-                            _ => needs_clipping_flag,
-                        };
-                        let batch_kind = ctx.prim_store.get_batch_kind(prim_metadata);
-
-                        let textures = BatchTextures {
-                            colors: ctx.prim_store.get_color_textures(prim_metadata),
-                        };
-
-                        AlphaBatchKey::new(batch_kind,
-                                           flags,
-                                           blend_mode,
-                                           textures)
-                    }
-                };
-
-                while existing_opaque_batch_index < opaque_batches.len() &&
-                        !opaque_batches[existing_opaque_batch_index].key.is_compatible_with(&batch_key) {
-                    existing_opaque_batch_index += 1
-                }
-
-                if existing_opaque_batch_index == opaque_batches.len() {
-                    let new_batch = match item {
-                        &AlphaRenderItem::Composite(..) => unreachable!(),
-                        &AlphaRenderItem::Blend(..) => unreachable!(),
-                        &AlphaRenderItem::HardwareComposite(..) => unreachable!(),
-                        &AlphaRenderItem::Primitive(_, prim_index, _) => {
-                            let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                            let batch_kind = ctx.prim_store.get_batch_kind(prim_metadata);
-                            PrimitiveBatch::new_instances(batch_kind, batch_key)
-                        }
-                    };
-                    opaque_batches.push(new_batch)
-                }
-
-                let batch = &mut opaque_batches[existing_opaque_batch_index];
-                match item {
-                    &AlphaRenderItem::Composite(..) => unreachable!(),
-                    &AlphaRenderItem::Blend(..) => unreachable!(),
-                    &AlphaRenderItem::HardwareComposite(..) => unreachable!(),
-                    &AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, z) => {
-                        let packed_layer_index =
-                            ctx.clip_scroll_group_store[clip_scroll_group_index.0]
-                               .packed_layer_index;
-                        ctx.prim_store.add_prim_to_batch(prim_index,
-                                                         batch,
-                                                         packed_layer_index,
-                                                         task_index,
-                                                         render_tasks,
-                                                         child_pass_index,
-                                                         z);
-                    }
-                }
+            for item in &task.items {
+                item.add_to_batch(&mut self.batch_list,
+                                  ctx,
+                                  render_tasks,
+                                  child_pass_index,
+                                  task_index);
             }
         }
-
-        self.alpha_batches.extend(alpha_batches.into_iter());
-        self.opaque_batches.extend(opaque_batches.into_iter());
     }
 }
 
 /// Batcher managing draw calls into the clip mask (in the RT cache).
 #[derive(Debug)]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<CacheClipInstance>,
@@ -1053,18 +814,17 @@ impl RenderTarget for ColorRenderTarget 
                 task: RenderTask,
                 ctx: &RenderTargetContext,
                 render_tasks: &RenderTaskCollection,
                 pass_index: RenderPassIndex) {
         match task.kind {
             RenderTaskKind::Alpha(info) => {
                 self.alpha_batcher.add_task(AlphaBatchTask {
                     task_id: task.id,
-                    opaque_items: info.opaque_items,
-                    alpha_items: info.alpha_items,
+                    items: info.items,
                 });
 
                 if info.isolate_clear {
                     let location = match task.location {
                         RenderTaskLocation::Dynamic(origin, size) => {
                             DeviceIntRect::new(origin.unwrap().0, size)
                         }
                         RenderTaskLocation::Fixed => panic!()
@@ -1431,90 +1191,47 @@ pub struct PrimitiveInstance {
     pub task_index: i32,
     clip_task_index: i32,
     layer_index: i32,
     sub_index: i32,
     z_sort_index: i32,
     pub user_data: [i32; 2],
 }
 
-#[derive(Debug)]
-pub enum PrimitiveBatchData {
-    Instances(Vec<PrimitiveInstance>),
-    Composite(PrimitiveInstance),
-}
-
-#[derive(Debug)]
-pub enum PrimitiveBatchItem {
-    Primitive(PrimitiveIndex),
-    StackingContext(StackingContextIndex),
+impl PrimitiveInstance {
+    pub fn build(&self,
+                 sub_index: i32,
+                 user_data0: i32,
+                 user_data1: i32) -> PrimitiveInstance {
+        PrimitiveInstance {
+            sub_index: sub_index,
+            user_data: [user_data0, user_data1],
+            ..*self
+        }
+    }
 }
 
 #[derive(Debug)]
 pub struct PrimitiveBatch {
     pub key: AlphaBatchKey,
-    pub data: PrimitiveBatchData,
-    pub items: Vec<PrimitiveBatchItem>,
+    pub instances: Vec<PrimitiveInstance>,
+    pub item_rects: Vec<DeviceIntRect>,
 }
 
 impl PrimitiveBatch {
-    fn new_instances(batch_kind: AlphaBatchKind, key: AlphaBatchKey) -> PrimitiveBatch {
-        let data = match batch_kind {
-            AlphaBatchKind::Rectangle |
-            AlphaBatchKind::TextRun |
-            AlphaBatchKind::Image |
-            AlphaBatchKind::ImageRect |
-            AlphaBatchKind::YuvImage |
-            AlphaBatchKind::Border |
-            AlphaBatchKind::AlignedGradient |
-            AlphaBatchKind::AngleGradient |
-            AlphaBatchKind::RadialGradient |
-            AlphaBatchKind::BoxShadow |
-            AlphaBatchKind::Blend |
-            AlphaBatchKind::HardwareComposite |
-            AlphaBatchKind::CacheImage => {
-                PrimitiveBatchData::Instances(Vec::new())
-            }
-            AlphaBatchKind::Composite => unreachable!(),
-        };
-
+    fn new(key: AlphaBatchKey) -> PrimitiveBatch {
         PrimitiveBatch {
             key: key,
-            data: data,
-            items: Vec::new(),
+            instances: Vec::new(),
+            item_rects: Vec::new(),
         }
     }
 
-    fn new_composite(stacking_context_index: StackingContextIndex,
-                     task_index: RenderTaskIndex,
-                     backdrop_task: RenderTaskIndex,
-                     src_task_index: RenderTaskIndex,
-                     mode: MixBlendMode,
-                     z_sort_index: i32) -> PrimitiveBatch {
-        let data = PrimitiveBatchData::Composite(PrimitiveInstance {
-            global_prim_id: -1,
-            prim_address: GpuStoreAddress(0),
-            task_index: task_index.0 as i32,
-            clip_task_index: -1,
-            layer_index: -1,
-            sub_index: mode as u32 as i32,
-            user_data: [ backdrop_task.0 as i32,
-                         src_task_index.0 as i32 ],
-            z_sort_index: z_sort_index,
-        });
-        let key = AlphaBatchKey::new(AlphaBatchKind::Composite,
-                                     AlphaBatchKeyFlags::empty(),
-                                     BlendMode::Alpha,
-                                     BatchTextures::no_texture());
-
-        PrimitiveBatch {
-            key: key,
-            data: data,
-            items: vec![PrimitiveBatchItem::StackingContext(stacking_context_index)],
-        }
+    fn add_instance(&mut self, instance: PrimitiveInstance) {
+        self.instances.push(instance);
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct PackedLayerIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct StackingContextIndex(pub usize);
@@ -1547,25 +1264,26 @@ pub struct StackingContext {
     // based on the size and position of all children and how they are clipped.
     pub is_visible: bool,
 }
 
 impl StackingContext {
     pub fn new(pipeline_id: PipelineId,
                reference_frame_offset: LayerPoint,
                is_page_root: bool,
+               transform_style: TransformStyle,
                composite_ops: CompositeOps)
                -> StackingContext {
         StackingContext {
             pipeline_id: pipeline_id,
             reference_frame_offset: reference_frame_offset,
             bounding_rect: DeviceIntRect::zero(),
             composite_ops: composite_ops,
             clip_scroll_groups: Vec::new(),
-            should_isolate: false,
+            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 {
         // Currently there is only one scrolled stacking context per context,
         // but eventually this will be selected from the vector based on the
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -1,16 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+use std::f32::consts::{FRAC_1_SQRT_2};
 use euclid::{Point2D, Rect, Size2D};
 use euclid::{TypedRect, TypedPoint2D, TypedSize2D, TypedPoint4D, TypedMatrix4D};
 use webrender_traits::{DeviceIntRect, DeviceIntPoint, DeviceIntSize};
 use webrender_traits::{LayerRect, WorldPoint4D, LayerPoint4D, LayerToWorldTransform};
+use webrender_traits::{BorderRadius, ComplexClipRegion, LayoutRect};
 use num_traits::Zero;
 
 // TODO: Implement these in euclid!
 pub trait MatrixHelpers<Src, Dst> {
     fn transform_point_and_perspective_project(&self, point: &TypedPoint4D<f32, Src>) -> TypedPoint2D<f32, Dst>;
     fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst>;
 
     /// Returns true if this matrix transforms an axis-aligned 2D rectangle to another axis-aligned
@@ -264,8 +266,55 @@ impl TransformedRect {
         }*/
     }
 }
 
 #[inline(always)]
 pub fn pack_as_float(value: u32) -> f32 {
     value as f32 + 0.5
 }
+
+
+pub trait ComplexClipRegionHelpers {
+    /// Return an aligned rectangle that is inside the clip region and doesn't intersect
+    /// any of the bounding rectangles of the rounded corners.
+    fn get_inner_rect_safe(&self) -> Option<LayoutRect>;
+    /// Return the approximately largest aligned rectangle that is fully inside
+    /// the provided clip region.
+    fn get_inner_rect_full(&self) -> Option<LayoutRect>;
+}
+
+impl ComplexClipRegionHelpers for ComplexClipRegion {
+    fn get_inner_rect_safe(&self) -> Option<LayoutRect> {
+        // value of `k==1.0` is used for extraction of the corner rectangles
+        // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
+        extract_inner_rect_impl(&self.rect, &self.radii, 1.0)
+    }
+
+    fn get_inner_rect_full(&self) -> Option<LayoutRect> {
+        // this `k` optimal for a simple case of all border radii being equal
+        let k = 1.0 - 0.5 * FRAC_1_SQRT_2; // could be nicely approximated to `0.3`
+        extract_inner_rect_impl(&self.rect, &self.radii, k)
+    }
+}
+
+#[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
+
+    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);
+
+    if xl <= xr && yt <= yb {
+        Some(TypedRect::new(TypedPoint2D::new(xl, 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.31.0"
+version = "0.32.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
@@ -62,44 +62,44 @@ pub enum ApiMsg {
     /// to forward gecko-specific messages to the render thread preserving the ordering
     /// within the other messages.
     ExternalEvent(ExternalEvent),
     ShutDown,
 }
 
 impl fmt::Debug for ApiMsg {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            &ApiMsg::AddRawFont(..) => { write!(f, "ApiMsg::AddRawFont") }
-            &ApiMsg::AddNativeFont(..) => { write!(f, "ApiMsg::AddNativeFont") }
-            &ApiMsg::DeleteFont(..) => { write!(f, "ApiMsg::DeleteFont") }
-            &ApiMsg::GetGlyphDimensions(..) => { write!(f, "ApiMsg::GetGlyphDimensions") }
-            &ApiMsg::AddImage(..) => { write!(f, "ApiMsg::AddImage") }
-            &ApiMsg::UpdateImage(..) => { write!(f, "ApiMsg::UpdateImage") }
-            &ApiMsg::DeleteImage(..) => { write!(f, "ApiMsg::DeleteImage") }
-            &ApiMsg::CloneApi(..) => { write!(f, "ApiMsg::CloneApi") }
-            &ApiMsg::SetDisplayList(..) => { write!(f, "ApiMsg::SetDisplayList") }
-            &ApiMsg::SetRootPipeline(..) => { write!(f, "ApiMsg::SetRootPipeline") }
-            &ApiMsg::Scroll(..) => { write!(f, "ApiMsg::Scroll") }
-            &ApiMsg::ScrollLayerWithId(..) => { write!(f, "ApiMsg::ScrollLayerWithId") }
-            &ApiMsg::TickScrollingBounce => { write!(f, "ApiMsg::TickScrollingBounce") }
-            &ApiMsg::TranslatePointToLayerSpace(..) => { write!(f, "ApiMsg::TranslatePointToLayerSpace") }
-            &ApiMsg::GetScrollLayerState(..) => { write!(f, "ApiMsg::GetScrollLayerState") }
-            &ApiMsg::RequestWebGLContext(..) => { write!(f, "ApiMsg::RequestWebGLContext") }
-            &ApiMsg::ResizeWebGLContext(..) => { write!(f, "ApiMsg::ResizeWebGLContext") }
-            &ApiMsg::WebGLCommand(..) => { write!(f, "ApiMsg::WebGLCommand") }
-            &ApiMsg::GenerateFrame(..) => { write!(f, "ApiMsg::GenerateFrame") }
-            &ApiMsg::VRCompositorCommand(..) => { write!(f, "ApiMsg::VRCompositorCommand") }
-            &ApiMsg::ExternalEvent(..) => { write!(f, "ApiMsg::ExternalEvent") }
-            &ApiMsg::ShutDown => { write!(f, "ApiMsg::ShutDown") }
-            &ApiMsg::SetPageZoom(..) => { write!(f, "ApiMsg::SetPageZoom") }
-            &ApiMsg::SetPinchZoom(..) => { write!(f, "ApiMsg::SetPinchZoom") }
-            &ApiMsg::SetPan(..) => { write!(f, "ApiMsg::SetPan") }
-            &ApiMsg::SetWindowParameters(..) => { write!(f, "ApiMsg::SetWindowParameters") }
-        }
+        f.write_str(match *self {
+            ApiMsg::AddRawFont(..) => "ApiMsg::AddRawFont",
+            ApiMsg::AddNativeFont(..) => "ApiMsg::AddNativeFont",
+            ApiMsg::DeleteFont(..) => "ApiMsg::DeleteFont",
+            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::TickScrollingBounce => "ApiMsg::TickScrollingBounce",
+            ApiMsg::TranslatePointToLayerSpace(..) => "ApiMsg::TranslatePointToLayerSpace",
+            ApiMsg::GetScrollLayerState(..) => "ApiMsg::GetScrollLayerState",
+            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",
+            ApiMsg::SetPinchZoom(..) => "ApiMsg::SetPinchZoom",
+            ApiMsg::SetPan(..) => "ApiMsg::SetPan",
+            ApiMsg::SetWindowParameters(..) => "ApiMsg::SetWindowParameters",
+        })
     }
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct Epoch(pub u32);
 
 #[cfg(not(feature = "webgl"))]
@@ -260,22 +260,24 @@ impl RenderApi {
         let msg = ApiMsg::DeleteImage(key);
         self.api_sender.send(msg).unwrap();
     }
 
     /// Sets the root pipeline.
     ///
     /// # Examples
     ///
-    /// ```ignore
-    /// let (mut renderer, sender) = webrender::renderer::Renderer::new(opts);
+    /// ```
+    /// # use webrender_traits::{PipelineId, RenderApiSender};
+    /// # fn example(sender: RenderApiSender) {
     /// let api = sender.create_api();
-    /// ...
-    /// let pipeline_id = PipelineId(0,0);
+    /// // ...
+    /// let pipeline_id = PipelineId(0, 0);
     /// api.set_root_pipeline(pipeline_id);
+    /// # }
     /// ```
     pub fn set_root_pipeline(&self, pipeline_id: PipelineId) {
         let msg = ApiMsg::SetRootPipeline(pipeline_id);
         self.api_sender.send(msg).unwrap();
     }
 
     /// Supplies a new frame to WebRender.
     ///
@@ -284,18 +286,18 @@ impl RenderApi {
     /// [new_frame_ready()][notifier] gets called.
     ///
     /// Note: Scrolling doesn't require an own Frame.
     ///
     /// Arguments:
     ///
     /// * `background_color`: The background color of this pipeline.
     /// * `epoch`: The unique Frame ID, monotonically increasing.
+    /// * `viewport_size`: The size of the viewport for this frame.
     /// * `pipeline_id`: The ID of the pipeline that is supplying this display list.
-    /// * `viewport_size`: The size of the viewport for this frame.
     /// * `display_list`: The root Display list used in this frame.
     /// * `auxiliary_lists`: Various items that the display lists and stacking contexts reference.
     /// * `preserve_frame_state`: If a previous frame exists which matches this pipeline
     ///                           id, this setting determines if frame state (such as scrolling
     ///                           position) should be preserved for this new display list.
     ///
     /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
     pub fn set_display_list(&self,
@@ -320,17 +322,17 @@ impl RenderApi {
             pipeline_id: pipeline_id,
             display_list_data: dl_data,
             auxiliary_lists_data: aux_data
         }).unwrap();
     }
 
     /// Scrolls the scrolling layer under the `cursor`
     ///
-    /// Webrender looks for the layer closest to the user
+    /// 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);
@@ -552,17 +554,17 @@ impl<T> From<PropertyBindingKey<T>> for 
 /// The current value of an animated property. This is
 /// supplied by the calling code.
 #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
 pub struct PropertyValue<T> {
     pub key: PropertyBindingKey<T>,
     pub value: T,
 }
 
-/// When using generate_frame(), a list of PropertyValue structures
+/// When using `generate_frame()`, a list of `PropertyValue` structures
 /// can optionally be supplied to provide the current value of any
 /// animated properties.
 #[derive(Clone, Deserialize, Serialize, Debug)]
 pub struct DynamicProperties {
     pub transforms: Vec<PropertyValue<LayoutTransform>>,
     pub floats: Vec<PropertyValue<f32>>,
 }
 
@@ -585,12 +587,12 @@ pub trait VRCompositorHandler: Send {
 
 pub trait RenderNotifier: Send {
     fn new_frame_ready(&mut self);
     fn new_scroll_frame_ready(&mut self, composite_needed: bool);
     fn external_event(&mut self, _evt: ExternalEvent) { unimplemented!() }
     fn shut_down(&mut self) {}
 }
 
-// Trait to allow dispatching functions to a specific thread or event loop.
+/// Trait to allow dispatching functions to a specific thread or event loop.
 pub trait RenderDispatcher: Send {
     fn dispatch(&self, Box<Fn() + Send>);
 }
--- a/gfx/webrender_traits/src/channel.rs
+++ b/gfx/webrender_traits/src/channel.rs
@@ -64,18 +64,18 @@ impl Payload {
             pipeline_id: pipeline_id,
             display_list_data: built_display_list_data,
             auxiliary_lists_data: auxiliary_lists_data,
         }
     }
 }
 
 
-/// A helper to handle the interface difference between IpcBytesSender and
-/// Sender<Vec<u8>>.
+/// A helper to handle the interface difference between `IpcBytesSender`
+/// and `Sender<Vec<u8>>`.
 pub trait PayloadSenderHelperMethods {
     fn send_payload(&self, data: Payload) -> Result<(), Error>;
 }
 
 pub trait PayloadReceiverHelperMethods {
     fn recv_payload(&self) -> Result<Payload, Error>;
 }
 
--- a/gfx/webrender_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -38,17 +38,16 @@ 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 content_size: LayoutSize,
     pub id: ScrollLayerId,
     pub parent_id: ScrollLayerId,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
@@ -206,16 +205,18 @@ pub struct Gradient {
     pub end_point: LayoutPoint,
     pub stops: ItemRange,
     pub extend_mode: ExtendMode,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct GradientDisplayItem {
     pub gradient: Gradient,
+    pub tile_size: LayoutSize,
+    pub tile_spacing: LayoutSize,
 }
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct GradientStop {
     pub offset: f32,
     pub color: ColorF,
 }
@@ -230,44 +231,54 @@ pub struct RadialGradient {
     pub ratio_xy: f32,
     pub stops: ItemRange,
     pub extend_mode: ExtendMode,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RadialGradientDisplayItem {
     pub gradient: RadialGradient,
+    pub tile_size: LayoutSize,
+    pub tile_spacing: LayoutSize,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PushStackingContextDisplayItem {
     pub stacking_context: StackingContext,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StackingContext {
     pub scroll_policy: ScrollPolicy,
     pub z_index: i32,
     pub transform: Option<PropertyBinding<LayoutTransform>>,
+    pub transform_style: TransformStyle,
     pub perspective: Option<LayoutTransform>,
     pub mix_blend_mode: MixBlendMode,
     pub filters: ItemRange,
 }
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
 pub enum ScrollPolicy {
     Scrollable  = 0,
     Fixed       = 1,
 }
 
 known_heap_size!(0, ScrollPolicy);
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
+pub enum TransformStyle {
+    Flat,
+    Preserve3D,
+}
+
+#[repr(u32)]
+#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum MixBlendMode {
     Normal      = 0,
     Multiply    = 1,
     Screen      = 2,
     Overlay     = 3,
     Darken      = 4,
     Lighten     = 5,
     ColorDodge  = 6,
@@ -353,25 +364,27 @@ pub struct ComplexClipRegion {
     /// Border radii of this rectangle.
     pub radii: BorderRadius,
 }
 
 impl StackingContext {
     pub fn new(scroll_policy: ScrollPolicy,
                z_index: i32,
                transform: Option<PropertyBinding<LayoutTransform>>,
+               transform_style: TransformStyle,
                perspective: Option<LayoutTransform>,
                mix_blend_mode: MixBlendMode,
                filters: Vec<FilterOp>,
                auxiliary_lists_builder: &mut AuxiliaryListsBuilder)
                -> StackingContext {
         StackingContext {
             scroll_policy: scroll_policy,
             z_index: z_index,
             transform: transform,
+            transform_style: transform_style,
             perspective: perspective,
             mix_blend_mode: mix_blend_mode,
             filters: auxiliary_lists_builder.add_filters(&filters),
         }
     }
 }
 
 impl BorderRadius {
@@ -482,34 +495,16 @@ impl ColorF {
 impl ComplexClipRegion {
     /// Create a new complex clip region.
     pub fn new(rect: LayoutRect, radii: BorderRadius) -> ComplexClipRegion {
         ComplexClipRegion {
             rect: rect,
             radii: radii,
         }
     }
-
-    //TODO: move to `util` module?
-    /// Return an aligned rectangle that is fully inside the clip region.
-    pub fn get_inner_rect(&self) -> Option<LayoutRect> {
-        let xl = self.rect.origin.x +
-            self.radii.top_left.width.max(self.radii.bottom_left.width);
-        let xr = self.rect.origin.x + self.rect.size.width -
-            self.radii.top_right.width.max(self.radii.bottom_right.width);
-        let yt = self.rect.origin.y +
-            self.radii.top_left.height.max(self.radii.top_right.height);
-        let yb = self.rect.origin.y + self.rect.size.height -
-            self.radii.bottom_left.height.max(self.radii.bottom_right.height);
-        if xl <= xr && yt <= yb {
-            Some(LayoutRect::new(LayoutPoint::new(xl, yt), LayoutSize::new(xr-xl, yb-yt)))
-        } else {
-            None
-        }
-    }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ScrollLayerId {
     Clip(u64, PipelineId),
     ClipExternalId(u64, PipelineId),
     ReferenceFrame(u64, PipelineId),
 }
@@ -524,18 +519,18 @@ impl ScrollLayerId {
     }
 
     pub fn new(id: u64, pipeline_id: PipelineId) -> ScrollLayerId {
         ScrollLayerId::ClipExternalId(id, pipeline_id)
     }
 
     pub fn pipeline_id(&self) -> PipelineId {
         match *self {
-            ScrollLayerId::Clip(_, pipeline_id) => pipeline_id,
-            ScrollLayerId::ClipExternalId(_, pipeline_id) => pipeline_id,
+            ScrollLayerId::Clip(_, pipeline_id) |
+            ScrollLayerId::ClipExternalId(_, pipeline_id) |
             ScrollLayerId::ReferenceFrame(_, pipeline_id) => pipeline_id,
         }
     }
 
     pub fn is_reference_frame(&self) -> bool {
         match *self {
             ScrollLayerId::ReferenceFrame(..) => true,
             _ => false,
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -4,21 +4,21 @@
 
 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 {IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering, ItemRange};
-use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, MixBlendMode, PipelineId};
+use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
+use {TransformStyle, MixBlendMode, PipelineId};
 use {PropertyBinding, PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
 use {RectangleDisplayItem, ScrollLayerId, ScrollPolicy, SpecificDisplayItem, StackingContext};
-use {TextDisplayItem, WebGLContextId, WebGLDisplayItem, YuvColorSpace};
-use YuvImageDisplayItem;
+use {TextDisplayItem, 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,
 }
@@ -75,17 +75,17 @@ impl BuiltDisplayList {
     pub fn data(&self) -> &[u8] {
         &self.data[..]
     }
 
     pub fn descriptor(&self) -> &BuiltDisplayListDescriptor {
         &self.descriptor
     }
 
-    pub fn all_display_items<'a>(&'a self) -> &'a [DisplayItem] {
+    pub fn all_display_items(&self) -> &[DisplayItem] {
         unsafe {
             convert_blob_to_pod(&self.data[0..self.descriptor.display_list_items_size])
         }
     }
 
     pub fn into_display_items(self) -> Vec<DisplayItem> {
         unsafe {
             convert_vec_blob_to_pod(self.data)
@@ -390,89 +390,98 @@ impl DisplayListBuilder {
         });
 
         self.push_item(item, rect, clip);
     }
 
     pub fn push_gradient(&mut self,
                          rect: LayoutRect,
                          clip: ClipRegion,
-                         gradient: Gradient) {
+                         gradient: Gradient,
+                         tile_size: LayoutSize,
+                         tile_spacing: LayoutSize) {
         let item = SpecificDisplayItem::Gradient(GradientDisplayItem {
             gradient: gradient,
+            tile_size: tile_size,
+            tile_spacing: tile_spacing,
         });
 
         self.push_item(item, rect, clip);
     }
 
     pub fn push_radial_gradient(&mut self,
                                 rect: LayoutRect,
                                 clip: ClipRegion,
-                                gradient: RadialGradient) {
+                                gradient: RadialGradient,
+                                tile_size: LayoutSize,
+                                tile_spacing: LayoutSize) {
         let item = SpecificDisplayItem::RadialGradient(RadialGradientDisplayItem {
             gradient: gradient,
+            tile_size: tile_size,
+            tile_spacing: tile_spacing,
         });
 
         self.push_item(item, rect, clip);
     }
 
     pub fn push_stacking_context(&mut self,
                                  scroll_policy: ScrollPolicy,
                                  bounds: LayoutRect,
                                  z_index: i32,
                                  transform: Option<PropertyBinding<LayoutTransform>>,
+                                 transform_style: TransformStyle,
                                  perspective: Option<LayoutTransform>,
                                  mix_blend_mode: MixBlendMode,
                                  filters: Vec<FilterOp>) {
         let item = SpecificDisplayItem::PushStackingContext(PushStackingContextDisplayItem {
             stacking_context: StackingContext {
                 scroll_policy: scroll_policy,
                 z_index: z_index,
                 transform: transform,
+                transform_style: transform_style,
                 perspective: perspective,
                 mix_blend_mode: mix_blend_mode,
                 filters: self.auxiliary_lists_builder.add_filters(&filters),
             }
         });
 
         self.push_item(item, bounds, ClipRegion::simple(&LayoutRect::zero()));
     }
 
     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,
-                       content_size: LayoutSize,
                        id: Option<ScrollLayerId>)
                        -> ScrollLayerId {
         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)
             }
         };
 
         let item = SpecificDisplayItem::Clip(ClipDisplayItem {
-            content_size: content_size,
             id: id,
             parent_id: *self.clip_stack.last().unwrap(),
         });
 
-        self.push_item(item, clip.main, clip);
+        self.push_item(item, content_rect, clip);
         id
     }
 
     pub fn push_scroll_layer(&mut self,
                              clip: ClipRegion,
-                             content_size: LayoutSize,
+                             content_rect: LayoutRect,
                              id: Option<ScrollLayerId>) {
-        let id = self.define_clip(clip, content_size, id);
+        let id = self.define_clip(content_rect, clip, id);
         self.clip_stack.push(id);
     }
 
     pub fn push_clip_id(&mut self, id: ScrollLayerId) {
         self.clip_stack.push(id);
     }
 
     pub fn pop_clip_id(&mut self) {
@@ -570,32 +579,27 @@ impl ItemRange {
         &backing_list[self.start..(self.start + self.length)]
     }
 
     pub fn get_mut<'a, T>(&self, backing_list: &'a mut [T]) -> &'a mut [T] {
         &mut backing_list[self.start..(self.start + self.length)]
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Default)]
 pub struct AuxiliaryListsBuilder {
     gradient_stops: Vec<GradientStop>,
     complex_clip_regions: Vec<ComplexClipRegion>,
     filters: Vec<FilterOp>,
     glyph_instances: Vec<GlyphInstance>,
 }
 
 impl AuxiliaryListsBuilder {
     pub fn new() -> AuxiliaryListsBuilder {
-        AuxiliaryListsBuilder {
-            gradient_stops: Vec::new(),
-            complex_clip_regions: Vec::new(),
-            filters: Vec::new(),
-            glyph_instances: Vec::new(),
-        }
+        AuxiliaryListsBuilder::default()
     }
 
     pub fn add_gradient_stops(&mut self, gradient_stops: &[GradientStop]) -> ItemRange {
         ItemRange::new(&mut self.gradient_stops, gradient_stops)
     }
 
     pub fn gradient_stops(&self, gradient_stops_range: &ItemRange) -> &[GradientStop] {
         gradient_stops_range.get(&self.gradient_stops[..])
--- a/gfx/webrender_traits/src/font.rs
+++ b/gfx/webrender_traits/src/font.rs
@@ -42,32 +42,44 @@ impl FontKey {
 
 #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Ord, PartialOrd)]
 pub enum FontRenderMode {
     Mono,
     Alpha,
     Subpixel,
 }
 
+const FIXED16_SHIFT: i32 = 16;
+
+// This matches the behaviour of SkScalarToFixed
+fn f32_truncate_to_fixed16(x: f32) -> i32 {
+    let fixed1 = (1 << FIXED16_SHIFT) as f32;
+    (x * fixed1) as i32
+}
+
 impl FontRenderMode {
     // Skia quantizes subpixel offets into 1/4 increments.
     // Given the absolute position, return the quantized increment
     fn subpixel_quantize_offset(&self, pos: f32) -> SubpixelOffset {
         if *self != FontRenderMode::Subpixel {
             return SubpixelOffset::Zero;
         }
 
-        const SUBPIXEL_ROUNDING :f32 = 0.125; // Skia chosen value.
-        let fraction = (pos + SUBPIXEL_ROUNDING).fract();
+        const SUBPIXEL_BITS: i32 = 2;
+        const SUBPIXEL_FIXED16_MASK: i32 = ((1 << SUBPIXEL_BITS) - 1) << (FIXED16_SHIFT - SUBPIXEL_BITS);
+
+        const SUBPIXEL_ROUNDING: f32 = 0.5 / (1 << SUBPIXEL_BITS) as f32;
+        let pos = pos + SUBPIXEL_ROUNDING;
+        let fraction = (f32_truncate_to_fixed16(pos) & SUBPIXEL_FIXED16_MASK) >> (FIXED16_SHIFT - SUBPIXEL_BITS);
 
         match fraction {
-            0.0...0.25 => SubpixelOffset::Zero,
-            0.25...0.5 => SubpixelOffset::Quarter,
-            0.5...0.75 => SubpixelOffset::Half,
-            0.75...1.0 => SubpixelOffset::ThreeQuarters,
+            0 => SubpixelOffset::Zero,
+            1 => SubpixelOffset::Quarter,
+            2 => SubpixelOffset::Half,
+            3 => SubpixelOffset::ThreeQuarters,
             _ => panic!("Should only be given the fractional part"),
         }
     }
 }
 
 #[repr(u8)]
 #[derive(Hash, Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
 pub enum SubpixelOffset {
@@ -99,17 +111,17 @@ impl SubpixelPoint {
                render_mode: FontRenderMode) -> SubpixelPoint {
         SubpixelPoint {
             x: render_mode.subpixel_quantize_offset(point.x),
             y: render_mode.subpixel_quantize_offset(point.y),
         }
     }
 
     pub fn to_f64(&self) -> (f64, f64) {
-        return (self.x.into(), self.y.into());
+        (self.x.into(), self.y.into())
     }
 
     pub fn set_offset(&mut self, point: Point2D<f32>, render_mode: FontRenderMode) {
         self.x = render_mode.subpixel_quantize_offset(point.x);
         self.y = render_mode.subpixel_quantize_offset(point.y);
     }
 }
 
--- a/gfx/webrender_traits/src/lib.rs
+++ b/gfx/webrender_traits/src/lib.rs
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #![cfg_attr(feature = "nightly", feature(nonzero))]
+#![cfg_attr(feature = "cargo-clippy", allow(too_many_arguments, float_cmp))]
 
 extern crate app_units;
 extern crate byteorder;
 #[cfg(feature = "nightly")]
 extern crate core;
 extern crate euclid;
 extern crate gleam;
 #[macro_use]