Bug 1351480 - Update webrender and webrender_traits to 7463ae5908ca1d4065763a8753af5d72a6f78b85. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 03 Apr 2017 22:50:47 -0400
changeset 555322 d99a305b952495012d2122650db24fe2d4d7581b
parent 555321 2afb34b24e7814031d145d40e853bc5b27b46f62
child 555323 03a8fe4016d029be066bb0e226b222c9fefda846
push id52214
push userkgupta@mozilla.com
push dateTue, 04 Apr 2017 02:58:29 +0000
reviewersjrmuizel
bugs1351480
milestone55.0a1
Bug 1351480 - Update webrender and webrender_traits to 7463ae5908ca1d4065763a8753af5d72a6f78b85. r?jrmuizel MozReview-Commit-ID: DR6ucPKvqr9
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/res/cs_clip_rectangle.fs.glsl
gfx/webrender/res/cs_clip_rectangle.glsl
gfx/webrender/res/cs_clip_rectangle.vs.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_box_shadow.fs.glsl
gfx/webrender/res/ps_box_shadow.vs.glsl
gfx/webrender/res/ps_composite.fs.glsl
gfx/webrender/res/ps_gradient.fs.glsl
gfx/webrender/res/ps_gradient.vs.glsl
gfx/webrender/res/ps_image.fs.glsl
gfx/webrender/res/ps_image.glsl
gfx/webrender/res/ps_image.vs.glsl
gfx/webrender/res/ps_radial_gradient.vs.glsl
gfx/webrender/res/shared.glsl
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/gpu_store.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/mask_cache.rs
gfx/webrender/src/platform/unix/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/scene.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/channel.rs
gfx/webrender_traits/src/channel_ipc.rs
gfx/webrender_traits/src/channel_mpsc.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/image.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: dafe3579e8dc886e6584116dc52a9362b543c169
+Latest Commit: 7463ae5908ca1d4065763a8753af5d72a6f78b85
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,30 +1,30 @@
 [package]
 name = "webrender"
-version = "0.26.0"
+version = "0.30.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"]
 profiler = ["thread_profiler/thread_profiler"]
 webgl = ["offscreen_gl_context", "webrender_traits/webgl"]
 
 [dependencies]
 app_units = "0.4"
-bincode = "1.0.0-alpha2"
+bincode = "1.0.0-alpha6"
 bit-set = "0.4"
 byteorder = "1.0"
 euclid = "0.11"
 fnv = "1.0"
-gleam = "0.4.1"
+gleam = "0.4.2"
 lazy_static = "0.2"
 log = "0.3"
 num-traits = "0.1.32"
 offscreen_gl_context = {version = "0.8.0", features = ["serde", "osmesa"], optional = true}
 time = "0.1"
 threadpool = "1.3.2"
 webrender_traits = {path = "../webrender_traits"}
 bitflags = "0.7"
--- a/gfx/webrender/res/cs_clip_rectangle.fs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.fs.glsl
@@ -35,10 +35,15 @@ float rounded_rect(vec2 pos) {
 
 
 void main(void) {
     float alpha = 1.f;
     vec2 local_pos = init_transform_fs(vPos, vLocalRect, alpha);
 
     float clip_alpha = rounded_rect(local_pos);
 
-    oFragColor = vec4(min(alpha, clip_alpha), 0.0, 0.0, 1.0);
+    float combined_alpha = min(alpha, clip_alpha);
+
+    // Select alpha or inverse alpha depending on clip in/out.
+    float final_alpha = mix(combined_alpha, 1.0 - combined_alpha, vClipMode);
+
+    oFragColor = vec4(final_alpha, 0.0, 0.0, 1.0);
 }
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -3,8 +3,9 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec3 vPos;
 flat varying vec4 vLocalRect;
 flat varying vec4 vClipRect;
 flat varying vec4 vClipRadius;
+flat varying float vClipMode;
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.vs.glsl
@@ -1,26 +1,25 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 struct ClipRect {
     vec4 rect;
-    vec4 dummy;
+    vec4 mode;
 };
 
 ClipRect fetch_clip_rect(int index) {
     ClipRect rect;
 
     ivec2 uv = get_fetch_uv_2(index);
 
     rect.rect = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
-    //rect.dummy = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
-    rect.dummy = vec4(0.0, 0.0, 0.0, 0.0);
+    rect.mode = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
 
     return rect;
 }
 
 struct ClipCorner {
     vec4 rect;
     vec4 outer_inner_radius;
 };
@@ -65,14 +64,15 @@ void main(void) {
 
     TransformVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                     layer,
                                                     area,
                                                     cci.segment_index);
     vLocalRect = vi.clipped_local_rect;
     vPos = vi.local_pos;
 
+    vClipMode = clip.rect.mode.x;
     vClipRect = vec4(local_rect.xy, local_rect.xy + local_rect.zw);
     vClipRadius = vec4(clip.top_left.outer_inner_radius.x,
                        clip.top_right.outer_inner_radius.x,
                        clip.bottom_right.outer_inner_radius.x,
                        clip.bottom_left.outer_inner_radius.x);
 }
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -258,26 +258,26 @@ GradientStop fetch_gradient_stop(int ind
     stop.color = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
     stop.offset = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
 
     return stop;
 }
 
 struct RadialGradient {
     vec4 start_end_center;
-    vec4 start_end_radius_extend_mode;
+    vec4 start_end_radius_ratio_xy_extend_mode;
 };
 
 RadialGradient fetch_radial_gradient(int index) {
     RadialGradient gradient;
 
     ivec2 uv = get_fetch_uv_2(index);
 
     gradient.start_end_center = texelFetchOffset(sData32, uv, 0, ivec2(0, 0));
-    gradient.start_end_radius_extend_mode = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
+    gradient.start_end_radius_ratio_xy_extend_mode = texelFetchOffset(sData32, uv, 0, ivec2(1, 0));
 
     return gradient;
 }
 
 struct Glyph {
     vec4 offset;
 };
 
--- a/gfx/webrender/res/ps_box_shadow.fs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.fs.glsl
@@ -1,20 +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/. */
 
 void main(void) {
+    vec4 clip_scale = vec4(1.0, 1.0, 1.0, do_clip());
+
     // Mirror and stretch the box shadow corner over the entire
     // primitives.
     vec2 uv = vMirrorPoint - abs(vUv.xy - vMirrorPoint);
 
     // Ensure that we don't fetch texels outside the box
     // shadow corner. This can happen, for example, when
     // drawing the outer parts of an inset box shadow.
     uv = clamp(uv, vec2(0.0), vec2(1.0));
 
     // Map the unit UV to the actual UV rect in the cache.
     uv = mix(vCacheUvRectCoords.xy, vCacheUvRectCoords.zw, uv);
 
     // Modulate the box shadow by the color.
-    oFragColor = dither(vColor * texture(sCacheRGBA8, vec3(uv, vUv.z)));
+    oFragColor = clip_scale * dither(vColor * texture(sCacheRGBA8, vec3(uv, vUv.z)));
 }
--- a/gfx/webrender/res/ps_box_shadow.vs.glsl
+++ b/gfx/webrender/res/ps_box_shadow.vs.glsl
@@ -24,9 +24,11 @@ void main(void) {
 
     vUv.xy = (vi.local_pos - prim.local_rect.p0) / patch_size;
     vMirrorPoint = 0.5 * prim.local_rect.size / patch_size;
 
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vCacheUvRectCoords = vec4(patch_origin, patch_origin + patch_size_device_pixels) / texture_size.xyxy;
 
     vColor = bs.color;
+
+    write_clip(vi.screen_pos, prim.clip_area);
 }
--- a/gfx/webrender/res/ps_composite.fs.glsl
+++ b/gfx/webrender/res/ps_composite.fs.glsl
@@ -166,16 +166,29 @@ const int MixBlendMode_Hue         = 12;
 const int MixBlendMode_Saturation  = 13;
 const int MixBlendMode_Color       = 14;
 const int MixBlendMode_Luminosity  = 15;
 
 void main(void) {
     vec4 Cb = texture(sCacheRGBA8, vUv0);
     vec4 Cs = texture(sCacheRGBA8, vUv1);
 
+    if (Cb.a == 0.0) {
+        oFragColor = Cs;
+        return;
+    }
+    if (Cs.a == 0.0) {
+        oFragColor = vec4(0.0, 0.0, 0.0, 0.0);
+        return;
+    }
+
+    // The mix-blend-mode functions assume no premultiplied alpha
+    Cb.rgb /= Cb.a;
+    Cs.rgb /= Cs.a;
+
     // Return yellow if none of the branches match (shouldn't happen).
     vec4 result = vec4(1.0, 1.0, 0.0, 1.0);
 
     switch (vOp) {
         case MixBlendMode_Multiply:
             result.rgb = Multiply(Cb.rgb, Cs.rgb);
             break;
         case MixBlendMode_Screen:
--- a/gfx/webrender/res/ps_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_gradient.fs.glsl
@@ -7,10 +7,14 @@ 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());
-    oFragColor = dither(vColor * vec4(1.0, 1.0, 1.0, alpha));
+
+    // 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);
 }
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_gradient.vs.glsl
@@ -14,47 +14,47 @@ void main(void) {
     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) {
         // Calculate the x coord of the gradient stops
         vec2 g01_x = mix(gradient.start_end_point.xx, gradient.start_end_point.zz,
                          vec2(g0.offset.x, g1.offset.x));
 
-        // The start and end point of gradient might exceed the geometry rect. So clamp
-        // it to the geometry rect.
-        g01_x = clamp(g01_x, prim.local_rect.p0.xx, prim.local_rect.p0.xx + prim.local_rect.size.xx);
+        // 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 rect using the clamped coords
-        segment_rect.p0 = vec2(g01_x.x, prim.local_rect.p0.y);
-        segment_rect.size = vec2(g01_x.y - g01_x.x, prim.local_rect.size.y);
+        // Calculate the segment rect using the clamped coords
+        segment_rect.p0 = vec2(g01_x_clamped.x, prim.local_rect.p0.y);
+        segment_rect.size = vec2(g01_x_clamped.y - g01_x_clamped.x, prim.local_rect.size.y);
         axis = vec2(1.0, 0.0);
 
-        // We need to adjust the colors of the stops because they may have been clamped
-        vec2 adjusted_offset =
-            (g01_x - segment_rect.p0.xx) / segment_rect.size.xx;
+        // 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(g0.offset.x, g1.offset.x));
 
-        // The start and end point of gradient might exceed the geometry rect. So clamp
-        // it to the geometry rect.
-        g01_y = clamp(g01_y, prim.local_rect.p0.yy, prim.local_rect.p0.yy + prim.local_rect.size.yy);
+        // 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 rect using the clamped coords
-        segment_rect.p0 = vec2(prim.local_rect.p0.x, g01_y.x);
-        segment_rect.size = vec2(prim.local_rect.size.x, g01_y.y - g01_y.x);
+        // Calculate the segment rect using the clamped coords
+        segment_rect.p0 = vec2(prim.local_rect.p0.x, g01_y_clamped.x);
+        segment_rect.size = vec2(prim.local_rect.size.x, g01_y_clamped.y - g01_y_clamped.x);
         axis = vec2(0.0, 1.0);
 
-        // We need to adjust the colors of the stops because they may have been clamped
-        vec2 adjusted_offset =
-            (g01_y - segment_rect.p0.yy) / segment_rect.size.yy;
+        // Adjust the stop colors by how much they were clamped
+        vec2 adjusted_offset = (g01_y_clamped - g01_y.xx) / (g01_y.y - g01_y.x);
         adjusted_color_g0 = mix(g0.color, g1.color, adjusted_offset.x);
         adjusted_color_g1 = mix(g0.color, g1.color, adjusted_offset.y);
     }
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
--- a/gfx/webrender/res/ps_image.fs.glsl
+++ b/gfx/webrender/res/ps_image.fs.glsl
@@ -19,17 +19,20 @@ void main(void) {
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We calculate the particular tile this fragment belongs to, taking into
     // account the spacing in between tiles. We only paint if our fragment does
     // not fall into that spacing.
     vec2 position_in_tile = mod(relative_pos_in_rect, vStretchSize + vTileSpacing);
-    // We clamp the texture coordinates to the half-pixel offset from the borders
-    // in order to avoid sampling outside of the texture area.
     vec2 st = vTextureOffset + ((position_in_tile / vStretchSize) * vTextureSize);
     st = clamp(st, vStRect.xy, vStRect.zw);
 
     alpha = alpha * float(all(bvec2(step(position_in_tile, vStretchSize))));
 
+#ifdef WR_FEATURE_TEXTURE_RECT
+    // textureLod doesn't support sampler2DRect. Use texture() instead.
+    oFragColor = vec4(alpha) * texture(sColor0, st);
+#else
     oFragColor = vec4(alpha) * textureLod(sColor0, st, 0.0);
+#endif
 }
--- a/gfx/webrender/res/ps_image.glsl
+++ b/gfx/webrender/res/ps_image.glsl
@@ -1,16 +1,19 @@
 /* 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/. */
 
+// If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use non-normalized
+// texture coordinates. Otherwise, it uses normalized texture coordinates. Please
+// check GL_TEXTURE_RECTANGLE.
 flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
 flat varying vec2 vTextureSize;   // Size of the image in the texture atlas.
 flat varying vec2 vTileSpacing;   // Amount of space between tiled instances of this image.
-flat varying vec4 vStRect;     	  // Rectangle of valid texture rect, in st-space.
+flat varying vec4 vStRect;        // Rectangle of valid texture rect.
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 flat varying vec4 vLocalRect;
 flat varying vec2 vStretchSize;
 #else
 varying vec2 vLocalPos;
 flat varying vec2 vStretchSize;
--- a/gfx/webrender/res/ps_image.vs.glsl
+++ b/gfx/webrender/res/ps_image.vs.glsl
@@ -22,21 +22,33 @@ void main(void) {
                                  prim.z,
                                  prim.layer,
                                  prim.task);
     vLocalPos = vi.local_pos - vi.local_rect.p0;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
+    vTileSpacing = image.stretch_size_and_tile_spacing.zw;
+    vStretchSize = image.stretch_size_and_tile_spacing.xy;
+
+    // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
+    // non-normalized texture coordinates.
+#ifdef WR_FEATURE_TEXTURE_RECT
+    vec2 texture_size_normalization_factor = vec2(1, 1);
+#else
+    vec2 texture_size_normalization_factor = vec2(textureSize(sColor0, 0));
+#endif
+
     // vUv will contain how many times this image has wrapped around the image size.
-    vec2 texture_size = vec2(textureSize(sColor0, 0));
-    vec2 st0 = res.uv_rect.xy / texture_size;
-    vec2 st1 = res.uv_rect.zw / texture_size;
+    vec2 st0 = res.uv_rect.xy / texture_size_normalization_factor;
+    vec2 st1 = res.uv_rect.zw / texture_size_normalization_factor;
 
     vTextureSize = st1 - st0;
     vTextureOffset = st0;
     vTileSpacing = image.stretch_size_and_tile_spacing.zw;
     vStretchSize = image.stretch_size_and_tile_spacing.xy;
 
-    vec2 half_texel = vec2(0.5) / texture_size;
+    // We clamp the texture coordinates to the half-pixel offset from the borders
+    // in order to avoid sampling outside of the texture area.
+    vec2 half_texel = vec2(0.5) / texture_size_normalization_factor;
     vStRect = vec4(min(st0, st1) + half_texel, max(st0, st1) - half_texel);
 }
--- a/gfx/webrender/res/ps_radial_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.vs.glsl
@@ -18,17 +18,24 @@ void main(void) {
     // 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;
-    vStartRadius = gradient.start_end_radius_extend_mode.x;
-    vEndRadius = gradient.start_end_radius_extend_mode.y;
+    vStartRadius = gradient.start_end_radius_ratio_xy_extend_mode.x;
+    vEndRadius = gradient.start_end_radius_ratio_xy_extend_mode.y;
+
+    // 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;
 
     // 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_extend_mode.z) == EXTEND_MODE_REPEAT);
+    vGradientRepeat = float(int(gradient.start_end_radius_ratio_xy_extend_mode.w) == EXTEND_MODE_REPEAT);
 }
--- a/gfx/webrender/res/shared.glsl
+++ b/gfx/webrender/res/shared.glsl
@@ -28,19 +28,25 @@
 
     // Fragment shader outputs
     out vec4 oFragColor;
 #endif
 
 //======================================================================================
 // Shared shader uniforms
 //======================================================================================
+#ifndef WR_FEATURE_TEXTURE_RECT
 uniform sampler2D sColor0;
 uniform sampler2D sColor1;
 uniform sampler2D sColor2;
+#else
+uniform sampler2DRect sColor0;
+uniform sampler2DRect sColor1;
+uniform sampler2DRect sColor2;
+#endif
 uniform sampler2D sDither;
 uniform sampler2D sMask;
 
 //======================================================================================
 // Interpolator definitions
 //======================================================================================
 
 //======================================================================================
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use euclid::Point3D;
 use geometry::ray_intersects_rect;
-use mask_cache::{ClipSource, MaskCacheInfo};
+use mask_cache::{ClipSource, MaskCacheInfo, RegionMode};
 use prim_store::GpuBlock32;
 use renderer::VertexDataStore;
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::TransformedRect;
 use webrender_traits::{ClipRegion, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use webrender_traits::{LayerToScrollTransform, LayerToWorldTransform, PipelineId};
 use webrender_traits::{ScrollEventPhase, ScrollLayerId, ScrollLayerRect, ScrollLocation};
@@ -19,17 +19,17 @@ use webrender_traits::{WorldPoint, World
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
 #[derive(Clone, Debug)]
 pub struct ClipInfo {
     /// The ClipSource for this node, which is used to generate mask_cache_info.
-    pub clip_source: ClipSource,
+    pub clip_sources: Vec<ClipSource>,
 
     /// The MaskCacheInfo for this node, which is produced by processing the
     /// provided ClipSource.
     pub mask_cache_info: Option<MaskCacheInfo>,
 
     /// The packed layer index for this node, which is used to render a clip mask
     /// for it, if necessary.
     pub packed_layer_index: PackedLayerIndex,
@@ -42,20 +42,20 @@ pub struct ClipInfo {
 
 impl ClipInfo {
     pub fn new(clip_region: &ClipRegion,
                clip_store: &mut VertexDataStore<GpuBlock32>,
                packed_layer_index: PackedLayerIndex,)
                -> ClipInfo {
         // We pass true here for the MaskCacheInfo because this type of
         // mask needs an extra clip for the clip rectangle.
-        let clip_source = ClipSource::Region(clip_region.clone());
+        let clip_sources = vec![ClipSource::Region(clip_region.clone(), RegionMode::IncludeRect)];
         ClipInfo {
-            mask_cache_info: MaskCacheInfo::new(&clip_source, true, clip_store),
-            clip_source: clip_source,
+            mask_cache_info: MaskCacheInfo::new(&clip_sources, clip_store),
+            clip_sources: clip_sources,
             packed_layer_index: packed_layer_index,
             xf_rect: None,
         }
     }
 
     pub fn is_masking(&self) -> bool {
         match self.mask_cache_info {
             Some(ref info) => info.is_masking(),
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -18,17 +18,17 @@ pub struct ClipScrollTree {
 
     /// The ScrollLayerId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
     pub current_scroll_layer_id: Option<ScrollLayerId>,
 
     /// The current reference frame id, used for giving a unique id to all new
     /// reference frames. The ClipScrollTree increments this by one every time a
     /// reference frame is created.
-    current_reference_frame_id: usize,
+    current_reference_frame_id: u64,
 
     /// The root reference frame, which is the true root of the ClipScrollTree. Initially
     /// this ID is not valid, which is indicated by ```node``` being empty.
     pub root_reference_frame_id: ScrollLayerId,
 
     /// The root scroll node which is the first child of the root reference frame.
     /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
     pub topmost_scroll_layer_id: ScrollLayerId,
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -46,16 +46,27 @@ static SHADER_PREAMBLE: &'static str = "
 pub enum DepthFunction {
     Less = gl::LESS,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum TextureTarget {
     Default,
     Array,
+    Rect,
+}
+
+impl TextureTarget {
+    pub fn to_gl_target(&self) -> gl::GLuint {
+        match *self {
+            TextureTarget::Default => gl::TEXTURE_2D,
+            TextureTarget::Array => gl::TEXTURE_2D_ARRAY,
+            TextureTarget::Rect => gl::TEXTURE_RECTANGLE,
+        }
+    }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum TextureFilter {
     Nearest,
     Linear,
 }
 
@@ -299,20 +310,20 @@ impl VertexFormat {
     }
 }
 
 impl TextureId {
     pub fn bind(&self, gl: &gl::Gl) {
         gl.bind_texture(self.target, self.name);
     }
 
-    pub fn new(name: gl::GLuint) -> TextureId {
+    pub fn new(name: gl::GLuint, texture_target: TextureTarget) -> TextureId {
         TextureId {
             name: name,
-            target: gl::TEXTURE_2D,
+            target: texture_target.to_gl_target(),
         }
     }
 
     pub fn invalid() -> TextureId {
         TextureId {
             name: 0,
             target: gl::TEXTURE_2D,
         }
@@ -1085,25 +1096,20 @@ impl Device {
     }
 
     pub fn create_texture_ids(&mut self,
                               count: i32,
                               target: TextureTarget) -> Vec<TextureId> {
         let id_list = self.gl.gen_textures(count);
         let mut texture_ids = Vec::new();
 
-        let target = match target {
-            TextureTarget::Default => gl::TEXTURE_2D,
-            TextureTarget::Array => gl::TEXTURE_2D_ARRAY,
-        };
-
         for id in id_list {
             let texture_id = TextureId {
                 name: id,
-                target: target,
+                target: target.to_gl_target(),
             };
 
             let texture = Texture {
                 gl: self.gl.clone(),
                 id: id,
                 width: 0,
                 height: 0,
                 format: ImageFormat::Invalid,
@@ -2044,19 +2050,18 @@ impl Device {
     }
 
     pub fn set_blend_mode_premultiplied_alpha(&self) {
         self.gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
 
     pub fn set_blend_mode_alpha(&self) {
-        //self.gl.blend_func(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
         self.gl.blend_func_separate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA,
-                                     gl::ONE, gl::ONE);
+                                    gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
         self.gl.blend_equation(gl::FUNC_ADD);
     }
 
     pub fn set_blend_mode_subpixel(&self, color: ColorF) {
         self.gl.blend_color(color.r, color.g, color.b, color.a);
         self.gl.blend_func(gl::CONSTANT_COLOR, gl::ONE_MINUS_SRC_COLOR);
     }
 
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -436,17 +436,19 @@ impl Frame {
         }
 
         // TODO(gw): Int with overflow etc
         context.builder.push_stacking_context(&reference_frame_relative_offset,
                                               pipeline_id,
                                               level == 0,
                                               composition_operations);
 
-        if level == 0 {
+        // 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,
                                                         &ClipRegion::simple(&background_rect),
@@ -651,16 +653,17 @@ impl Frame {
                 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);
                 }
                 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,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use batch_builder::BorderSideHelpers;
 use frame::FrameId;
 use gpu_store::GpuStoreAddress;
 use internal_types::{HardwareCompositeOp, SourceTexture};
-use mask_cache::{ClipSource, MaskCacheInfo};
+use mask_cache::{ClipMode, ClipSource, MaskCacheInfo, RegionMode};
 use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, BoxShadowPrimitiveGpu};
 use prim_store::{GradientPrimitiveCpu, GradientPrimitiveGpu, ImagePrimitiveCpu, ImagePrimitiveGpu};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveGeometry, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, RadialGradientPrimitiveGpu};
 use prim_store::{RectanglePrimitive, TextRunPrimitiveCpu, TextRunPrimitiveGpu};
 use prim_store::{TexelRect, YuvImagePrimitiveCpu, YuvImagePrimitiveGpu};
 use profiler::{FrameProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, MaskCacheKey, MaskResult, RenderTask, RenderTaskIndex};
@@ -143,42 +143,47 @@ impl FrameBuilder {
             stacking_context_stack: Vec::new(),
         }
     }
 
     fn add_primitive(&mut self,
                      scroll_layer_id: ScrollLayerId,
                      rect: &LayerRect,
                      clip_region: &ClipRegion,
+                     extra_clip: Option<ClipSource>,
                      container: PrimitiveContainer)
                      -> PrimitiveIndex {
         let stacking_context_index = *self.stacking_context_stack.last().unwrap();
         if !self.stacking_context_store[stacking_context_index.0]
                 .has_clip_scroll_group(scroll_layer_id) {
             let group_index = self.create_clip_scroll_group(stacking_context_index,
                                                             scroll_layer_id);
             let stacking_context = &mut self.stacking_context_store[stacking_context_index.0];
             stacking_context.clip_scroll_groups.push(group_index);
         }
 
         let geometry = PrimitiveGeometry {
             local_rect: *rect,
             local_clip_rect: clip_region.main,
         };
-        let clip_source = if clip_region.is_complex() {
-            ClipSource::Region(clip_region.clone())
-        } else {
-            ClipSource::NoClip
-        };
-        let clip_info = MaskCacheInfo::new(&clip_source,
-                                           false,
+        let mut clip_sources = Vec::new();
+        if clip_region.is_complex() {
+            clip_sources.push(ClipSource::Region(clip_region.clone(), RegionMode::ExcludeRect));
+        }
+        // TODO(gw): Perhaps in the future it's worth passing in an array
+        //           so that callers can provide an arbitrary number
+        //           of clips?
+        if let Some(extra_clip) = extra_clip {
+            clip_sources.push(extra_clip);
+        }
+        let clip_info = MaskCacheInfo::new(&clip_sources,
                                            &mut self.prim_store.gpu_data32);
 
         let prim_index = self.prim_store.add_primitive(geometry,
-                                                       Box::new(clip_source),
+                                                       clip_sources,
                                                        clip_info,
                                                        container);
 
         match self.cmds.last_mut().unwrap() {
             &mut PrimitiveRunCmd::PrimitiveRun(_run_prim_index, ref mut count, run_layer_id)
                 if run_layer_id == scroll_layer_id => {
                     debug_assert!(_run_prim_index.0 + *count == prim_index.0);
                     *count += 1;
@@ -348,16 +353,17 @@ impl FrameBuilder {
 
         let prim = RectanglePrimitive {
             color: *color,
         };
 
         let prim_index = self.add_primitive(scroll_layer_id,
                                             rect,
                                             clip_region,
+                                            None,
                                             PrimitiveContainer::Rectangle(prim));
 
         match flags {
             PrimitiveFlags::None => {}
             PrimitiveFlags::Scrollbar(scroll_layer_id, border_radius) => {
                 self.scrollbar_prims.push(ScrollbarPrimitive {
                     prim_index: prim_index,
                     scroll_layer_id: scroll_layer_id,
@@ -646,16 +652,17 @@ impl FrameBuilder {
                         radius.bottom_right,
                         radius.bottom_left,
                     ],
                 };
 
                 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) {
                     self.add_gradient(scroll_layer_id,
                                       segment,
                                       clip_region,
                                       border.gradient.start_point,
@@ -668,16 +675,17 @@ impl FrameBuilder {
                 for segment in create_segments(border.outset) {
                     self.add_radial_gradient(scroll_layer_id,
                                              segment,
                                              clip_region,
                                              border.gradient.start_center,
                                              border.gradient.start_radius,
                                              border.gradient.end_center,
                                              border.gradient.end_radius,
+                                             border.gradient.ratio_xy,
                                              border.gradient.stops,
                                              border.gradient.extend_mode);
                 }
             }
         }
     }
 
     pub fn add_gradient(&mut self,
@@ -685,18 +693,22 @@ impl FrameBuilder {
                         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.y == end_point.y);
+                      (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());
         // 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 ||
@@ -727,47 +739,53 @@ impl FrameBuilder {
         };
 
         let prim = if aligned {
             PrimitiveContainer::AlignedGradient(gradient_cpu, gradient_gpu)
         } else {
             PrimitiveContainer::AngleGradient(gradient_cpu, gradient_gpu)
         };
 
-        self.add_primitive(scroll_layer_id, &rect, clip_region, prim);
+        self.add_primitive(scroll_layer_id,
+                           &rect,
+                           clip_region,
+                           None,
+                           prim);
     }
 
     pub fn add_radial_gradient(&mut self,
                                scroll_layer_id: ScrollLayerId,
                                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) {
         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),
-            padding: [0.0],
         };
 
         self.add_primitive(scroll_layer_id,
                            &rect,
                            clip_region,
+                           None,
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu, radial_gradient_gpu));
     }
 
     pub fn add_text(&mut self,
                     scroll_layer_id: ScrollLayerId,
                     rect: LayerRect,
                     clip_region: &ClipRegion,
                     font_key: FontKey,
@@ -827,16 +845,17 @@ impl FrameBuilder {
 
             let prim_gpu = TextRunPrimitiveGpu {
                 color: *color,
             };
 
             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,
@@ -883,22 +902,28 @@ impl FrameBuilder {
         // expensive box-shadow shader.
         enum BoxShadowKind {
             Simple(Vec<LayerRect>),     // Can be drawn via simple rectangles only
             Shadow(Vec<LayerRect>),     // Requires the full box-shadow code path
         }
 
         let shadow_kind = match clip_mode {
             BoxShadowClipMode::Outset | BoxShadowClipMode::None => {
+                // If a border radius is set, we need to draw inside
+                // the original box in order to draw where the border
+                // corners are. A clip-out mask applied below will
+                // ensure that we don't draw on the box itself.
+                let inner_box_bounds = box_bounds.inflate(-border_radius,
+                                                          -border_radius);
                 // For outset shadows, subtracting the element rectangle
                 // from the outer rectangle gives the rectangles we need
                 // to draw. In the simple case (no blur radius), we can
                 // just draw these as solid colors.
                 let mut rects = Vec::new();
-                subtract_rect(&outer_rect, box_bounds, &mut rects);
+                subtract_rect(&outer_rect, &inner_box_bounds, &mut rects);
                 if edge_size == 0.0 {
                     BoxShadowKind::Simple(rects)
                 } else {
                     BoxShadowKind::Shadow(rects)
                 }
             }
             BoxShadowClipMode::Inset => {
                 // For inset shadows, in the simple case (no blur) we
@@ -937,30 +962,40 @@ 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.
+                let extra_clip = if border_radius > 0.0 {
+                    Some(ClipSource::Complex(*box_bounds,
+                                             border_radius,
+                                             ClipMode::ClipOut))
+                } else {
+                    None
+                };
 
                 let prim_gpu = BoxShadowPrimitiveGpu {
                     src_rect: *box_bounds,
                     bs_rect: bs_rect,
                     color: *color,
                     blur_radius: blur_radius,
                     border_radius: border_radius,
                     edge_size: edge_size,
                     inverted: inverted,
                 };
 
                 self.add_primitive(scroll_layer_id,
                                    &outer_rect,
                                    clip_region,
+                                   extra_clip,
                                    PrimitiveContainer::BoxShadow(prim_gpu, rects));
             }
         }
     }
 
     pub fn add_webgl_rectangle(&mut self,
                                scroll_layer_id: ScrollLayerId,
                                rect: LayerRect,
@@ -976,16 +1011,17 @@ impl FrameBuilder {
         let prim_gpu = ImagePrimitiveGpu {
             stretch_size: rect.size,
             tile_spacing: LayerSize::zero(),
         };
 
         self.add_primitive(scroll_layer_id,
                            &rect,
                            clip_region,
+                           None,
                            PrimitiveContainer::Image(prim_cpu, prim_gpu));
     }
 
     pub fn add_image(&mut self,
                      scroll_layer_id: ScrollLayerId,
                      rect: LayerRect,
                      clip_region: &ClipRegion,
                      stretch_size: &LayerSize,
@@ -1007,16 +1043,17 @@ impl FrameBuilder {
         let prim_gpu = ImagePrimitiveGpu {
             stretch_size: *stretch_size,
             tile_spacing: *tile_spacing,
         };
 
         self.add_primitive(scroll_layer_id,
                            &rect,
                            clip_region,
+                           None,
                            PrimitiveContainer::Image(prim_cpu, prim_gpu));
     }
 
     pub fn add_yuv_image(&mut self,
                          scroll_layer_id: ScrollLayerId,
                          rect: LayerRect,
                          clip_region: &ClipRegion,
                          y_image_key: ImageKey,
@@ -1030,16 +1067,17 @@ impl FrameBuilder {
             yuv_resource_address: GpuStoreAddress(0),
         };
 
         let prim_gpu = YuvImagePrimitiveGpu::new(rect.size, color_space);
 
         self.add_primitive(scroll_layer_id,
                            &rect,
                            clip_region,
+                           None,
                            PrimitiveContainer::YuvImage(prim_cpu, prim_gpu));
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(&mut self,
                                                 screen_rect: &DeviceIntRect,
                                                 clip_scroll_tree: &mut ClipScrollTree,
@@ -1087,20 +1125,20 @@ impl FrameBuilder {
             geom.local_rect.origin.x = clip_scroll_node.local_viewport_rect.origin.x +
                                        clip_scroll_node.local_viewport_rect.size.width -
                                        geom.local_rect.size.width -
                                        distance_from_edge;
 
             geom.local_rect.origin.y = util::lerp(min_y, max_y, f);
             geom.local_clip_rect = geom.local_rect;
 
-            let clip_source = if scrollbar_prim.border_radius == 0.0 {
-                ClipSource::NoClip
+            let clip_source = if scrollbar_prim.border_radius > 0.0 {
+                Some(ClipSource::Complex(geom.local_rect, scrollbar_prim.border_radius, ClipMode::Clip))
             } else {
-                ClipSource::Complex(geom.local_rect, scrollbar_prim.border_radius)
+                None
             };
             self.prim_store.set_clip_source(scrollbar_prim.prim_index, clip_source);
             *self.prim_store.gpu_geometry.get_mut(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32)) = geom;
         }
     }
 
     fn build_render_task(&self) -> (RenderTask, usize) {
         profile_scope!("build_render_task");
@@ -1160,17 +1198,17 @@ impl FrameBuilder {
                     }
 
                     let composite_count = stacking_context.composite_ops.count();
 
                     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::Alpha,
+                                                                      HardwareCompositeOp::PremultipliedAlpha,
                                                                       next_z);
                         next_z += 1;
                         prev_task.as_alpha_batch().alpha_items.push(item);
                         prev_task.children.push(current_task);
                         current_task = prev_task;
                     }
 
                     for filter in &stacking_context.composite_ops.filters {
@@ -1208,35 +1246,37 @@ impl FrameBuilder {
 
                     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 clip_scroll_group = &self.clip_scroll_group_store[group_index.0];
+                    let xf_rect = match &self.clip_scroll_group_store[group_index.0].xf_rect {
+                        &Some(ref xf_rect) => xf_rect,
+                        &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 transform_kind = clip_scroll_group.xf_rect.as_ref().unwrap().kind;
                             let needs_clipping = prim_metadata.clip_task.is_some();
-                            let needs_blending = transform_kind == TransformedRectKind::Complex ||
+                            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
                             };
@@ -1370,17 +1410,17 @@ struct LayerRectCalculationAndCullingPas
 
     /// A cached clip info stack, which should handle the most common situation,
     /// which is that we are using the same clip info stack that we were using
     /// previously.
     current_clip_stack: Vec<(PackedLayerIndex, MaskCacheInfo)>,
 
     /// Information about the cached clip stack, which is used to avoid having
     /// to recalculate it for every primitive.
-    current_clip_info: Option<(ScrollLayerId, DeviceIntRect)>
+    current_clip_info: Option<(ScrollLayerId, Option<DeviceIntRect>)>
 }
 
 impl<'a> LayerRectCalculationAndCullingPass<'a> {
     fn create_and_run(frame_builder: &'a mut FrameBuilder,
                       screen_rect: &'a DeviceIntRect,
                       clip_scroll_tree: &'a mut ClipScrollTree,
                       auxiliary_lists_map: &'a AuxiliaryListsMap,
                       resource_cache: &'a mut ResourceCache,
@@ -1452,26 +1492,28 @@ impl<'a> LayerRectCalculationAndCullingP
             let mask_info = match node_clip_info.mask_cache_info {
                 Some(ref mut mask_info) => mask_info,
                 _ => continue,
             };
 
             let auxiliary_lists = self.auxiliary_lists_map.get(&node.pipeline_id)
                                                           .expect("No auxiliary lists?");
 
-            mask_info.update(&node_clip_info.clip_source,
+            mask_info.update(&node_clip_info.clip_sources,
                              &packed_layer.transform,
                              &mut self.frame_builder.prim_store.gpu_data32,
                              self.device_pixel_ratio,
                              auxiliary_lists);
 
-            if let Some(mask) = node_clip_info.clip_source.image_mask() {
-                // We don't add the image mask for resolution, because
-                // layer masks are resolved later.
-                self.resource_cache.request_image(mask.image, ImageRendering::Auto, None);
+            for clip_source in &node_clip_info.clip_sources {
+                if let Some(mask) = clip_source.image_mask() {
+                    // We don't add the image mask for resolution, because
+                    // layer masks are resolved later.
+                    self.resource_cache.request_image(mask.image, ImageRendering::Auto, None);
+                }
             }
         }
     }
 
     fn recalculate_clip_scroll_groups(&mut self) {
         for ref mut group in &mut self.frame_builder.clip_scroll_group_store {
             let stacking_context_index = group.stacking_context_index;
             let stacking_context = &mut self.frame_builder
@@ -1548,17 +1590,17 @@ impl<'a> LayerRectCalculationAndCullingP
         // calculate the device bounding rect. In the future, we could cache this during
         // the initial adding of items for the common case (where there is only a single
         // scroll layer for items in a stacking context).
         let stacking_context = &mut self.frame_builder
                                         .stacking_context_store[stacking_context_index.0];
         stacking_context.bounding_rect = DeviceIntRect::zero();
     }
 
-    fn rebuild_clip_info_stack_if_necessary(&mut self, id: ScrollLayerId) -> DeviceIntRect {
+    fn rebuild_clip_info_stack_if_necessary(&mut self, id: ScrollLayerId) -> Option<DeviceIntRect> {
         if let Some((current_scroll_id, bounding_rect)) = self.current_clip_info {
             if current_scroll_id == id {
                 return bounding_rect;
             }
         }
 
         // TODO(mrobinson): If we notice that this process is expensive, we can special-case
         // more common situations, such as moving from a child or a parent.
@@ -1571,24 +1613,25 @@ impl<'a> LayerRectCalculationAndCullingP
             current_id = node.parent;
 
             let clip_info = match node.node_type {
                 NodeType::Clip(ref clip) if clip.is_masking() => clip,
                 _ => continue,
             };
 
             if bounding_rect.is_none() {
-                bounding_rect = Some(clip_info.xf_rect.as_ref().unwrap().bounding_rect);
+                bounding_rect =
+                    Some(clip_info.xf_rect.as_ref().map_or_else(DeviceIntRect::zero,
+                                                                |x| x.bounding_rect))
             }
             self.current_clip_stack.push((clip_info.packed_layer_index,
                                           clip_info.mask_cache_info.clone().unwrap()))
         }
         self.current_clip_stack.reverse();
 
-        let bounding_rect = bounding_rect.unwrap_or_else(DeviceIntRect::zero);
         self.current_clip_info = Some((id, bounding_rect));
         bounding_rect
     }
 
     fn handle_primitive_run(&mut self,
                             prim_index: PrimitiveIndex,
                             prim_count: usize,
                             scroll_layer_id: ScrollLayerId) {
@@ -1602,16 +1645,19 @@ impl<'a> LayerRectCalculationAndCullingP
             }
 
             let group_index = stacking_context.clip_scroll_group(scroll_layer_id);
             let clip_scroll_group = &self.frame_builder.clip_scroll_group_store[group_index.0];
             (clip_scroll_group.packed_layer_index, stacking_context.pipeline_id)
         };
 
         let node_clip_bounds = self.rebuild_clip_info_stack_if_necessary(scroll_layer_id);
+        if node_clip_bounds.map_or(false, |bounds| bounds.is_empty()) {
+            return;
+        }
 
         let stacking_context =
             &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
         let packed_layer = &self.frame_builder.packed_layers[packed_layer_index.0];
         let auxiliary_lists = self.auxiliary_lists_map.get(&pipeline_id)
                                                       .expect("No auxiliary lists?");
 
         for i in 0..prim_count {
@@ -1653,16 +1699,17 @@ impl<'a> LayerRectCalculationAndCullingP
                 }
 
                 // Try to create a mask if we may need to.
                 if !self.current_clip_stack.is_empty() {
                     // If the primitive doesn't have a specific clip, key the task ID off the
                     // stacking context. This means that two primitives which are only clipped
                     // by the stacking context stack can share clip masks during render task
                     // assignment to targets.
+                    let node_clip_bounds = node_clip_bounds.unwrap_or_else(DeviceIntRect::zero);
                     let (mask_key, mask_rect) = match prim_clip_info {
                         Some(..) => (MaskCacheKey::Primitive(prim_index), prim_bounding_rect),
                         None => (MaskCacheKey::ScrollLayer(scroll_layer_id), node_clip_bounds)
                     };
                     let mask_opt =
                         RenderTask::new_mask(mask_rect, mask_key, &self.current_clip_stack);
                     match mask_opt {
                         MaskResult::Outside => { // Primitive is completely clipped out.
--- a/gfx/webrender/src/gpu_store.rs
+++ b/gfx/webrender/src/gpu_store.rs
@@ -15,16 +15,24 @@ pub struct GpuStoreAddress(pub i32);
 impl Add<i32> for GpuStoreAddress {
     type Output = GpuStoreAddress;
 
     fn add(self, other: i32) -> GpuStoreAddress {
         GpuStoreAddress(self.0 + other)
     }
 }
 
+impl Add<usize> for GpuStoreAddress {
+    type Output = GpuStoreAddress;
+
+    fn add(self, other: usize) -> GpuStoreAddress {
+        GpuStoreAddress(self.0 + other as i32)
+    }
+}
+
 pub trait GpuStoreLayout {
     fn image_format() -> ImageFormat;
 
     fn texture_width<T>() -> usize;
 
     fn texture_filter() -> TextureFilter;
 
     fn texel_size() -> usize {
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -12,17 +12,17 @@ use std::f32;
 use std::hash::BuildHasherDefault;
 use std::{i32, usize};
 use std::path::PathBuf;
 use std::sync::Arc;
 use tiling;
 use renderer::BlendMode;
 use webrender_traits::{Epoch, ColorF, PipelineId};
 use webrender_traits::{ImageFormat, NativeFontHandle};
-use webrender_traits::{ExternalImageId, ScrollLayerId};
+use webrender_traits::{ExternalImageData, ExternalImageId, ScrollLayerId};
 use webrender_traits::{ImageData};
 use webrender_traits::{DeviceUintRect};
 
 // An ID for a texture that is owned by the
 // texture cache module. This can include atlases
 // or standalone textures allocated via the
 // texture cache (e.g. if an image is too large
 // to be added to an atlas). The texture cache
@@ -38,17 +38,17 @@ pub struct CacheTextureId(pub usize);
 // pipeline until they reach the rendering
 // thread, where they are resolved to a
 // native texture ID.
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 pub enum SourceTexture {
     Invalid,
     TextureCache(CacheTextureId),
-    External(ExternalImageId),
+    External(ExternalImageData),
     #[cfg_attr(not(feature = "webgl"), allow(dead_code))]
     /// This is actually a gl::GLuint, with the shared texture id between the
     /// main context and the WebGL context.
     WebGL(u32),
 }
 
 const COLOR_FLOAT_TO_FIXED: f32 = 255.0;
 const COLOR_FLOAT_TO_FIXED_WIDE: f32 = 65535.0;
@@ -382,18 +382,18 @@ pub enum LowLevelFilterOp {
     Invert(Au),
     Opacity(Au),
     Saturate(Au),
     Sepia(Au),
 }
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 pub enum HardwareCompositeOp {
-    Alpha,
+    PremultipliedAlpha,
 }
 
 impl HardwareCompositeOp {
     pub fn to_blend_mode(&self) -> BlendMode {
         match self {
-            &HardwareCompositeOp::Alpha => BlendMode::Alpha,
+            &HardwareCompositeOp::PremultipliedAlpha => BlendMode::PremultipliedAlpha,
         }
     }
 }
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -1,14 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-//#![feature(mpsc_select)]
-
 //! A GPU based Webrender.
 //!
 //! 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/)
 //!
@@ -19,33 +17,29 @@
 //! 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.
 //!
 //! What is referred to as a `frame`, is the current geometry on the screen.
-//! A new Frame is created by calling [set_root_stacking_context()][newframe] on the `RenderApi`.
+//! A new Frame is created by calling [`set_display_list()`][newframe] on the `RenderApi`.
 //! When the geometry is processed, the application will be informed via a `RenderNotifier`,
 //! a callback which you employ with [set_render_notifier][notifier] on the `Renderer`
 //! More information about [stacking contexts][stacking_contexts].
 //!
-//! `set_root_stacking_context()` also needs to be supplied with `BuiltDisplayList`s.
+//! `set_display_list()` also needs to be supplied with `BuiltDisplayList`s.
 //! These are obtained by finalizing a `DisplayListBuilder`. These are used to draw your geometry.
 //! But it doesn't only contain trivial geometry, it can also store another StackingContext, as
 //! they're nestable.
 //!
-//! Remember to insert the DisplayListId into the StackingContext as well, as they'll be referenced
-//! from there. An Id for your DisplayList can be obtained by calling
-//! `yourRenderApiInstance.next_display_list_id();`
-//!
 //! [stacking_contexts]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
-//! [newframe]: ../webrender_traits/struct.RenderApi.html#method.set_root_stacking_context
-//! [notifier]: struct.Renderer.html#method.set_render_notifier
+//! [newframe]: ../webrender_traits/struct.RenderApi.html#method.set_display_list
+//! [notifier]: renderer/struct.Renderer.html#method.set_render_notifier
 
 #[macro_use]
 extern crate lazy_static;
 #[macro_use]
 extern crate log;
 #[macro_use]
 extern crate bitflags;
 #[macro_use]
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -3,168 +3,281 @@
  * 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 webrender_traits::{AuxiliaryLists, BorderRadius, ClipRegion, ComplexClipRegion, ImageMask};
-use webrender_traits::{DeviceIntRect, DeviceIntSize, LayerRect, LayerToWorldTransform};
+use webrender_traits::{DeviceIntRect, LayerToWorldTransform};
+use webrender_traits::{LayerRect, LayerPoint, LayerSize};
+
+const MAX_CLIP: f32 = 1000000.0;
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ClipMode {
+    Clip,           // Pixels inside the region are visible.
+    ClipOut,        // Pixels outside the region are visible.
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum RegionMode {
+    IncludeRect,
+    ExcludeRect,
+}
 
 #[derive(Clone, Debug)]
 pub enum ClipSource {
-    NoClip,
-    Complex(LayerRect, f32),
-    Region(ClipRegion),
+    Complex(LayerRect, f32, ClipMode),
+    // The RegionMode here specifies whether to consider the rect
+    // 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::NoClip => None,
             &ClipSource::Complex(..) => None,
-            &ClipSource::Region(ref region) => region.image_mask,
+            &ClipSource::Region(ref region, _) => region.image_mask,
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub struct ClipAddressRange {
     pub start: GpuStoreAddress,
-    item_count: u32,
+    item_count: usize,
+}
+
+/// Represents a local rect and a device space
+/// bounding rect that can be updated when the
+/// transform changes.
+#[derive(Clone, Debug, PartialEq)]
+pub struct Geometry {
+    pub local_rect: LayerRect,
+    pub bounding_rect: DeviceIntRect,
+}
+
+impl Geometry {
+    fn new(local_rect: LayerRect) -> Geometry {
+        Geometry {
+            local_rect: local_rect,
+            bounding_rect: DeviceIntRect::zero(),
+        }
+    }
+
+    fn update(&mut self,
+              transform: &LayerToWorldTransform,
+              device_pixel_ratio: f32) {
+        let transformed = TransformedRect::new(&self.local_rect,
+                                               &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
+/// bounds to be unknown. This is conservative, but ensures
+/// correctness. In the future we can make this a lot
+/// more clever with some proper region handling.
+#[derive(Clone, Debug, PartialEq)]
+pub enum MaskBounds {
+    /// We know both the outer and inner rect. This is the
+    /// fast path for, e.g. a simple rounded rect.
+    OuterInner(Geometry, Geometry),
+    /// We know the outer rect only.
+    Outer(Geometry),
+    /// We can't determine the bounds - draw mask over entire rect.
+    /// This is currently used for clip-out operations on
+    /// box shadows.
+    None,
 }
 
 #[derive(Clone, Debug)]
 pub struct MaskCacheInfo {
     pub clip_range: ClipAddressRange,
-    pub effective_clip_count: u32,
+    pub effective_clip_count: usize,
     pub image: Option<(ImageMask, GpuStoreAddress)>,
-    pub local_rect: Option<LayerRect>,
-    pub local_inner: Option<LayerRect>,
-    pub inner_rect: DeviceIntRect,
-    pub outer_rect: DeviceIntRect,
+    pub bounds: Option<MaskBounds>,
     pub is_aligned: bool,
 }
 
 impl MaskCacheInfo {
     /// Create a new mask cache info. It allocates the GPU store data but leaves
     /// it unitialized for the following `update()` call to deal with.
-    pub fn new(source: &ClipSource,
-               extra_clip: bool,
+    pub fn new(clips: &[ClipSource],
                clip_store: &mut VertexDataStore<GpuBlock32>)
                -> Option<MaskCacheInfo> {
-        let (image, clip_range) = match source {
-            &ClipSource::NoClip => return None,
-            &ClipSource::Complex(..) => {
-                (None,
-                ClipAddressRange {
-                    start: clip_store.alloc(CLIP_DATA_GPU_SIZE),
-                    item_count: 1,
-                })
+        if clips.is_empty() {
+            return None;
+        }
+
+        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(..) => {
+                    clip_count += 1;
+                },
+                &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;
+                    }
+                },
+            }
+        }
+
+        let clip_range = ClipAddressRange {
+            start: if clip_count > 0 {
+                clip_store.alloc(CLIP_DATA_GPU_SIZE * clip_count)
+            } else {
+                GpuStoreAddress(0)
             },
-            &ClipSource::Region(ref region) => {
-                let count = region.complex.length + if extra_clip {1} else {0};
-                (region.image_mask.map(|info|
-                    (info, clip_store.alloc(MASK_DATA_GPU_SIZE))),
-                ClipAddressRange {
-                    start: if count > 0 {
-                        clip_store.alloc(CLIP_DATA_GPU_SIZE * count)
-                    } else {
-                        GpuStoreAddress(0)
-                    },
-                    item_count: count as u32,
-                })
-            },
+            item_count: clip_count,
         };
 
         Some(MaskCacheInfo {
             clip_range: clip_range,
             effective_clip_count: clip_range.item_count,
             image: image,
-            local_rect: None,
-            local_inner: None,
-            inner_rect: DeviceIntRect::zero(),
-            outer_rect: DeviceIntRect::zero(),
+            bounds: None,
             is_aligned: true,
         })
     }
 
     pub fn update(&mut self,
-                  source: &ClipSource,
+                  sources: &[ClipSource],
                   transform: &LayerToWorldTransform,
                   clip_store: &mut VertexDataStore<GpuBlock32>,
                   device_pixel_ratio: f32,
                   aux_lists: &AuxiliaryLists) {
+        let is_aligned = transform.can_losslessly_transform_and_perspective_project_a_2d_rect();
 
-        self.is_aligned = transform.can_losslessly_transform_and_perspective_project_a_2d_rect();
+        // If we haven't cached this info, or if the transform type has changed
+        // we need to re-calculate the number of clips.
+        if self.bounds.is_none() || self.is_aligned != is_aligned {
+            let mut local_rect = Some(LayerRect::new(LayerPoint::new(-MAX_CLIP, -MAX_CLIP),
+                                                     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) => {
+                        // 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;
 
-        if self.local_rect.is_none() {
-            let mut local_rect;
-            let mut local_inner: Option<LayerRect>;
-            match source {
-                &ClipSource::NoClip => unreachable!(),
-                &ClipSource::Complex(rect, radius) => {
-                    let slice = clip_store.get_slice_mut(self.clip_range.start, CLIP_DATA_GPU_SIZE);
-                    let data = ClipData::uniform(rect, radius);
-                    PrimitiveStore::populate_clip_data(slice, data);
-                    debug_assert_eq!(self.clip_range.item_count, 1);
-                    local_rect = Some(rect);
-                    local_inner = ComplexClipRegion::new(rect, BorderRadius::uniform(radius))
-                                                    .get_inner_rect();
-                }
-                &ClipSource::Region(ref region) => {
-                    local_rect = Some(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,
-                    };
+                        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();
+                    }
+                    &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,
+                        };
 
-                    let clips = aux_lists.complex_clip_regions(&region.complex);
-                    self.effective_clip_count = if !self.is_aligned && self.clip_range.item_count > clips.len() as u32 {
-                        // we have an extra clip rect coming from the transformed layer
-                        assert_eq!(self.clip_range.item_count, clips.len() as u32 + 1);
-                        let address = GpuStoreAddress(self.clip_range.start.0 + (CLIP_DATA_GPU_SIZE * clips.len()) as i32);
-                        let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE);
-                        PrimitiveStore::populate_clip_data(slice, ClipData::uniform(region.main, 0.0));
-                        self.clip_range.item_count
-                    } else {
-                        clips.len() as u32
-                    };
+                        let clips = aux_lists.complex_clip_regions(&region.complex);
+                        if !self.is_aligned && region_mode == RegionMode::IncludeRect {
+                            // we have an extra clip rect coming from the transformed layer
+                            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);
+                            PrimitiveStore::populate_clip_data(slice, ClipData::uniform(region.main, 0.0, ClipMode::Clip));
+                        }
 
-                    let slice = clip_store.get_slice_mut(self.clip_range.start, 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)));
+                        debug_assert!(self.effective_clip_count + clips.len() <= self.clip_range.item_count);
+                        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)));
+                        }
                     }
                 }
-            };
-            self.local_rect = Some(local_rect.unwrap_or(LayerRect::zero()));
-            self.local_inner = local_inner;
+            }
+
+            // Work out the type of mask geometry we have, based on the
+            // list of clip sources above.
+            if has_clip_out {
+                // For clip-out, the mask rect is not known.
+                self.bounds = Some(MaskBounds::None);
+            } else {
+                // TODO(gw): local inner is only valid if there's a single clip (for now).
+                // This can be improved in the future, with some proper
+                // rectangle region handling.
+                if sources.len() > 1 {
+                    local_inner = None;
+                }
+
+                let local_rect = local_rect.unwrap_or(LayerRect::zero());
+
+                self.bounds = match local_inner {
+                    Some(local_inner) => {
+                        Some(MaskBounds::OuterInner(Geometry::new(local_rect),
+                                                    Geometry::new(local_inner)))
+                    }
+                    None => {
+                        Some(MaskBounds::Outer(Geometry::new(local_rect)))
+                    }
+                };
+            }
         }
 
-        let transformed = TransformedRect::new(self.local_rect.as_ref().unwrap(),
-                                               &transform,
-                                               device_pixel_ratio);
-        self.outer_rect = transformed.bounding_rect;
-
-        self.inner_rect = if let Some(ref inner_rect) = self.local_inner {
-            let transformed = TransformedRect::new(inner_rect,
-                                                   &transform,
-                                                   device_pixel_ratio);
-            transformed.inner_rect
-        } else {
-            DeviceIntRect::new(self.outer_rect.origin, DeviceIntSize::zero())
+        // Update the device space bounding rects of the mask
+        // geometry.
+        match self.bounds.as_mut().unwrap() {
+            &mut MaskBounds::None => {}
+            &mut MaskBounds::Outer(ref mut outer) => {
+                outer.update(transform, device_pixel_ratio);
+            }
+            &mut MaskBounds::OuterInner(ref mut outer, ref mut inner) => {
+                outer.update(transform, device_pixel_ratio);
+                inner.update(transform, device_pixel_ratio);
+            }
         }
     }
 
     /// Check if this `MaskCacheInfo` actually carries any masks. `effective_clip_count`
     /// can change during the `update` call depending on the transformation, so the mask may
     /// appear to be empty.
     pub fn is_masking(&self) -> bool {
         self.image.is_some() || self.effective_clip_count != 0
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -41,19 +41,17 @@ fn float_to_fixed_ft(f: f64) -> i32 {
     float_to_fixed(6, f)
 }
 
 impl FontContext {
     pub fn new() -> FontContext {
         let mut lib: FT_Library = ptr::null_mut();
         unsafe {
             let result = FT_Init_FreeType(&mut lib);
-            if !result.succeeded() {
-                panic!("Unable to initialize FreeType library {:?}", result);
-            }
+            assert!(result.succeeded(), "Unable to initialize FreeType library {:?}", result);
 
             // TODO(gw): Check result of this to determine if freetype build supports subpixel.
             let result = FT_Library_SetLcdFilter(lib, FT_LcdFilter::FT_LCD_FILTER_DEFAULT);
             if !result.succeeded() {
                 println!("WARN: Initializing a FreeType library build without subpixel AA enabled!");
             }
         }
 
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.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 app_units::Au;
 use euclid::{Point2D, Size2D};
 use gpu_store::GpuStoreAddress;
 use internal_types::{SourceTexture, PackedTexel};
-use mask_cache::{ClipSource, MaskCacheInfo};
+use mask_cache::{ClipMode, ClipSource, MaskCacheInfo};
 use renderer::{VertexDataStore, GradientDataStore};
 use render_task::{RenderTask, RenderTaskLocation};
 use resource_cache::{CacheItem, ImageProperties, ResourceCache};
 use std::mem;
 use std::usize;
 use util::TransformedRect;
 use webrender_traits::{AuxiliaryLists, ColorF, ImageKey, ImageRendering, YuvColorSpace};
 use webrender_traits::{ClipRegion, ComplexClipRegion, ItemRange, GlyphKey};
@@ -104,17 +104,17 @@ pub enum PrimitiveCacheKey {
     BoxShadow(BoxShadowPrimitiveCacheKey),
     TextShadow(PrimitiveIndex),
 }
 
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
 pub struct PrimitiveMetadata {
     pub is_opaque: bool,
-    pub clip_source: Box<ClipSource>,
+    pub clips: Vec<ClipSource>,
     pub clip_cache_info: Option<MaskCacheInfo>,
     pub prim_kind: PrimitiveKind,
     pub cpu_prim_index: SpecificPrimitiveIndex,
     pub gpu_prim_index: GpuStoreAddress,
     pub gpu_data_address: GpuStoreAddress,
     pub gpu_data_count: i32,
     // An optional render task that is a dependency of
     // drawing this primitive. For instance, box shadows
@@ -244,18 +244,18 @@ pub struct GradientPrimitiveCpu {
 
 #[derive(Debug, Clone)]
 #[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 padding: [f32; 1],
 }
 
 #[derive(Debug)]
 pub struct RadialGradientPrimitiveCpu {
     pub stops_range: ItemRange,
     pub extend_mode: ExtendMode,
     pub cache_dirty: bool,
 }
@@ -410,17 +410,18 @@ struct GlyphPrimitive {
     offset: LayerPoint,
     padding: LayerPoint,
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 struct ClipRect {
     rect: LayerRect,
-    padding: [f32; 4],
+    mode: f32,
+    padding: [f32; 3],
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 struct ClipCorner {
     rect: LayerRect,
     outer_radius_x: f32,
     outer_radius_y: f32,
@@ -456,17 +457,19 @@ pub struct ClipData {
     bottom_right: ClipCorner,
 }
 
 impl ClipData {
     pub fn from_clip_region(clip: &ComplexClipRegion) -> ClipData {
         ClipData {
             rect: ClipRect {
                 rect: clip.rect,
-                padding: [0.0, 0.0, 0.0, 0.0],
+                padding: [0.0; 3],
+                // TODO(gw): Support other clip modes for regions?
+                mode: ClipMode::Clip as u32 as f32,
             },
             top_left: ClipCorner {
                 rect: LayerRect::new(
                     LayerPoint::new(clip.rect.origin.x, clip.rect.origin.y),
                     LayerSize::new(clip.radii.top_left.width, clip.radii.top_left.height)),
                 outer_radius_x: clip.radii.top_left.width,
                 outer_radius_y: clip.radii.top_left.height,
                 inner_radius_x: 0.0,
@@ -498,21 +501,22 @@ impl ClipData {
                 outer_radius_x: clip.radii.bottom_right.width,
                 outer_radius_y: clip.radii.bottom_right.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
         }
     }
 
-    pub fn uniform(rect: LayerRect, radius: f32) -> ClipData {
+    pub fn uniform(rect: LayerRect, radius: f32, mode: ClipMode) -> ClipData {
         ClipData {
             rect: ClipRect {
                 rect: rect,
-                padding: [0.0; 4],
+                padding: [0.0; 3],
+                mode: mode as u32 as f32,
             },
             top_left: ClipCorner::uniform(
                 LayerRect::new(
                     LayerPoint::new(rect.origin.x, rect.origin.y),
                     LayerSize::new(radius, radius)),
                 radius, 0.0),
             top_right: ClipCorner::uniform(
                 LayerRect::new(
@@ -599,31 +603,31 @@ impl PrimitiveStore {
         data[1] = GpuBlock32::from(clip.top_left);
         data[2] = GpuBlock32::from(clip.top_right);
         data[3] = GpuBlock32::from(clip.bottom_left);
         data[4] = GpuBlock32::from(clip.bottom_right);
     }
 
     pub fn add_primitive(&mut self,
                          geometry: PrimitiveGeometry,
-                         clip_source: Box<ClipSource>,
+                         clips: Vec<ClipSource>,
                          clip_info: Option<MaskCacheInfo>,
                          container: PrimitiveContainer) -> PrimitiveIndex {
         let prim_index = self.cpu_metadata.len();
         self.cpu_bounding_rects.push(None);
         self.gpu_geometry.push(geometry);
 
         let metadata = match container {
             PrimitiveContainer::Rectangle(rect) => {
                 let is_opaque = rect.color.a == 1.0;
                 let gpu_address = self.gpu_data16.push(rect);
 
                 let metadata = PrimitiveMetadata {
                     is_opaque: is_opaque,
-                    clip_source: clip_source,
+                    clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Rectangle,
                     cpu_prim_index: SpecificPrimitiveIndex::invalid(),
                     gpu_prim_index: gpu_address,
                     gpu_data_address: GpuStoreAddress(0),
                     gpu_data_count: 0,
                     render_task: None,
                     clip_task: None,
@@ -633,17 +637,17 @@ impl PrimitiveStore {
             }
             PrimitiveContainer::TextRun(mut text_cpu, text_gpu) => {
                 let gpu_address = self.gpu_data16.push(text_gpu);
                 let gpu_glyphs_address = self.gpu_data16.alloc(text_cpu.glyph_range.length);
                 text_cpu.resource_address = self.gpu_resource_rects.alloc(text_cpu.glyph_range.length);
 
                 let metadata = PrimitiveMetadata {
                     is_opaque: false,
-                    clip_source: clip_source,
+                    clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::TextRun,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()),
                     gpu_prim_index: gpu_address,
                     gpu_data_address: gpu_glyphs_address,
                     gpu_data_count: text_cpu.glyph_range.length as i32,
                     render_task: None,
                     clip_task: None,
@@ -654,17 +658,17 @@ impl PrimitiveStore {
             }
             PrimitiveContainer::Image(mut image_cpu, image_gpu) => {
                 image_cpu.resource_address = self.gpu_resource_rects.alloc(1);
 
                 let gpu_address = self.gpu_data16.push(image_gpu);
 
                 let metadata = PrimitiveMetadata {
                     is_opaque: false,
-                    clip_source: clip_source,
+                    clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Image,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_images.len()),
                     gpu_prim_index: gpu_address,
                     gpu_data_address: GpuStoreAddress(0),
                     gpu_data_count: 0,
                     render_task: None,
                     clip_task: None,
@@ -675,17 +679,17 @@ impl PrimitiveStore {
             }
             PrimitiveContainer::YuvImage(mut image_cpu, image_gpu) => {
                 image_cpu.yuv_resource_address = self.gpu_resource_rects.alloc(3);
 
                 let gpu_address = self.gpu_data16.push(image_gpu);
 
                 let metadata = PrimitiveMetadata {
                     is_opaque: true,
-                    clip_source: clip_source,
+                    clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::YuvImage,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_yuv_images.len()),
                     gpu_prim_index: gpu_address,
                     gpu_data_address: GpuStoreAddress(0),
                     gpu_data_count: 0,
                     render_task: None,
                     clip_task: None,
@@ -694,17 +698,17 @@ impl PrimitiveStore {
                 self.cpu_yuv_images.push(image_cpu);
                 metadata
             }
             PrimitiveContainer::Border(border_cpu, border_gpu) => {
                 let gpu_address = self.gpu_data128.push(border_gpu);
 
                 let metadata = PrimitiveMetadata {
                     is_opaque: false,
-                    clip_source: clip_source,
+                    clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::Border,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_borders.len()),
                     gpu_prim_index: gpu_address,
                     gpu_data_address: GpuStoreAddress(0),
                     gpu_data_count: 0,
                     render_task: None,
                     clip_task: None,
@@ -715,17 +719,17 @@ impl PrimitiveStore {
             }
             PrimitiveContainer::AlignedGradient(gradient_cpu, gradient_gpu) => {
                 let gpu_address = self.gpu_data32.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,
-                    clip_source: clip_source,
+                    clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::AlignedGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     gpu_prim_index: gpu_address,
                     gpu_data_address: gpu_stops_address,
                     gpu_data_count: gradient_cpu.stops_range.length as i32,
                     render_task: None,
                     clip_task: None,
@@ -736,17 +740,17 @@ impl PrimitiveStore {
             }
             PrimitiveContainer::AngleGradient(gradient_cpu, gradient_gpu) => {
                 let gpu_address = self.gpu_data32.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,
-                    clip_source: clip_source,
+                    clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::AngleGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_gradients.len()),
                     gpu_prim_index: gpu_address,
                     gpu_data_address: gpu_gradient_address,
                     gpu_data_count: 1,
                     render_task: None,
                     clip_task: None,
@@ -757,17 +761,17 @@ impl PrimitiveStore {
             }
             PrimitiveContainer::RadialGradient(radial_gradient_cpu, radial_gradient_gpu) => {
                 let gpu_address = self.gpu_data32.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,
-                    clip_source: clip_source,
+                    clips: clips,
                     clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::RadialGradient,
                     cpu_prim_index: SpecificPrimitiveIndex(self.cpu_radial_gradients.len()),
                     gpu_prim_index: gpu_address,
                     gpu_data_address: gpu_gradient_address,
                     gpu_data_count: 1,
                     render_task: None,
                     clip_task: None,
@@ -802,18 +806,18 @@ impl PrimitiveStore {
                                                              cache_size,
                                                              PrimitiveIndex(prim_index));
 
                 let gpu_prim_address = self.gpu_data64.push(box_shadow_gpu);
                 let gpu_data_address = self.gpu_data16.get_next_address();
 
                 let metadata = PrimitiveMetadata {
                     is_opaque: false,
-                    clip_source: clip_source,
-                    clip_cache_info: None,
+                    clips: clips,
+                    clip_cache_info: clip_info,
                     prim_kind: PrimitiveKind::BoxShadow,
                     cpu_prim_index: SpecificPrimitiveIndex::invalid(),
                     gpu_prim_index: gpu_prim_address,
                     gpu_data_address: gpu_data_address,
                     gpu_data_count: instance_rects.len() as i32,
                     render_task: Some(render_task),
                     clip_task: None,
                 };
@@ -859,27 +863,27 @@ impl PrimitiveStore {
                      image_key: ImageKey,
                      image_uv_address: GpuStoreAddress,
                      image_rendering: ImageRendering,
                      tile_offset: Option<TileOffset>) -> (SourceTexture, Option<CacheItem>) {
         let image_properties = resource_cache.get_image_properties(image_key);
 
         // Check if an external image that needs to be resolved
         // by the render thread.
-        match image_properties.external_id {
-            Some(external_id) => {
+        match image_properties.external_image {
+            Some(external_image) => {
                 // This is an external texture - we will add it to
                 // the deferred resolves list to be patched by
                 // the render thread...
                 deferred_resolves.push(DeferredResolve {
                     image_properties: image_properties,
                     resource_address: image_uv_address,
                 });
 
-                (SourceTexture::External(external_id), None)
+                (SourceTexture::External(external_image), None)
             }
             None => {
                 let cache_item = resource_cache.get_cached_image(image_key, image_rendering, tile_offset);
                 (cache_item.texture_id, Some(cache_item))
             }
         }
     }
 
@@ -989,31 +993,35 @@ impl PrimitiveStore {
                     }
                 }
             }
         }
 
         deferred_resolves
     }
 
-    pub fn set_clip_source(&mut self, index: PrimitiveIndex, source: ClipSource) {
+    pub fn set_clip_source(&mut self, index: PrimitiveIndex, source: Option<ClipSource>) {
         let metadata = &mut self.cpu_metadata[index.0];
-        let (rect, is_complex) = match source {
-            ClipSource::NoClip => (None, false),
-            ClipSource::Complex(rect, radius) => (Some(rect), radius > 0.0),
-            ClipSource::Region(ref region) => (Some(region.main), region.is_complex()),
-        };
-        if let Some(rect) = rect {
-            self.gpu_geometry.get_mut(GpuStoreAddress(index.0 as i32))
-                .local_clip_rect = rect;
-            if is_complex {
-                metadata.clip_cache_info = None; //CLIP TODO: re-use the existing GPU allocation
+        metadata.clips = match source {
+            Some(source) => {
+                let (rect, is_complex) = match source {
+                    ClipSource::Complex(rect, radius, _) => (rect, radius > 0.0),
+                    ClipSource::Region(ref region, _) => (region.main, region.is_complex()),
+                };
+                self.gpu_geometry.get_mut(GpuStoreAddress(index.0 as i32))
+                    .local_clip_rect = rect;
+                if is_complex {
+                    metadata.clip_cache_info = None; //CLIP TODO: re-use the existing GPU allocation
+                }
+                vec![source]
+            }
+            None => {
+                vec![]
             }
         }
-        *metadata.clip_source.as_mut() = source;
     }
 
     pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata {
         &self.cpu_metadata[index.0]
     }
 
     pub fn prim_count(&self) -> usize {
         self.cpu_metadata.len()
@@ -1049,24 +1057,26 @@ impl PrimitiveStore {
                                    device_pixel_ratio: f32,
                                    auxiliary_lists: &AuxiliaryLists) -> bool {
 
         let metadata = &mut self.cpu_metadata[prim_index.0];
         let mut prim_needs_resolve = false;
         let mut rebuild_bounding_rect = false;
 
         if let Some(ref mut clip_info) = metadata.clip_cache_info {
-            clip_info.update(&metadata.clip_source,
+            clip_info.update(&metadata.clips,
                              layer_transform,
                              &mut self.gpu_data32,
                              device_pixel_ratio,
                              auxiliary_lists);
-            if let &ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }) = metadata.clip_source.as_ref() {
-                resource_cache.request_image(mask.image, ImageRendering::Auto, None);
-                prim_needs_resolve = true;
+            for clip in &metadata.clips {
+                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 |
             PrimitiveKind::Border  => {}
             PrimitiveKind::BoxShadow => {
                 // TODO(gw): Account for zoom factor!
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -1,13 +1,13 @@
 /* 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 bincode::{SizeLimit, serialize};
+use bincode::{Infinite, serialize};
 use std::fmt::Debug;
 use std::mem;
 use std::any::TypeId;
 use std::fs::File;
 use std::io::Write;
 use std::path::PathBuf;
 use webrender_traits::ApiMsg;
 use byteorder::{LittleEndian, WriteBytesExt};
@@ -45,17 +45,17 @@ impl BinaryRecorder {
         self.file.write_u32::<LittleEndian>(data.len() as u32).ok();
         self.file.write(data).ok();
     }
 }
 
 impl ApiRecordingReceiver for BinaryRecorder {
     fn write_msg(&mut self, _: u32, msg: &ApiMsg) {
         if should_record_msg(msg) {
-            let buf = serialize(msg, SizeLimit::Infinite).unwrap();
+            let buf = serialize(msg, Infinite).unwrap();
             self.write_length_and_data(&buf);
         }
     }
 
     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);
@@ -66,17 +66,17 @@ pub fn should_record_msg(msg: &ApiMsg) -
     match msg {
         &ApiMsg::AddRawFont(..) |
         &ApiMsg::AddNativeFont(..) |
         &ApiMsg::DeleteFont(..) |
         &ApiMsg::AddImage(..) |
         &ApiMsg::GenerateFrame(..) |
         &ApiMsg::UpdateImage(..) |
         &ApiMsg::DeleteImage(..) |
-        &ApiMsg::SetRootDisplayList(..) |
+        &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
@@ -1,32 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use byteorder::{LittleEndian, ReadBytesExt};
 use frame::Frame;
 use frame_builder::FrameBuilderConfig;
 use internal_types::{FontTemplate, SourceTexture, ResultMsg, RendererFrame};
 use profiler::{BackendProfileCounters, TextureCacheProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
 use std::collections::HashMap;
-use std::io::{Cursor, Read};
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use texture_cache::TextureCache;
 use thread_profiler::register_thread_with_profiler;
 use threadpool::ThreadPool;
 use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
 use webrender_traits::{DeviceIntPoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize, LayerPoint};
 use webrender_traits::{ApiMsg, AuxiliaryLists, BuiltDisplayList, IdNamespace, ImageData};
 use webrender_traits::{PipelineId, RenderNotifier, RenderDispatcher, WebGLCommand, WebGLContextId};
-use webrender_traits::channel::{PayloadHelperMethods, PayloadReceiver, PayloadSender, MsgReceiver};
+use webrender_traits::channel::{PayloadSenderHelperMethods, PayloadReceiverHelperMethods, PayloadReceiver, PayloadSender, MsgReceiver};
 use webrender_traits::{BlobImageRenderer, VRCompositorCommand, VRCompositorHandler};
 #[cfg(feature = "webgl")]
 use offscreen_gl_context::GLContextDispatcher;
 #[cfg(not(feature = "webgl"))]
 use webgl_types::GLContextDispatcher;
 
 /// The render backend is responsible for transforming high level display lists into
 /// GPU-friendly work which is then submitted to the renderer in the form of a frame::Frame.
@@ -172,80 +170,74 @@ impl RenderBackend {
                         ApiMsg::CloneApi(sender) => {
                             let result = self.next_namespace_id;
 
                             let IdNamespace(id_namespace) = self.next_namespace_id;
                             self.next_namespace_id = IdNamespace(id_namespace + 1);
 
                             sender.send(result).unwrap();
                         }
-                        ApiMsg::SetRootDisplayList(background_color,
-                                                   epoch,
-                                                   pipeline_id,
-                                                   viewport_size,
-                                                   display_list_descriptor,
-                                                   auxiliary_lists_descriptor,
-                                                   preserve_frame_state) => {
-                            profile_scope!("SetRootDisplayList");
+                        ApiMsg::SetDisplayList(background_color,
+                                               epoch,
+                                               pipeline_id,
+                                               viewport_size,
+                                               display_list_descriptor,
+                                               auxiliary_lists_descriptor,
+                                               preserve_frame_state) => {
+                            profile_scope!("SetDisplayList");
                             let mut leftover_auxiliary_data = vec![];
                             let mut auxiliary_data;
                             loop {
-                                auxiliary_data = self.payload_rx.recv().unwrap();
+                                auxiliary_data = self.payload_rx.recv_payload().unwrap();
                                 {
-                                    let mut payload_reader = Cursor::new(&auxiliary_data[..]);
-                                    let payload_epoch =
-                                        payload_reader.read_u32::<LittleEndian>().unwrap();
-                                    if payload_epoch == epoch.0 {
+                                    if auxiliary_data.epoch == epoch &&
+                                       auxiliary_data.pipeline_id == pipeline_id {
                                         break
                                     }
                                 }
                                 leftover_auxiliary_data.push(auxiliary_data)
                             }
                             for leftover_auxiliary_data in leftover_auxiliary_data {
-                                self.payload_tx.send_vec(leftover_auxiliary_data).unwrap()
+                                self.payload_tx.send_payload(leftover_auxiliary_data).unwrap()
                             }
                             if let Some(ref mut r) = self.recorder {
-                                r.write_payload(frame_counter, &auxiliary_data);
+                                r.write_payload(frame_counter, &auxiliary_data.to_data());
                             }
 
-                            let mut auxiliary_data = Cursor::new(&mut auxiliary_data[4..]);
-                            let mut built_display_list_data =
-                                vec![0; display_list_descriptor.size()];
-                            auxiliary_data.read_exact(&mut built_display_list_data[..]).unwrap();
                             let built_display_list =
-                                BuiltDisplayList::from_data(built_display_list_data,
+                                BuiltDisplayList::from_data(auxiliary_data.display_list_data,
                                                             display_list_descriptor);
-                            let mut auxiliary_lists_data =
-                                vec![0; auxiliary_lists_descriptor.size()];
-                            auxiliary_data.read_exact(&mut auxiliary_lists_data[..]).unwrap();
                             let auxiliary_lists =
-                                AuxiliaryLists::from_data(auxiliary_lists_data,
+                                AuxiliaryLists::from_data(auxiliary_data.auxiliary_lists_data,
                                                           auxiliary_lists_descriptor);
 
                             if !preserve_frame_state {
                                 self.discard_frame_state_for_pipeline(pipeline_id);
                             }
-
-                            self.scene.set_root_display_list(pipeline_id,
-                                                             epoch,
-                                                             built_display_list,
-                                                             background_color,
-                                                             viewport_size,
-                                                             auxiliary_lists);
-                            self.build_scene();
+                            profile_counters.total_time.profile(|| {
+                                self.scene.set_display_list(pipeline_id,
+                                                            epoch,
+                                                            built_display_list,
+                                                            background_color,
+                                                            viewport_size,
+                                                            auxiliary_lists);
+                                self.build_scene();
+                            })
                         }
                         ApiMsg::SetRootPipeline(pipeline_id) => {
                             profile_scope!("SetRootPipeline");
                             self.scene.set_root_pipeline_id(pipeline_id);
 
                             if self.scene.display_lists.get(&pipeline_id).is_none() {
                                 continue;
                             }
 
-                            self.build_scene();
+                            profile_counters.total_time.profile(|| {
+                                self.build_scene();
+                            })
                         }
                         ApiMsg::Scroll(delta, cursor, move_phase) => {
                             profile_scope!("Scroll");
                             let frame = {
                                 let counters = &mut profile_counters.texture_cache;
                                 profile_counters.total_time.profile(|| {
                                     if self.frame.scroll(delta, cursor, move_phase) {
                                         Some(self.render(counters))
@@ -378,17 +370,19 @@ impl RenderBackend {
                             // just rebuilds the frame if there are animated property
                             // bindings present for now.
                             // TODO(gw): Once the scrolling / reference frame changes
                             //           are completed, optimize the internals of
                             //           animated properties to not require a full
                             //           rebuild of the frame!
                             if let Some(property_bindings) = property_bindings {
                                 self.scene.properties.set_properties(property_bindings);
-                                self.build_scene();
+                                profile_counters.total_time.profile(|| {
+                                    self.build_scene();
+                                });
                             }
 
                             let frame = {
                                 let counters = &mut profile_counters.texture_cache;
                                 profile_counters.total_time.profile(|| {
                                     self.render(counters)
                                 })
                             };
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
-use mask_cache::MaskCacheInfo;
+use mask_cache::{MaskBounds, MaskCacheInfo};
 use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, mem, usize};
 use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind, StackingContextIndex};
 use webrender_traits::{DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{MixBlendMode, ScrollLayerId};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
@@ -187,29 +187,54 @@ impl RenderTask {
                     -> MaskResult {
         if clips.is_empty() {
             return MaskResult::Outside;
         }
 
         // We scan through the clip stack and detect if our actual rectangle
         // 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.
-        let result = clips.iter()
-                          .fold(Some(actual_rect), |current, clip| {
-            current.and_then(|rect| rect.intersection(&clip.1.outer_rect))
-        });
+
+        // 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) => {
+                    result = result.and_then(|rect| {
+                        rect.intersection(&outer.bounding_rect)
+                    });
+                }
+                &MaskBounds::None => {
+                    result = Some(actual_rect);
+                    break;
+                }
+            }
+        }
 
         let task_rect = match result {
             None => return MaskResult::Outside,
             Some(rect) => rect,
         };
 
+        // 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| rect.intersection(&clip.1.inner_rect))
+            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
+                };
+                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.
         //           In the future, we'll expand this to handle the
         //           more complex types of clip mask geometry.
         let mut geometry_kind = MaskGeometryKind::Default;
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -37,39 +37,40 @@ 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};
+use tiling::{AlphaBatchKind, BlurCommand, Frame, PrimitiveBatch, PrimitiveBatchData, 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, ImageData, ImageFormat, RenderApiSender};
+use webrender_traits::{ExternalImageId, ExternalImageType, ImageData, ImageFormat, RenderApiSender};
 use webrender_traits::{DeviceIntRect, DevicePoint, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
 use webrender_traits::{ImageDescriptor, BlobImageRenderer};
 use webrender_traits::channel;
 use webrender_traits::VRCompositorHandler;
 
 pub const GPU_DATA_TEXTURE_POOL: usize = 5;
 pub const MAX_VERTEX_TEXTURE_WIDTH: usize = 1024;
 
 const GPU_TAG_CACHE_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "C_BoxShadow", color: debug_colors::BLACK };
 const GPU_TAG_CACHE_CLIP: GpuProfileTag = GpuProfileTag { label: "C_Clip", color: debug_colors::PURPLE };
 const GPU_TAG_CACHE_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "C_TextRun", color: debug_colors::MISTYROSE };
 const GPU_TAG_INIT: GpuProfileTag = GpuProfileTag { label: "Init", color: debug_colors::WHITE };
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag { label: "Target", color: debug_colors::SLATEGREY };
 const GPU_TAG_PRIM_RECT: GpuProfileTag = GpuProfileTag { label: "Rect", color: debug_colors::RED };
 const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag { label: "Image", color: debug_colors::GREEN };
+const GPU_TAG_PRIM_IMAGE_RECT: GpuProfileTag = GpuProfileTag { label: "ImageRect", color: debug_colors::GREENYELLOW };
 const GPU_TAG_PRIM_YUV_IMAGE: GpuProfileTag = GpuProfileTag { label: "YuvImage", color: debug_colors::DARKGREEN };
 const GPU_TAG_PRIM_BLEND: GpuProfileTag = GpuProfileTag { label: "Blend", color: debug_colors::LIGHTBLUE };
 const GPU_TAG_PRIM_HW_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "HwComposite", color: debug_colors::DODGERBLUE };
 const GPU_TAG_PRIM_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "Composite", color: debug_colors::MAGENTA };
 const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "TextRun", color: debug_colors::BLUE };
 const GPU_TAG_PRIM_GRADIENT: GpuProfileTag = GpuProfileTag { label: "Gradient", color: debug_colors::YELLOW };
 const GPU_TAG_PRIM_ANGLE_GRADIENT: GpuProfileTag = GpuProfileTag { label: "AngleGradient", color: debug_colors::POWDERBLUE };
 const GPU_TAG_PRIM_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag { label: "RadialGradient", color: debug_colors::LIGHTPINK };
@@ -218,16 +219,17 @@ impl GpuStoreLayout for GradientDataText
 }
 
 type GradientDataTexture = GpuDataTexture<GradientDataTextureLayout>;
 pub type GradientDataStore = GpuStore<GradientData, GradientDataTextureLayout>;
 
 const TRANSFORM_FEATURE: &'static str = "TRANSFORM";
 const SUBPIXEL_AA_FEATURE: &'static str = "SUBPIXEL_AA";
 const CLIP_FEATURE: &'static str = "CLIP";
+const TEXTURE_RECT_FEATURE: &'static str = "TEXTURE_RECT";
 
 enum ShaderKind {
     Primitive,
     Cache(VertexFormat),
     ClipCache,
 }
 
 struct LazilyCompiledShader {
@@ -460,16 +462,17 @@ pub struct Renderer {
     // shadow primitive shader stretches the box shadow cache
     // output, and the cache_image shader blits the results of
     // a cache shader (e.g. blur) to the screen.
     ps_rectangle: PrimitiveShader,
     ps_rectangle_clip: PrimitiveShader,
     ps_text_run: PrimitiveShader,
     ps_text_run_subpixel: PrimitiveShader,
     ps_image: PrimitiveShader,
+    ps_image_rect: PrimitiveShader,
     ps_yuv_image: PrimitiveShader,
     ps_border: PrimitiveShader,
     ps_gradient: PrimitiveShader,
     ps_angle_gradient: PrimitiveShader,
     ps_radial_gradient: PrimitiveShader,
     ps_box_shadow: PrimitiveShader,
     ps_cache_image: PrimitiveShader,
 
@@ -550,34 +553,34 @@ impl From<ShaderError> for InitError {
     fn from(err: ShaderError) -> Self { InitError::Shader(err) }
 }
 
 impl From<std::io::Error> for InitError {
     fn from(err: std::io::Error) -> Self { InitError::Thread(err) }
 }
 
 impl Renderer {
-    /// Initializes webrender and creates a Renderer and RenderApiSender.
+    /// Initializes webrender and creates a `Renderer` and `RenderApiSender`.
     ///
     /// # Examples
-    /// Initializes a Renderer with some reasonable values. For more information see
-    /// [RendererOptions][rendereroptions].
-    /// [rendereroptions]: struct.RendererOptions.html
+    /// Initializes a `Renderer` with some reasonable values. For more information see
+    /// [`RendererOptions`][rendereroptions].
     ///
     /// ```rust,ignore
     /// # use webrender::renderer::Renderer;
     /// # use std::path::PathBuf;
     /// let opts = webrender::RendererOptions {
     ///    device_pixel_ratio: 1.0,
     ///    resource_override_path: None,
     ///    enable_aa: false,
     ///    enable_profiler: false,
     /// };
     /// let (renderer, sender) = Renderer::new(opts);
     /// ```
+    /// [rendereroptions]: struct.RendererOptions.html
     pub fn new(gl: Rc<gl::Gl>,
                mut options: RendererOptions,
                initial_window_size: DeviceUintSize) -> Result<(Renderer, RenderApiSender), InitError> {
         let (api_tx, api_rx) = try!{ channel::msg_channel() };
         let (payload_tx, payload_rx) = try!{ channel::payload_channel() };
         let (result_tx, result_rx) = channel();
 
         register_thread_with_profiler("Compositor".to_owned());
@@ -665,16 +668,23 @@ impl Renderer {
 
         let ps_image = try!{
             PrimitiveShader::new("ps_image",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
+        let ps_image_rect = try!{
+            PrimitiveShader::new("ps_image",
+                                 &mut device,
+                                 &[ TEXTURE_RECT_FEATURE ],
+                                 options.precache_shaders)
+        };
+
         let ps_yuv_image = try!{
             PrimitiveShader::new("ps_yuv_image",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
         let ps_border = try!{
@@ -907,16 +917,17 @@ impl Renderer {
             cs_blur: cs_blur,
             cs_clip_rectangle: cs_clip_rectangle,
             cs_clip_image: cs_clip_image,
             ps_rectangle: ps_rectangle,
             ps_rectangle_clip: ps_rectangle_clip,
             ps_text_run: ps_text_run,
             ps_text_run_subpixel: ps_text_run_subpixel,
             ps_image: ps_image,
+            ps_image_rect: ps_image_rect,
             ps_yuv_image: ps_yuv_image,
             ps_border: ps_border,
             ps_box_shadow: ps_box_shadow,
             ps_gradient: ps_gradient,
             ps_angle_gradient: ps_angle_gradient,
             ps_radial_gradient: ps_radial_gradient,
             ps_cache_image: ps_cache_image,
             ps_blend: ps_blend,
@@ -1034,20 +1045,20 @@ impl Renderer {
     // For a texture cache texture, the IDs are stored in a vector
     // map for fast access. For WebGL textures, the native texture ID
     // is stored inline. When we add support for external textures,
     // we will add a callback here that is able to ask the caller
     // for the image data.
     fn resolve_source_texture(&mut self, texture_id: &SourceTexture) -> TextureId {
         match *texture_id {
             SourceTexture::Invalid => TextureId::invalid(),
-            SourceTexture::WebGL(id) => TextureId::new(id),
-            SourceTexture::External(ref key) => {
+            SourceTexture::WebGL(id) => TextureId::new(id, TextureTarget::Default),
+            SourceTexture::External(external_image) => {
                 *self.external_images
-                     .get(key)
+                     .get(&external_image.id)
                      .expect("BUG: External image should be resolved by now!")
             }
             SourceTexture::TextureCache(index) => {
                 self.cache_texture_id_map[index.0]
             }
         }
     }
 
@@ -1060,18 +1071,18 @@ impl Renderer {
     pub fn get_frame_profiles(&mut self) -> (Vec<CpuProfile>, Vec<GpuProfile>) {
         let cpu_profiles = self.cpu_profiles.drain(..).collect();
         let gpu_profiles = self.gpu_profiles.drain(..).collect();
         (cpu_profiles, gpu_profiles)
     }
 
     /// Renders the current frame.
     ///
-    /// A Frame is supplied by calling [set_root_stacking_context()][newframe].
-    /// [newframe]: ../../webrender_traits/struct.RenderApi.html#method.set_root_stacking_context
+    /// A Frame is supplied by calling [`set_display_list()`][newframe].
+    /// [newframe]: ../../webrender_traits/struct.RenderApi.html#method.set_display_list
     pub fn render(&mut self, framebuffer_size: DeviceUintSize) {
         profile_scope!("render");
 
         if let Some(mut frame) = self.current_frame.take() {
             if let Some(ref mut frame) = frame.frame {
                 let mut profile_timers = RendererProfileTimers::new();
 
                 // Block CPU waiting for last frame's GPU profiles to arrive.
@@ -1188,34 +1199,41 @@ impl Renderer {
                                     self.device.init_texture(texture_id,
                                                              width,
                                                              height,
                                                              format,
                                                              filter,
                                                              mode,
                                                              Some(raw.as_slice()));
                                 }
-                                ImageData::ExternalBuffer(id) => {
-                                    let handler = self.external_image_handler
-                                                      .as_mut()
-                                                      .expect("Found external image, but no handler set!");
+                                ImageData::External(ext_image) => {
+                                    match ext_image.image_type {
+                                        ExternalImageType::ExternalBuffer => {
+                                            let handler = self.external_image_handler
+                                                              .as_mut()
+                                                              .expect("Found external image, but no handler set!");
 
-                                    match handler.lock(id).source {
-                                        ExternalImageSource::RawData(raw) => {
-                                            self.device.init_texture(texture_id,
-                                                                     width,
-                                                                     height,
-                                                                     format,
-                                                                     filter,
-                                                                     mode,
-                                                                     Some(raw));
+                                            match handler.lock(ext_image.id).source {
+                                                ExternalImageSource::RawData(raw) => {
+                                                    self.device.init_texture(texture_id,
+                                                                             width,
+                                                                             height,
+                                                                             format,
+                                                                             filter,
+                                                                             mode,
+                                                                             Some(raw));
+                                                }
+                                                _ => panic!("No external buffer found"),
+                                            };
+                                            handler.unlock(ext_image.id);
                                         }
-                                        _ => panic!("No external buffer found"),
-                                    };
-                                    handler.unlock(id);
+                                        _ => {
+                                            panic!("External texture handle should not use TextureUpdateOp::Create.");
+                                        }
+                                    }
                                 }
                                 _ => {
                                     panic!("No suitable image buffer for TextureUpdateOp::Create.");
                                 }
                             }
                         } else {
                             self.device.init_texture(texture_id,
                                                      width,
@@ -1334,16 +1352,20 @@ impl Renderer {
                             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)
                     }
@@ -1454,21 +1476,37 @@ impl Renderer {
                          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();
-            self.device.clear_target(clear_color, Some(1.0));
+            match render_target {
+                Some(..) => {
+                    // TODO(gw): Applying a scissor rect and minimal clear here
+                    // is a very large performance win on the Intel and nVidia
+                    // GPUs that I have tested with. It's possible it may be a
+                    // performance penalty on other GPU types - we should test this
+                    // and consider different code paths.
+                    self.device.clear_target_rect(clear_color,
+                                                  Some(1.0),
+                                                  target.used_rect());
+                }
+                None => {
+                    self.device.clear_target(clear_color, Some(1.0));
+                }
+            }
 
             let isolate_clear_color = Some([0.0, 0.0, 0.0, 0.0]);
             for isolate_clear in &target.isolate_clears {
-                self.device.clear_target_rect(isolate_clear_color, None, *isolate_clear);
+                self.device.clear_target_rect(isolate_clear_color,
+                                              None,
+                                              *isolate_clear);
             }
 
             self.device.disable_depth_write();
         }
 
         // Draw any blurs for this target.
         // Blurs are rendered as a standard 2-pass
         // separable implementation.
@@ -1587,18 +1625,25 @@ impl Renderer {
                          target_size: DeviceUintSize,
                          projection: &Matrix4D<f32>) {
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_SETUP_TARGET);
             self.device.bind_draw_target(Some(render_target), Some(target_size));
             self.device.disable_depth();
             self.device.disable_depth_write();
 
+            // TODO(gw): Applying a scissor rect and minimal clear here
+            // is a very large performance win on the Intel and nVidia
+            // GPUs that I have tested with. It's possible it may be a
+            // performance penalty on other GPU types - we should test this
+            // and consider different code paths.
             let clear_color = [1.0, 1.0, 1.0, 0.0];
-            self.device.clear_target(Some(clear_color), None);
+            self.device.clear_target_rect(Some(clear_color),
+                                          None,
+                                          target.used_rect());
         }
 
         // Draw the clip items into the tiled alpha mask.
         {
             let _gm = self.gpu_profile.add_marker(GPU_TAG_CACHE_CLIP);
             let vao = self.clip_vao_id;
             // switch to multiplicative blending
             self.device.set_blend(true);
@@ -1637,26 +1682,34 @@ impl Renderer {
         if !frame.deferred_resolves.is_empty() {
             let handler = self.external_image_handler
                               .as_mut()
                               .expect("Found external image, but no handler set!");
 
             for deferred_resolve in &frame.deferred_resolves {
                 GpuMarker::fire(self.device.gl(), "deferred resolve");
                 let props = &deferred_resolve.image_properties;
-                let external_id = props.external_id
-                                       .expect("BUG: Deferred resolves must be external images!");
-                let image = handler.lock(external_id);
+                let ext_image = props.external_image
+                                     .expect("BUG: Deferred resolves must be external images!");
+                let image = handler.lock(ext_image.id);
+                let texture_target = match ext_image.image_type {
+                    ExternalImageType::Texture2DHandle => TextureTarget::Default,
+                    ExternalImageType::TextureRectHandle => TextureTarget::Rect,
+                    _ => {
+                        panic!("{:?} is not a suitable image type in update_deferred_resolves().",
+                            ext_image.image_type);
+                    }
+                };
 
                 let texture_id = match image.source {
-                    ExternalImageSource::NativeTexture(texture_id) => TextureId::new(texture_id),
+                    ExternalImageSource::NativeTexture(texture_id) => TextureId::new(texture_id, texture_target),
                     _ => panic!("No native texture found."),
                 };
 
-                self.external_images.insert(external_id, texture_id);
+                self.external_images.insert(ext_image.id, texture_id);
                 let resource_rect_index = deferred_resolve.resource_address.0 as usize;
                 let resource_rect = &mut frame.gpu_resource_rects[resource_rect_index];
                 resource_rect.uv0 = DevicePoint::new(image.u0, image.v0);
                 resource_rect.uv1 = DevicePoint::new(image.u1, image.v1);
             }
         }
     }
 
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -19,21 +19,24 @@ use std::mem;
 use std::sync::{Arc, Barrier, Mutex};
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::thread;
 use texture_cache::{TextureCache, TextureCacheItemId};
 use thread_profiler::register_thread_with_profiler;
 use webrender_traits::{Epoch, FontKey, GlyphKey, ImageKey, ImageFormat, ImageRendering};
 use webrender_traits::{FontRenderMode, ImageData, GlyphDimensions, WebGLContextId};
 use webrender_traits::{DevicePoint, DeviceIntSize, DeviceUintRect, ImageDescriptor, ColorF};
-use webrender_traits::{ExternalImageId, GlyphOptions, GlyphInstance, TileOffset, TileSize};
+use webrender_traits::{GlyphOptions, GlyphInstance, TileOffset, TileSize};
 use webrender_traits::{BlobImageRenderer, BlobImageDescriptor, BlobImageError};
+use webrender_traits::{ExternalImageData, ExternalImageType};
 use threadpool::ThreadPool;
 use euclid::Point2D;
 
+const DEFAULT_TILE_SIZE: TileSize = 512;
+
 thread_local!(pub static FONT_CONTEXT: RefCell<FontContext> = RefCell::new(FontContext::new()));
 
 type GlyphCache = ResourceClassCache<RenderedGlyphKey, Option<TextureCacheItemId>>;
 
 /// Message sent from the resource cache to the glyph cache thread.
 enum GlyphCacheMsg {
     /// Begin the frame - pass ownership of the glyph cache to the thread.
     BeginFrame(FrameId, GlyphCache),
@@ -89,17 +92,17 @@ impl RenderedGlyphKey {
             render_mode: render_mode,
             glyph_options: glyph_options,
         }
     }
 }
 
 pub struct ImageProperties {
     pub descriptor: ImageDescriptor,
-    pub external_id: Option<ExternalImageId>,
+    pub external_image: Option<ExternalImageData>,
     pub tiling: Option<TileSize>,
 }
 
 #[derive(Debug, Copy, Clone, PartialEq)]
 enum State {
     Idle,
     AddResources,
     QueryResources,
@@ -249,16 +252,30 @@ impl ResourceCache {
             blob_image_requests: HashSet::new(),
         }
     }
 
     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) => {
+                // 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();
         self.font_templates.insert(font_key, template);
     }
@@ -270,81 +287,84 @@ impl ResourceCache {
         self.font_templates.remove(&font_key);
     }
 
     pub fn add_image_template(&mut self,
                               image_key: ImageKey,
                               descriptor: ImageDescriptor,
                               data: ImageData,
                               mut tiling: Option<TileSize>) {
-        if descriptor.width > self.max_texture_size() || descriptor.height > self.max_texture_size() {
+        if tiling.is_none() && self.should_tile(&descriptor, &data) {
             // We aren't going to be able to upload a texture this big, so tile it, even
             // if tiling was not requested.
-            tiling = Some(512);
+            tiling = Some(DEFAULT_TILE_SIZE);
         }
 
         let resource = ImageResource {
             descriptor: descriptor,
             data: data,
             epoch: Epoch(0),
             tiling: tiling,
             dirty_rect: None,
         };
 
         self.image_templates.insert(image_key, resource);
     }
 
     pub fn update_image_template(&mut self,
                                  image_key: ImageKey,
                                  descriptor: ImageDescriptor,
-                                 bytes: Vec<u8>,
+                                 data: ImageData,
                                  dirty_rect: Option<DeviceUintRect>) {
-        let (next_epoch, prev_dirty_rect) = match self.image_templates.get(&image_key) {
-            Some(image) => {
-                // This image should not be an external image.
-                match image.data {
-                    ImageData::ExternalHandle(id) => {
-                        panic!("Update an external image with buffer, id={} image_key={:?}", id.0, image_key);
-                    },
-                    _ => {},
-                }
+        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);
 
-                let Epoch(current_epoch) = image.epoch;
-                (Epoch(current_epoch + 1), image.dirty_rect)
+            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);
             }
-            None => {
-                (Epoch(0), None)
+
+            ImageResource {
+                descriptor: descriptor,
+                data: data,
+                epoch: next_epoch,
+                tiling: tiling,
+                dirty_rect: match (dirty_rect, image.dirty_rect) {
+                    (Some(rect), Some(prev_rect)) => Some(rect.union(&prev_rect)),
+                    (Some(rect), None) => Some(rect),
+                    _ => None,
+                },
             }
-        };
-
-        let resource = ImageResource {
-            descriptor: descriptor,
-            data: ImageData::new(bytes),
-            epoch: next_epoch,
-            tiling: None,
-            dirty_rect: match (dirty_rect, prev_dirty_rect) {
-                (Some(rect), Some(prev_rect)) => Some(rect.union(&prev_rect)),
-                (Some(rect), None) => Some(rect),
-                _ => None,
-            },
+        } else {
+            panic!("Attempt to update non-existant image (key {:?}).", image_key);
         };
 
         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::ExternalHandle(id) => {
-                    self.pending_external_image_update_list.push(id);
-                },
-                _ => {},
+                ImageData::External(ext_image) => {
+                    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);
     }
 
@@ -389,16 +409,17 @@ impl ResourceCache {
                         data.clone(),
                         &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,
                     );
                 }
             }
         } else {
             self.pending_image_requests.push(request);
         }
     }
 
@@ -518,25 +539,34 @@ impl ResourceCache {
             uv1: DevicePoint::new(item.pixel_rect.bottom_right.x as f32,
                                   item.pixel_rect.bottom_right.y as f32),
         }
     }
 
     pub fn get_image_properties(&self, image_key: ImageKey) -> ImageProperties {
         let image_template = &self.image_templates[&image_key];
 
-        let external_id = match image_template.data {
-            ImageData::ExternalHandle(id) => Some(id),
-            // raw and externalBuffer are all use resource_cache.
-            ImageData::Raw(..) | ImageData::ExternalBuffer(..) | ImageData::Blob(..) => None,
+        let external_image = match image_template.data {
+            ImageData::External(ext_image) => {
+                match ext_image.image_type {
+                    ExternalImageType::Texture2DHandle |
+                    ExternalImageType::TextureRectHandle => {
+                        Some(ext_image)
+                    },
+                    // external buffer uses resource_cache.
+                    ExternalImageType::ExternalBuffer => None,
+                }
+            },
+            // raw and blob image are all using resource_cache.
+            ImageData::Raw(..) | ImageData::Blob(..) => None,
         };
 
         ImageProperties {
             descriptor: image_template.descriptor,
-            external_id: external_id,
+            external_image: external_image,
             tiling: image_template.tiling,
         }
     }
 
     #[inline]
     pub fn get_webgl_texture(&self, context_id: &WebGLContextId) -> CacheItem {
         let webgl_texture = &self.webgl_textures[context_id];
         CacheItem {
@@ -647,104 +677,122 @@ impl ResourceCache {
                     Err(BlobImageError::Other(msg)) => {
                         panic!("Vector image error {}", msg);
                     }
                 }
             }
         }
     }
 
-    fn finalize_image_request(&mut self,
-                              request: ImageRequest,
-                              image_data: Option<ImageData>,
-                              texture_cache_profile: &mut TextureCacheProfileCounters) {
+    fn update_texture_cache(&mut self,
+                            request: &ImageRequest,
+                            image_data: Option<ImageData>,
+                            texture_cache_profile: &mut TextureCacheProfileCounters) {
         let image_template = self.image_templates.get_mut(&request.key).unwrap();
         let image_data = image_data.unwrap_or_else(||{
             image_template.data.clone()
         });
 
-        match image_template.data {
-            ImageData::ExternalHandle(..) => {
-                // external handle doesn't need to update the texture_cache.
-            }
-            ImageData::Raw(..) | ImageData::ExternalBuffer(..) | ImageData::Blob(..) => {
-                let descriptor = if let Some(tile) = request.tile {
-                    let tile_size = image_template.tiling.unwrap() as u32;
-                    let image_descriptor = &image_template.descriptor;
-                    let stride = image_descriptor.compute_stride();
-                    let bpp = image_descriptor.format.bytes_per_pixel().unwrap();
+        let descriptor = if let Some(tile) = request.tile {
+            let tile_size = image_template.tiling.unwrap() as u32;
+            let image_descriptor = &image_template.descriptor;
+            let stride = image_descriptor.compute_stride();
+            let bpp = image_descriptor.format.bytes_per_pixel().unwrap();
 
-                    // Storage for the tiles on the right and bottom edges is shrunk to
-                    // fit the image data (See decompose_tiled_image in frame.rs).
-                    let actual_width = if (tile.x as u32) < image_descriptor.width / tile_size {
-                        tile_size
-                    } else {
-                        image_descriptor.width % tile_size
-                    };
+            // Storage for the tiles on the right and bottom edges is shrunk to
+            // fit the image data (See decompose_tiled_image in frame.rs).
+            let actual_width = if (tile.x as u32) < image_descriptor.width / tile_size {
+                tile_size
+            } else {
+                image_descriptor.width % tile_size
+            };
+
+            let actual_height = if (tile.y as u32) < image_descriptor.height / tile_size {
+                tile_size
+            } else {
+                image_descriptor.height % tile_size
+            };
+
+            let offset = image_descriptor.offset + tile.y as u32 * tile_size * stride
+                                                 + tile.x as u32 * tile_size * bpp;
 
-                    let actual_height = if (tile.y as u32) < image_descriptor.height / tile_size {
-                        tile_size
-                    } else {
-                        image_descriptor.height % tile_size
-                    };
+            ImageDescriptor {
+                width: actual_width,
+                height: actual_height,
+                stride: Some(stride),
+                offset: offset,
+                format: image_descriptor.format,
+                is_opaque: image_descriptor.is_opaque,
+            }
+        } else {
+            image_template.descriptor.clone()
+        };
 
-                    let offset = image_descriptor.offset + tile.y as u32 * tile_size * stride
-                                                         + tile.x as u32 * tile_size * bpp;
+        match self.cached_images.entry(request.clone(), self.current_frame_id) {
+            Occupied(entry) => {
+                let image_id = entry.get().texture_cache_id;
 
-                    ImageDescriptor {
-                        width: actual_width,
-                        height: actual_height,
-                        stride: Some(stride),
-                        offset: offset,
-                        format: image_descriptor.format,
-                        is_opaque: image_descriptor.is_opaque,
-                    }
-                } else {
-                    image_template.descriptor.clone()
+                if entry.get().epoch != image_template.epoch {
+                    self.texture_cache.update(image_id,
+                                              descriptor,
+                                              image_data,
+                                              image_template.dirty_rect);
+
+                    // Update the cached epoch
+                    *entry.into_mut() = CachedImageInfo {
+                        texture_cache_id: image_id,
+                        epoch: image_template.epoch,
+                    };
+                    image_template.dirty_rect = None;
+                }
+            }
+            Vacant(entry) => {
+                let image_id = self.texture_cache.new_item_id();
+
+                let filter = match request.rendering {
+                    ImageRendering::Pixelated => TextureFilter::Nearest,
+                    ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
                 };
 
-                match self.cached_images.entry(request.clone(), self.current_frame_id) {
-                    Occupied(entry) => {
-                        let image_id = entry.get().texture_cache_id;
-
-                        if entry.get().epoch != image_template.epoch {
-                            self.texture_cache.update(image_id,
-                                                      descriptor,
-                                                      image_data,
-                                                      image_template.dirty_rect);
+                self.texture_cache.insert(image_id,
+                                          descriptor,
+                                          filter,
+                                          image_data,
+                                          texture_cache_profile);
 
-                            // Update the cached epoch
-                            *entry.into_mut() = CachedImageInfo {
-                                texture_cache_id: image_id,
-                                epoch: image_template.epoch,
-                            };
-                            image_template.dirty_rect = None;
-                        }
+                entry.insert(CachedImageInfo {
+                    texture_cache_id: image_id,
+                    epoch: image_template.epoch,
+                });
+            }
+        }
+    }
+    fn finalize_image_request(&mut self,
+                              request: ImageRequest,
+                              image_data: Option<ImageData>,
+                              texture_cache_profile: &mut TextureCacheProfileCounters) {
+        match self.image_templates.get(&request.key).unwrap().data {
+            ImageData::External(ext_image) => {
+                match ext_image.image_type {
+                    ExternalImageType::Texture2DHandle |
+                    ExternalImageType::TextureRectHandle => {
+                        // external handle doesn't need to update the texture_cache.
                     }
-                    Vacant(entry) => {
-                        let image_id = self.texture_cache.new_item_id();
-
-                        let filter = match request.rendering {
-                            ImageRendering::Pixelated => TextureFilter::Nearest,
-                            ImageRendering::Auto | ImageRendering::CrispEdges => TextureFilter::Linear,
-                        };
-
-                        self.texture_cache.insert(image_id,
-                                                  descriptor,
-                                                  filter,
+                    ExternalImageType::ExternalBuffer => {
+                        self.update_texture_cache(&request,
                                                   image_data,
                                                   texture_cache_profile);
-
-                        entry.insert(CachedImageInfo {
-                            texture_cache_id: image_id,
-                            epoch: image_template.epoch,
-                        });
                     }
                 }
             }
+            ImageData::Raw(..) | ImageData::Blob(..) => {
+                self.update_texture_cache(&request,
+                                           image_data,
+                                           texture_cache_profile);
+            }
         }
     }
 
     pub fn end_frame(&mut self) {
         debug_assert!(self.state == State::QueryResources);
         self.state = State::Idle;
     }
 
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -108,25 +108,25 @@ impl Scene {
             properties: SceneProperties::new(),
         }
     }
 
     pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) {
         self.root_pipeline_id = Some(pipeline_id);
     }
 
-    pub fn set_root_display_list(&mut self,
-                                 pipeline_id: PipelineId,
-                                 epoch: Epoch,
-                                 built_display_list: BuiltDisplayList,
-                                 background_color: Option<ColorF>,
-                                 viewport_size: LayerSize,
-                                 auxiliary_lists: AuxiliaryLists) {
+    pub fn set_display_list(&mut self,
+                            pipeline_id: PipelineId,
+                            epoch: Epoch,
+                            built_display_list: BuiltDisplayList,
+                            background_color: Option<ColorF>,
+                            viewport_size: LayerSize,
+                            auxiliary_lists: AuxiliaryLists) {
         self.pipeline_auxiliary_lists.insert(pipeline_id, auxiliary_lists);
-        self.display_lists.insert(pipeline_id, built_display_list.all_display_items().to_vec());
+        self.display_lists.insert(pipeline_id, built_display_list.into_display_items());
 
         let new_pipeline = ScenePipeline {
             pipeline_id: pipeline_id,
             epoch: epoch,
             viewport_size: viewport_size,
             background_color: background_color,
         };
 
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -11,17 +11,17 @@ use profiler::TextureCacheProfileCounter
 use std::cmp::{self, Ordering};
 use std::collections::HashMap;
 use std::collections::hash_map::Entry;
 use std::hash::BuildHasherDefault;
 use std::mem;
 use std::slice::Iter;
 use time;
 use util;
-use webrender_traits::{ImageData, ImageFormat, DevicePixel, DeviceIntPoint};
+use webrender_traits::{ExternalImageType, ImageData, ImageFormat, DevicePixel, DeviceIntPoint};
 use webrender_traits::{DeviceUintRect, DeviceUintSize, DeviceUintPoint};
 use webrender_traits::ImageDescriptor;
 
 /// The number of bytes we're allowed to use for a texture.
 const MAX_BYTES_PER_TEXTURE: u32 = 1024 * 1024 * 256;  // 256MB
 
 /// The number of RGBA pixels we're allowed to use for a texture.
 const MAX_RGBA_PIXELS_PER_TEXTURE: u32 = MAX_BYTES_PER_TEXTURE / 4;
@@ -606,18 +606,18 @@ impl TextureCache {
         let (page_list, page_profile) = match format {
             ImageFormat::A8 => (&mut self.arena.pages_a8, &mut profile.pages_a8),
             ImageFormat::RGBA8 => (&mut self.arena.pages_rgba8, &mut profile.pages_rgba8),
             ImageFormat::RGB8 => (&mut self.arena.pages_rgb8, &mut profile.pages_rgb8),
             ImageFormat::Invalid | ImageFormat::RGBAF32 => unreachable!(),
         };
 
         // TODO(gw): Handle this sensibly (support failing to render items that can't fit?)
-        assert!(requested_size.width < self.max_texture_size);
-        assert!(requested_size.height < self.max_texture_size);
+        assert!(requested_size.width <= self.max_texture_size);
+        assert!(requested_size.height <= self.max_texture_size);
 
         let mut page_id = None; //using ID here to please the borrow checker
         for (i, page) in page_list.iter_mut().enumerate() {
             if page.can_allocate(&requested_size) {
                 page_id = Some(i);
                 break;
             }
             // try to coalesce it
@@ -710,44 +710,47 @@ impl TextureCache {
                   dirty_rect: Option<DeviceUintRect>) {
         let existing_item = self.items.get(image_id);
 
         // TODO(gw): Handle updates to size/format!
         debug_assert_eq!(existing_item.allocated_rect.size.width, descriptor.width);
         debug_assert_eq!(existing_item.allocated_rect.size.height, descriptor.height);
 
         let op = match data {
-            ImageData::ExternalHandle(..) | ImageData::ExternalBuffer(..)=> {
+            ImageData::External(..) => {
                 panic!("Doesn't support Update() for external image.");
             }
             ImageData::Blob(..) => {
                 panic!("The vector image should have been rasterized into a raw image.");
             }
             ImageData::Raw(bytes) => {
-                if let Some(dirty) = dirty_rect {
-                    let stride = descriptor.compute_stride();
-                    let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x;
-                    TextureUpdateOp::Update {
-                        page_pos_x: existing_item.allocated_rect.origin.x + dirty.origin.x,
-                        page_pos_y: existing_item.allocated_rect.origin.y + dirty.origin.y,
-                        width: dirty.size.width,
-                        height: dirty.size.height,
-                        data: bytes,
-                        stride: Some(stride),
-                        offset: offset,
+                match dirty_rect {
+                    Some(dirty) => {
+                        let stride = descriptor.compute_stride();
+                        let offset = descriptor.offset + dirty.origin.y * stride + dirty.origin.x;
+                        TextureUpdateOp::Update {
+                            page_pos_x: existing_item.allocated_rect.origin.x + dirty.origin.x,
+                            page_pos_y: existing_item.allocated_rect.origin.y + dirty.origin.y,
+                            width: dirty.size.width,
+                            height: dirty.size.height,
+                            data: bytes,
+                            stride: Some(stride),
+                            offset: offset,
+                        }
                     }
-                } else {
-                    TextureUpdateOp::Update {
-                        page_pos_x: existing_item.allocated_rect.origin.x,
-                        page_pos_y: existing_item.allocated_rect.origin.y,
-                        width: descriptor.width,
-                        height: descriptor.height,
-                        data: bytes,
-                        stride: descriptor.stride,
-                        offset: descriptor.offset,
+                    None => {
+                        TextureUpdateOp::Update {
+                            page_pos_x: existing_item.allocated_rect.origin.x,
+                            page_pos_y: existing_item.allocated_rect.origin.y,
+                            width: descriptor.width,
+                            height: descriptor.height,
+                            data: bytes,
+                            stride: descriptor.stride,
+                            offset: descriptor.offset,
+                        }
                     }
                 }
             }
         };
 
         let update_op = TextureUpdate {
             id: existing_item.texture_id,
             op: op,
@@ -776,18 +779,35 @@ impl TextureCache {
                                    height,
                                    format,
                                    filter,
                                    profile);
 
         match result.kind {
             AllocationKind::TexturePage => {
                 match data {
-                    ImageData::ExternalHandle(..) => {
-                        panic!("External handle should not go through texture_cache.");
+                    ImageData::External(ext_image) => {
+                        match ext_image.image_type {
+                            ExternalImageType::Texture2DHandle |
+                            ExternalImageType::TextureRectHandle => {
+                                panic!("External texture handle should not go through texture_cache.");
+                            }
+                            ExternalImageType::ExternalBuffer => {
+                                let update_op = TextureUpdate {
+                                    id: result.item.texture_id,
+                                    op: TextureUpdateOp::UpdateForExternalBuffer {
+                                        rect: result.item.allocated_rect,
+                                        id: ext_image.id,
+                                        stride: stride,
+                                    },
+                                };
+
+                                self.pending_updates.push(update_op);
+                            }
+                        }
                     }
                     ImageData::Blob(..) => {
                         panic!("The vector image should have been rasterized.");
                     }
                     ImageData::Raw(bytes) => {
                         let update_op = TextureUpdate {
                             id: result.item.texture_id,
                             op: TextureUpdateOp::Update {
@@ -798,34 +818,42 @@ impl TextureCache {
                                 data: bytes,
                                 stride: stride,
                                 offset: descriptor.offset,
                             },
                         };
 
                         self.pending_updates.push(update_op);
                     }
-                    ImageData::ExternalBuffer(id) => {
-                        let update_op = TextureUpdate {
-                            id: result.item.texture_id,
-                            op: TextureUpdateOp::UpdateForExternalBuffer {
-                                rect: result.item.allocated_rect,
-                                id: id,
-                                stride: stride,
-                            },
-                        };
-
-                        self.pending_updates.push(update_op);
-                    }
                 }
             }
             AllocationKind::Standalone => {
                 match data {
-                    ImageData::ExternalHandle(..) => {
-                        panic!("External handle should not go through texture_cache.");
+                    ImageData::External(ext_image) => {
+                        match ext_image.image_type {
+                            ExternalImageType::Texture2DHandle |
+                            ExternalImageType::TextureRectHandle => {
+                                panic!("External texture handle should not go through texture_cache.");
+                            }
+                            ExternalImageType::ExternalBuffer => {
+                                let update_op = TextureUpdate {
+                                    id: result.item.texture_id,
+                                    op: TextureUpdateOp::Create {
+                                        width: width,
+                                        height: height,
+                                        format: format,
+                                        filter: filter,
+                                        mode: RenderTargetMode::None,
+                                        data: Some(data),
+                                    },
+                                };
+
+                                self.pending_updates.push(update_op);
+                            }
+                        }
                     }
                     _ => {
                         let update_op = TextureUpdate {
                             id: result.item.texture_id,
                             op: TextureUpdateOp::Create {
                                 width: width,
                                 height: height,
                                 format: format,
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -18,20 +18,22 @@ use render_task::{RenderTaskId, RenderTa
 use render_task::RenderTaskLocation;
 use renderer::BlendMode;
 use resource_cache::ResourceCache;
 use std::{f32, i32, mem, usize};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use texture_cache::TexturePage;
 use util::{TransformedRect, TransformedRectKind};
-use webrender_traits::{AuxiliaryLists, ColorF, DeviceIntPoint, DeviceIntRect, DeviceUintPoint};
+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::{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,
@@ -64,17 +66,34 @@ trait AlphaBatchHelpers {
                                        z_sort_index: i32);
 }
 
 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 => AlphaBatchKind::Image,
+            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 {
@@ -268,17 +287,18 @@ impl AlphaBatchHelpers for PrimitiveStor
                                 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::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,
@@ -849,28 +869,69 @@ impl ClipBatcher {
 
 pub struct RenderTargetContext<'a> {
     pub stacking_context_store: &'a [StackingContext],
     pub clip_scroll_group_store: &'a [ClipScrollGroup],
     pub prim_store: &'a PrimitiveStore,
     pub resource_cache: &'a ResourceCache,
 }
 
+struct TextureAllocator {
+    // TODO(gw): Replace this with a simpler allocator for
+    // render target allocation - this use case doesn't need
+    // to deal with coalescing etc that the general texture
+    // cache allocator requires.
+    page_allocator: TexturePage,
+
+    // Track the used rect of the render target, so that
+    // we can set a scissor rect and only clear to the
+    // used portion of the target as an optimization.
+    used_rect: DeviceIntRect,
+}
+
+impl TextureAllocator {
+    fn new(size: DeviceUintSize) -> TextureAllocator {
+        TextureAllocator {
+            page_allocator: TexturePage::new(CacheTextureId(0), size),
+            used_rect: DeviceIntRect::zero(),
+        }
+    }
+
+    fn allocate(&mut self, size: &DeviceUintSize) -> Option<DeviceUintPoint> {
+        let origin = self.page_allocator.allocate(size);
+
+        if let Some(origin) = origin {
+            // TODO(gw): We need to make all the device rects
+            //           be consistent in the use of the
+            //           DeviceIntRect and DeviceUintRect types!
+            let origin = DeviceIntPoint::new(origin.x as i32,
+                                             origin.y as i32);
+            let size = DeviceIntSize::new(size.width as i32,
+                                          size.height as i32);
+            let rect = DeviceIntRect::new(origin, size);
+            self.used_rect = rect.union(&self.used_rect);
+        }
+
+        origin
+    }
+}
+
 pub trait RenderTarget {
     fn new(size: DeviceUintSize) -> Self;
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint>;
     fn build(&mut self,
              _ctx: &RenderTargetContext,
              _render_tasks: &mut RenderTaskCollection,
              _child_pass_index: RenderPassIndex) {}
     fn add_task(&mut self,
                 task: RenderTask,
                 ctx: &RenderTargetContext,
                 render_tasks: &RenderTaskCollection,
                 pass_index: RenderPassIndex);
+    fn used_rect(&self) -> DeviceIntRect;
 }
 
 #[derive(Debug, Copy, Clone)]
 pub enum RenderTargetKind {
     Color,   // RGBA32
     Alpha,   // R8
 }
 
@@ -948,38 +1009,42 @@ pub struct ColorRenderTarget {
     //           be removed anyway.
     pub text_run_cache_prims: Vec<PrimitiveInstance>,
     pub text_run_textures: BatchTextures,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurCommand>,
     pub horizontal_blurs: Vec<BlurCommand>,
     pub readbacks: Vec<DeviceIntRect>,
     pub isolate_clears: Vec<DeviceIntRect>,
-    page_allocator: TexturePage,
+    allocator: TextureAllocator,
 }
 
 impl RenderTarget for ColorRenderTarget {
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
-        self.page_allocator.allocate(&size)
+        self.allocator.allocate(&size)
     }
 
     fn new(size: DeviceUintSize) -> ColorRenderTarget {
         ColorRenderTarget {
             alpha_batcher: AlphaBatcher::new(),
             box_shadow_cache_prims: Vec::new(),
             text_run_cache_prims: Vec::new(),
             text_run_textures: BatchTextures::no_texture(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             isolate_clears: Vec::new(),
-            page_allocator: TexturePage::new(CacheTextureId(0), size),
+            allocator: TextureAllocator::new(size),
         }
     }
 
+    fn used_rect(&self) -> DeviceIntRect {
+        self.allocator.used_rect
+    }
+
     fn build(&mut self,
              ctx: &RenderTargetContext,
              render_tasks: &mut RenderTaskCollection,
              child_pass_index: RenderPassIndex) {
         self.alpha_batcher.build(ctx,
                                  render_tasks,
                                  child_pass_index);
     }
@@ -1094,31 +1159,35 @@ impl RenderTarget for ColorRenderTarget 
                 self.readbacks.push(device_rect);
             }
         }
     }
 }
 
 pub struct AlphaRenderTarget {
     pub clip_batcher: ClipBatcher,
-    page_allocator: TexturePage,
+    allocator: TextureAllocator,
 }
 
 impl RenderTarget for AlphaRenderTarget {
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
-        self.page_allocator.allocate(&size)
+        self.allocator.allocate(&size)
     }
 
     fn new(size: DeviceUintSize) -> AlphaRenderTarget {
         AlphaRenderTarget {
             clip_batcher: ClipBatcher::new(),
-            page_allocator: TexturePage::new(CacheTextureId(0), size),
+            allocator: TextureAllocator::new(size),
         }
     }
 
+    fn used_rect(&self) -> DeviceIntRect {
+        self.allocator.used_rect
+    }
+
     fn add_task(&mut self,
                 task: RenderTask,
                 ctx: &RenderTargetContext,
                 render_tasks: &RenderTaskCollection,
                 pass_index: RenderPassIndex) {
         match task.kind {
             RenderTaskKind::Alpha(..) |
             RenderTaskKind::VerticalBlur(..) |
@@ -1256,16 +1325,17 @@ impl RenderPass {
 #[repr(u8)]
 pub enum AlphaBatchKind {
     Composite = 0,
     HardwareComposite,
     Blend,
     Rectangle,
     TextRun,
     Image,
+    ImageRect,
     YuvImage,
     Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
     CacheImage,
 }
@@ -1386,16 +1456,17 @@ pub struct PrimitiveBatch {
 }
 
 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 |
--- a/gfx/webrender_traits/Cargo.toml
+++ b/gfx/webrender_traits/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender_traits"
-version = "0.27.0"
+version = "0.31.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
@@ -1,14 +1,13 @@
 /* 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 byteorder::{LittleEndian, WriteBytesExt};
-use channel::{self, MsgSender, PayloadHelperMethods, PayloadSender};
+use channel::{self, MsgSender, Payload, PayloadSenderHelperMethods, PayloadSender};
 #[cfg(feature = "webgl")]
 use offscreen_gl_context::{GLContextAttributes, GLLimits};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use {AuxiliaryLists, AuxiliaryListsDescriptor, BuiltDisplayList, BuiltDisplayListDescriptor};
 use {ColorF, DeviceIntPoint, DeviceIntSize, DeviceUintRect, DeviceUintSize, FontKey};
 use {GlyphDimensions, GlyphKey, ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize};
@@ -23,31 +22,31 @@ pub enum ApiMsg {
     AddRawFont(FontKey, Vec<u8>),
     AddNativeFont(FontKey, NativeFontHandle),
     DeleteFont(FontKey),
     /// Gets the glyph dimensions
     GetGlyphDimensions(Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
     /// Adds an image from the resource cache.
     AddImage(ImageKey, ImageDescriptor, ImageData, Option<TileSize>),
     /// Updates the the resource cache with the new image data.
-    UpdateImage(ImageKey, ImageDescriptor, Vec<u8>, Option<DeviceUintRect>),
+    UpdateImage(ImageKey, ImageDescriptor, ImageData, Option<DeviceUintRect>),
     /// Drops an image from the resource cache.
     DeleteImage(ImageKey),
     CloneApi(MsgSender<IdNamespace>),
     /// Supplies a new frame to WebRender.
     ///
     /// After receiving this message, WebRender will read the display list, followed by the
     /// auxiliary lists, from the payload channel.
-    SetRootDisplayList(Option<ColorF>,
-                       Epoch,
-                       PipelineId,
-                       LayoutSize,
-                       BuiltDisplayListDescriptor,
-                       AuxiliaryListsDescriptor,
-                       bool),
+    SetDisplayList(Option<ColorF>,
+                   Epoch,
+                   PipelineId,
+                   LayoutSize,
+                   BuiltDisplayListDescriptor,
+                   AuxiliaryListsDescriptor,
+                   bool),
     SetPageZoom(ZoomFactor),
     SetPinchZoom(ZoomFactor),
     SetPan(DeviceIntPoint),
     SetRootPipeline(PipelineId),
     SetWindowParameters(DeviceUintSize, DeviceUintRect),
     Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
     ScrollLayerWithId(LayoutPoint, ScrollLayerId),
     TickScrollingBounce,
@@ -72,17 +71,17 @@ impl fmt::Debug for ApiMsg {
             &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::SetRootDisplayList(..) => { write!(f, "ApiMsg::SetRootDisplayList") }
+            &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") }
@@ -245,19 +244,19 @@ impl RenderApi {
 
     /// Updates a specific image.
     ///
     /// Currently doesn't support changing dimensions or format by updating.
     // TODO: Support changing dimensions (and format) during image update?
     pub fn update_image(&self,
                         key: ImageKey,
                         descriptor: ImageDescriptor,
-                        bytes: Vec<u8>,
+                        data: ImageData,
                         dirty_rect: Option<DeviceUintRect>) {
-        let msg = ApiMsg::UpdateImage(key, descriptor, bytes, dirty_rect);
+        let msg = ApiMsg::UpdateImage(key, descriptor, data, dirty_rect);
         self.api_sender.send(msg).unwrap();
     }
 
     /// Deletes the specific image.
     pub fn delete_image(&self, key: ImageKey) {
         let msg = ApiMsg::DeleteImage(key);
         self.api_sender.send(msg).unwrap();
     }
@@ -294,36 +293,39 @@ impl RenderApi {
     /// * `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_root_display_list(&self,
-                                 background_color: Option<ColorF>,
-                                 epoch: Epoch,
-                                 viewport_size: LayoutSize,
-                                 (pipeline_id, display_list, auxiliary_lists): (PipelineId, BuiltDisplayList, AuxiliaryLists),
-                                 preserve_frame_state: bool) {
-        let msg = ApiMsg::SetRootDisplayList(background_color,
+    pub fn set_display_list(&self,
+                            background_color: Option<ColorF>,
+                            epoch: Epoch,
+                            viewport_size: LayoutSize,
+                            (pipeline_id, display_list, auxiliary_lists): (PipelineId, BuiltDisplayList, AuxiliaryLists),
+                            preserve_frame_state: bool) {
+        let (dl_data, dl_desc) = display_list.into_data();
+        let (aux_data, aux_desc) = auxiliary_lists.into_data();
+        let msg = ApiMsg::SetDisplayList(background_color,
                                              epoch,
                                              pipeline_id,
                                              viewport_size,
-                                             display_list.descriptor().clone(),
-                                             *auxiliary_lists.descriptor(),
+                                             dl_desc,
+                                             aux_desc,
                                              preserve_frame_state);
         self.api_sender.send(msg).unwrap();
 
-        let mut payload = vec![];
-        payload.write_u32::<LittleEndian>(epoch.0).unwrap();
-        payload.extend_from_slice(display_list.data());
-        payload.extend_from_slice(auxiliary_lists.data());
-        self.payload_sender.send_vec(payload).unwrap();
+        self.payload_sender.send_payload(Payload {
+            epoch: epoch,
+            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
     /// which has `ScrollPolicy::Scrollable` set.
     pub fn scroll(&self, scroll_location: ScrollLocation, cursor: WorldPoint, phase: ScrollEventPhase) {
         let msg = ApiMsg::Scroll(scroll_location, cursor, phase);
@@ -484,16 +486,25 @@ impl ZoomFactor {
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize, Eq, Hash)]
 pub struct PropertyBindingId {
     namespace: u32,
     uid: u32,
 }
 
+impl PropertyBindingId {
+    pub fn new(value: u64) -> Self {
+        PropertyBindingId {
+            namespace: (value>>32) as u32,
+            uid: value as u32,
+        }
+    }
+}
+
 /// A unique key that is used for connecting animated property
 /// values to bindings in the display list.
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PropertyBindingKey<T> {
     pub id: PropertyBindingId,
     _phantom: PhantomData<T>,
 }
@@ -503,16 +514,25 @@ impl<T: Copy> PropertyBindingKey<T> {
     pub fn with(&self, value: T) -> PropertyValue<T> {
         PropertyValue {
             key: *self,
             value: value,
         }
     }
 }
 
+impl<T> PropertyBindingKey<T> {
+    pub fn new(value: u64) -> Self {
+        PropertyBindingKey {
+            id: PropertyBindingId::new(value),
+            _phantom: PhantomData,
+        }
+    }
+}
+
 /// A binding property can either be a specific value
 /// (the normal, non-animated case) or point to a binding location
 /// to fetch the current value from.
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub enum PropertyBinding<T> {
     Value(T),
     Binding(PropertyBindingKey<T>),
 }
--- a/gfx/webrender_traits/src/channel.rs
+++ b/gfx/webrender_traits/src/channel.rs
@@ -1,15 +1,86 @@
 /* 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 helper to handle the interface difference between
-// IpcBytesSender and Sender<Vec<u8>>
-pub trait PayloadHelperMethods {
-    fn send_vec(&self, data: Vec<u8>) -> Result<(), Error>;
+use byteorder::{LittleEndian, WriteBytesExt, ReadBytesExt};
+use std::io::{Cursor, Read};
+use api::{Epoch, PipelineId};
+use std::mem;
+
+#[derive(Clone)]
+pub struct Payload {
+    /// An epoch used to get the proper payload for a pipeline id frame request.
+    ///
+    /// TODO(emilio): Is this still relevant? We send the messages for the same
+    /// pipeline in order, so we shouldn't need it. Seems like this was only
+    /// wallpapering (in most cases) the underlying problem in #991.
+    pub epoch: Epoch,
+    /// A pipeline id to key the payload with, along with the epoch.
+    pub pipeline_id: PipelineId,
+    pub display_list_data: Vec<u8>,
+    pub auxiliary_lists_data: Vec<u8>
+}
+
+impl Payload {
+    /// Convert the payload to a raw byte vector, in order for it to be
+    /// efficiently shared via shmem, for example.
+    ///
+    /// TODO(emilio, #1049): Consider moving the IPC boundary to the
+    /// constellation in Servo and remove this complexity from WR.
+    pub fn to_data(&self) -> Vec<u8> {
+        let mut data = Vec::with_capacity(mem::size_of::<u32>() +
+                                          2 * mem::size_of::<u32>() +
+                                          mem::size_of::<u64>() +
+                                          self.display_list_data.len() +
+                                          mem::size_of::<u64>() +
+                                          self.auxiliary_lists_data.len());
+        data.write_u32::<LittleEndian>(self.epoch.0).unwrap();
+        data.write_u32::<LittleEndian>(self.pipeline_id.0).unwrap();
+        data.write_u32::<LittleEndian>(self.pipeline_id.1).unwrap();
+        data.write_u64::<LittleEndian>(self.display_list_data.len() as u64).unwrap();
+        data.extend_from_slice(&self.display_list_data);
+        data.write_u64::<LittleEndian>(self.auxiliary_lists_data.len() as u64).unwrap();
+        data.extend_from_slice(&self.auxiliary_lists_data);
+        data
+    }
+
+    /// Deserializes the given payload from a raw byte vector.
+    pub fn from_data(data: Vec<u8>) -> Payload {
+        let mut payload_reader = Cursor::new(&data[..]);
+        let epoch = Epoch(payload_reader.read_u32::<LittleEndian>().unwrap());
+        let pipeline_id = PipelineId(payload_reader.read_u32::<LittleEndian>().unwrap(),
+                                     payload_reader.read_u32::<LittleEndian>().unwrap());
+
+        let dl_size = payload_reader.read_u64::<LittleEndian>().unwrap() as usize;
+        let mut built_display_list_data = vec![0; dl_size];
+        payload_reader.read_exact(&mut built_display_list_data[..]).unwrap();
+
+        let aux_size = payload_reader.read_u64::<LittleEndian>().unwrap() as usize;
+        let mut auxiliary_lists_data = vec![0; aux_size];
+        payload_reader.read_exact(&mut auxiliary_lists_data[..]).unwrap();
+
+        Payload {
+            epoch: epoch,
+            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>>.
+pub trait PayloadSenderHelperMethods {
+    fn send_payload(&self, data: Payload) -> Result<(), Error>;
+}
+
+pub trait PayloadReceiverHelperMethods {
+    fn recv_payload(&self) -> Result<Payload, Error>;
 }
 
 #[cfg(not(feature = "ipc"))]
 include!("channel_mpsc.rs");
 
 #[cfg(feature = "ipc")]
 include!("channel_ipc.rs");
--- a/gfx/webrender_traits/src/channel_ipc.rs
+++ b/gfx/webrender_traits/src/channel_ipc.rs
@@ -1,32 +1,41 @@
 /* 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 serde::{Deserialize, Serialize};
-use std::io::Error;
+use std::io::{Error, ErrorKind};
+use std::io;
+use std::error;
 
 use ipc_channel::ipc::{self, IpcSender, IpcReceiver, IpcBytesSender, IpcBytesReceiver};
 
 ///
 /// Handles the channel implementation when IPC is enabled.
 ///
 
 pub type MsgSender<T> = IpcSender<T>;
 
 pub type MsgReceiver<T> = IpcReceiver<T>;
 
 pub type PayloadSender = IpcBytesSender;
 
 pub type PayloadReceiver = IpcBytesReceiver;
 
-impl PayloadHelperMethods for PayloadSender {
-    fn send_vec(&self, data: Vec<u8>) -> Result<(), Error> {
-        self.send(&data)
+impl PayloadSenderHelperMethods for PayloadSender {
+    fn send_payload(&self, data: Payload) -> Result<(), Error> {
+        self.send(&data.to_data())
+    }
+}
+
+impl PayloadReceiverHelperMethods for PayloadReceiver {
+    fn recv_payload(&self) -> Result<Payload, Error> {
+        self.recv().map(|data| Payload::from_data(data) )
+                   .map_err(|e| io::Error::new(ErrorKind::Other, error::Error::description(&e)))
     }
 }
 
 pub fn msg_channel<T: Serialize + Deserialize>() -> Result<(MsgSender<T>, MsgReceiver<T>), Error> {
     ipc::channel()
 }
 
 pub fn payload_channel() -> Result<(PayloadSender, PayloadReceiver), Error> {
--- a/gfx/webrender_traits/src/channel_mpsc.rs
+++ b/gfx/webrender_traits/src/channel_mpsc.rs
@@ -1,30 +1,35 @@
 /* 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 serde::{Deserialize, Serialize};
 use std::io::{Error, ErrorKind};
-
 use serde::{Deserializer, Serializer};
 
 use std::sync::mpsc;
 
 ///
 /// Handles the channel implementation when in process channels are enabled.
 ///
 
-pub type PayloadSender = MsgSender<Vec<u8>>;
+pub type PayloadSender = MsgSender<Payload>;
 
-pub type PayloadReceiver = MsgReceiver<Vec<u8>>;
+pub type PayloadReceiver = MsgReceiver<Payload>;
 
-impl PayloadHelperMethods for PayloadSender {
-    fn send_vec(&self, data: Vec<u8>) -> Result<(), Error> {
-        self.send(data)
+impl PayloadSenderHelperMethods for PayloadSender {
+    fn send_payload(&self, payload: Payload) -> Result<(), Error> {
+        self.send(payload)
+    }
+}
+
+impl PayloadReceiverHelperMethods for PayloadReceiver {
+    fn recv_payload(&self) -> Result<Payload, Error> {
+        self.recv()
     }
 }
 
 pub struct MsgReceiver<T> {
     rx: mpsc::Receiver<T>,
 }
 
 impl<T> MsgReceiver<T> {
--- a/gfx/webrender_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -222,16 +222,17 @@ pub struct GradientStop {
 known_heap_size!(0, GradientStop);
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RadialGradient {
     pub start_center: LayoutPoint,
     pub start_radius: f32,
     pub end_center: LayoutPoint,
     pub end_radius: f32,
+    pub ratio_xy: f32,
     pub stops: ItemRange,
     pub extend_mode: ExtendMode,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RadialGradientDisplayItem {
     pub gradient: RadialGradient,
 }
@@ -503,31 +504,31 @@ impl ComplexClipRegion {
         } else {
             None
         }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum ScrollLayerId {
-    Clip(usize, PipelineId),
-    ClipExternalId(usize, PipelineId),
-    ReferenceFrame(usize, PipelineId),
+    Clip(u64, PipelineId),
+    ClipExternalId(u64, PipelineId),
+    ReferenceFrame(u64, PipelineId),
 }
 
 impl ScrollLayerId {
     pub fn root_scroll_layer(pipeline_id: PipelineId) -> ScrollLayerId {
         ScrollLayerId::Clip(0, pipeline_id)
     }
 
     pub fn root_reference_frame(pipeline_id: PipelineId) -> ScrollLayerId {
         ScrollLayerId::ReferenceFrame(0, pipeline_id)
     }
 
-    pub fn new(id: usize, pipeline_id: PipelineId) -> 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::ReferenceFrame(_, pipeline_id) => pipeline_id,
@@ -536,15 +537,15 @@ impl ScrollLayerId {
 
     pub fn is_reference_frame(&self) -> bool {
         match *self {
             ScrollLayerId::ReferenceFrame(..) => true,
             _ => false,
         }
     }
 
-    pub fn external_id(&self) -> Option<usize> {
+    pub fn external_id(&self) -> Option<u64> {
         match *self {
             ScrollLayerId::ClipExternalId(id, _) => Some(id),
             _ => None,
         }
     }
 }
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -80,25 +80,32 @@ impl BuiltDisplayList {
         &self.descriptor
     }
 
     pub fn all_display_items<'a>(&'a self) -> &'a [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)
+        }
+    }
+
 }
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub list: Vec<DisplayItem>,
     auxiliary_lists_builder: AuxiliaryListsBuilder,
     pub pipeline_id: PipelineId,
     clip_stack: Vec<ScrollLayerId>,
-    next_scroll_layer_id: usize,
+    next_scroll_layer_id: u64,
 }
 
 impl DisplayListBuilder {
     pub fn new(pipeline_id: PipelineId) -> DisplayListBuilder {
         DisplayListBuilder {
             list: Vec::new(),
             auxiliary_lists_builder: AuxiliaryListsBuilder::new(),
             pipeline_id: pipeline_id,
@@ -211,41 +218,144 @@ impl DisplayListBuilder {
                 blur_radius: blur_radius,
                 glyph_options: glyph_options,
             });
 
             self.push_item(item, rect, clip);
         }
     }
 
+    // Gradients can be defined with stops outside the range of [0, 1]
+    // when this happens the gradient needs to be normalized by adjusting
+    // the gradient stops and gradient line into an equivalent gradient
+    // with stops in the range [0, 1]. this is done by moving the beginning
+    // of the gradient line to where stop[0] and the end of the gradient line
+    // to stop[n-1]. this function adjusts the stops in place, and returns
+    // the amount to adjust the gradient line start and stop
+    fn normalize_stops(stops: &mut Vec<GradientStop>,
+                       extend_mode: ExtendMode) -> (f32, f32) {
+        assert!(stops.len() >= 2);
+
+        let first = *stops.first().unwrap();
+        let last = *stops.last().unwrap();
+
+        assert!(first.offset <= last.offset);
+
+        let stops_origin = first.offset;
+        let stops_delta = last.offset - first.offset;
+
+        if stops_delta > 0.000001 {
+            for stop in stops {
+                stop.offset = (stop.offset - stops_origin) / stops_delta;
+            }
+
+            (first.offset, last.offset)
+        } else {
+            // We have a degenerate gradient and can't accurately transform the stops
+            // what happens here depends on the repeat behavior, but in any case
+            // we reconstruct the gradient stops to something simpler and equivalent
+            stops.clear();
+
+            match extend_mode {
+                ExtendMode::Clamp => {
+                    // This gradient is two colors split at the offset of the stops,
+                    // so create a gradient with two colors split at 0.5 and adjust
+                    // the gradient line so 0.5 is at the offset of the stops
+                    stops.push(GradientStop {
+                        color: first.color,
+                        offset: 0.0,
+                    });
+                    stops.push(GradientStop {
+                        color: first.color,
+                        offset: 0.5,
+                    });
+                    stops.push(GradientStop {
+                        color: last.color,
+                        offset: 0.5,
+                    });
+                    stops.push(GradientStop {
+                        color: last.color,
+                        offset: 1.0,
+                    });
+
+                    let offset = last.offset;
+
+                    (offset - 0.5, offset + 0.5)
+                }
+                ExtendMode::Repeat => {
+                    // A repeating gradient with stops that are all in the same
+                    // position should just display the last color. I believe the
+                    // spec says that it should be the average color of the gradient,
+                    // but this matches what Gecko and Blink does
+                    stops.push(GradientStop {
+                        color: last.color,
+                        offset: 0.0,
+                    });
+                    stops.push(GradientStop {
+                        color: last.color,
+                        offset: 1.0,
+                    });
+
+                    (0.0, 1.0)
+                }
+            }
+        }
+    }
+
     pub fn create_gradient(&mut self,
                            start_point: LayoutPoint,
                            end_point: LayoutPoint,
-                           stops: Vec<GradientStop>,
+                           mut stops: Vec<GradientStop>,
                            extend_mode: ExtendMode) -> Gradient {
+        let (start_offset,
+             end_offset) = DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
+
+        let start_to_end = end_point - start_point;
+
         Gradient {
-            start_point: start_point,
-            end_point: end_point,
+            start_point: start_point + start_to_end * start_offset,
+            end_point: start_point + start_to_end * end_offset,
             stops: self.auxiliary_lists_builder.add_gradient_stops(&stops),
             extend_mode: extend_mode,
         }
     }
 
     pub fn create_radial_gradient(&mut self,
-                                  start_center: LayoutPoint,
-                                  start_radius: f32,
-                                  end_center: LayoutPoint,
-                                  end_radius: f32,
-                                  stops: Vec<GradientStop>,
+                                  center: LayoutPoint,
+                                  radius: LayoutSize,
+                                  mut stops: Vec<GradientStop>,
                                   extend_mode: ExtendMode) -> RadialGradient {
+        let (start_offset,
+             end_offset) = DisplayListBuilder::normalize_stops(&mut stops, extend_mode);
+
+        RadialGradient {
+            start_center: center,
+            start_radius: radius.width * start_offset,
+            end_center: center,
+            end_radius: radius.width * end_offset,
+            ratio_xy: radius.width / radius.height,
+            stops: self.auxiliary_lists_builder.add_gradient_stops(&stops),
+            extend_mode: extend_mode,
+        }
+    }
+
+    pub fn create_complex_radial_gradient(&mut self,
+                                          start_center: LayoutPoint,
+                                          start_radius: f32,
+                                          end_center: LayoutPoint,
+                                          end_radius: f32,
+                                          ratio_xy: f32,
+                                          stops: Vec<GradientStop>,
+                                          extend_mode: ExtendMode) -> RadialGradient {
         RadialGradient {
             start_center: start_center,
             start_radius: start_radius,
             end_center: end_center,
             end_radius: end_radius,
+            ratio_xy: ratio_xy,
             stops: self.auxiliary_lists_builder.add_gradient_stops(&stops),
             extend_mode: extend_mode,
         }
     }
 
     pub fn push_border(&mut self,
                        rect: LayoutRect,
                        clip: ClipRegion,
@@ -280,40 +390,30 @@ impl DisplayListBuilder {
         });
 
         self.push_item(item, rect, clip);
     }
 
     pub fn push_gradient(&mut self,
                          rect: LayoutRect,
                          clip: ClipRegion,
-                         start_point: LayoutPoint,
-                         end_point: LayoutPoint,
-                         stops: Vec<GradientStop>,
-                         extend_mode: ExtendMode) {
+                         gradient: Gradient) {
         let item = SpecificDisplayItem::Gradient(GradientDisplayItem {
-            gradient: self.create_gradient(start_point, end_point, stops, extend_mode),
+            gradient: gradient,
         });
 
         self.push_item(item, rect, clip);
     }
 
     pub fn push_radial_gradient(&mut self,
                                 rect: LayoutRect,
                                 clip: ClipRegion,
-                                start_center: LayoutPoint,
-                                start_radius: f32,
-                                end_center: LayoutPoint,
-                                end_radius: f32,
-                                stops: Vec<GradientStop>,
-                                extend_mode: ExtendMode) {
+                                gradient: RadialGradient) {
         let item = SpecificDisplayItem::RadialGradient(RadialGradientDisplayItem {
-            gradient: self.create_radial_gradient(start_center, start_radius,
-                                                  end_center, end_radius,
-                                                  stops, extend_mode),
+            gradient: gradient,
         });
 
         self.push_item(item, rect, clip);
     }
 
     pub fn push_stacking_context(&mut self,
                                  scroll_policy: ScrollPolicy,
                                  bounds: LayoutRect,
@@ -628,8 +728,14 @@ unsafe fn convert_vec_pod_to_blob<T>(mut
     mem::forget(data);
     v
 }
 
 unsafe fn convert_blob_to_pod<T>(blob: &[u8]) -> &[T] where T: Copy + 'static {
     slice::from_raw_parts(blob.as_ptr() as *const T, blob.len() / mem::size_of::<T>())
 }
 
+// this variant of the above lets us convert without needing to make a copy
+unsafe fn convert_vec_blob_to_pod<T>(mut data: Vec<u8>) -> Vec<T> where T: Copy + 'static {
+    let v = Vec::from_raw_parts(data.as_mut_ptr() as *mut T, data.len() / mem::size_of::<T>(), data.capacity() / mem::size_of::<T>());
+    mem::forget(data);
+    v
+}
--- a/gfx/webrender_traits/src/image.rs
+++ b/gfx/webrender_traits/src/image.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/. */
 
 use std::sync::Arc;
+use DeviceUintRect;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ImageKey(pub u32, pub u32);
 
 impl ImageKey {
     pub fn new(key0: u32, key1: u32) -> ImageKey {
         ImageKey(key0, key1)
@@ -64,22 +65,34 @@ impl ImageDescriptor {
         }
     }
 
     pub fn compute_stride(&self) -> u32 {
         self.stride.unwrap_or(self.width * self.format.bytes_per_pixel().unwrap())
     }
 }
 
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub enum ExternalImageType {
+    Texture2DHandle,    // gl TEXTURE_2D handle
+    TextureRectHandle,  // gl TEXTURE_RECT handle
+    ExternalBuffer,
+}
+
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub struct ExternalImageData {
+    pub id: ExternalImageId,
+    pub image_type: ExternalImageType,
+}
+
 #[derive(Clone, Serialize, Deserialize)]
 pub enum ImageData {
     Raw(Arc<Vec<u8>>),
     Blob(Arc<BlobImageData>),
-    ExternalHandle(ExternalImageId),
-    ExternalBuffer(ExternalImageId),
+    External(ExternalImageData),
 }
 
 impl ImageData {
     pub fn new(bytes: Vec<u8>) -> ImageData {
         ImageData::Raw(Arc::new(bytes))
     }
 
     pub fn new_shared(bytes: Arc<Vec<u8>>) -> ImageData {
@@ -94,17 +107,18 @@ impl ImageData {
         ImageData::Blob(commands)
     }
 }
 
 pub trait BlobImageRenderer: Send {
     fn request_blob_image(&mut self,
                             key: ImageKey,
                             data: Arc<BlobImageData>,
-                            descriptor: &BlobImageDescriptor);
+                            descriptor: &BlobImageDescriptor,
+                            dirty_rect: Option<DeviceUintRect>);
     fn resolve_blob_image(&mut self, key: ImageKey) -> BlobImageResult;
 }
 
 pub type BlobImageData = Vec<u8>;
 
 pub type BlobImageResult = Result<RasterizedBlobImage, BlobImageError>;
 
 #[repr(C)]