Bug 1363683 - Update webrender to cset 7f37799d63ed80a07675ad599526290843394c99. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 15 May 2017 14:14:26 -0400
changeset 577962 fb18194afdc8a6ef5c5788a2592c4ddcb7d4f4b3
parent 577961 e377209ad3e8246c20ab53c4fbb4e7a4c802a24b
child 577963 a5a2eaed91ea6c4eff278e488581d6014502ed9d
push id58844
push userkgupta@mozilla.com
push dateMon, 15 May 2017 18:23:13 +0000
reviewersjrmuizel
bugs1363683
milestone55.0a1
Bug 1363683 - Update webrender to cset 7f37799d63ed80a07675ad599526290843394c99. r?jrmuizel MozReview-Commit-ID: Cj3z0YDDlY3
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/examples/basic.rs
gfx/webrender/examples/blob.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/cs_clip_border.fs.glsl
gfx/webrender/res/cs_clip_border.glsl
gfx/webrender/res/cs_clip_border.vs.glsl
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_blend.vs.glsl
gfx/webrender/res/ps_border.fs.glsl
gfx/webrender/res/ps_border.glsl
gfx/webrender/res/ps_border.vs.glsl
gfx/webrender/res/ps_border_corner.fs.glsl
gfx/webrender/res/ps_border_corner.vs.glsl
gfx/webrender/res/ps_border_edge.fs.glsl
gfx/webrender/res/ps_border_edge.glsl
gfx/webrender/res/ps_border_edge.vs.glsl
gfx/webrender/res/ps_composite.vs.glsl
gfx/webrender/res/ps_split_composite.fs.glsl
gfx/webrender/res/ps_split_composite.glsl
gfx/webrender/res/ps_split_composite.vs.glsl
gfx/webrender/res/ps_yuv_image.fs.glsl
gfx/webrender/res/ps_yuv_image.vs.glsl
gfx/webrender/src/border.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/ellipse.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/mask_cache.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/platform/unix/font.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/print_tree.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/src/util.rs
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/channel.rs
gfx/webrender_traits/src/color.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/image.rs
gfx/webrender_traits/src/webgl.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: 964df2fa00f330daf4f233669e37133f93113792
+Latest Commit: 7f37799d63ed80a07675ad599526290843394c99
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.36.0"
+version = "0.39.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"]
@@ -20,22 +20,22 @@ byteorder = "1.0"
 euclid = "0.11.2"
 fnv = "1.0"
 gleam = "0.4.3"
 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"
+rayon = {version = "0.7", features = ["unstable"]}
 webrender_traits = {path = "../webrender_traits"}
 bitflags = "0.7"
-gamma-lut = "0.1"
+gamma-lut = "0.2"
 thread_profiler = "0.1.1"
-plane-split = "0.2.1"
+plane-split = "0.3"
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
 servo-glutin = "0.10.1"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.2", default-features = false }
 
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -257,19 +257,20 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
@@ -367,17 +368,17 @@ fn main() {
 
         let clip = builder.push_clip_region(&bounds, Vec::new(), None);
         builder.push_text(text_bounds,
                           clip,
                           &glyphs,
                           font_key,
                           ColorF::new(1.0, 1.0, 0.0, 1.0),
                           Au::from_px(32),
-                          Au::from_px(0),
+                          0.0,
                           None);
     }
 
     if false { // draw box shadow?
         let rect = LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(0.0, 0.0));
         let simple_box_bounds = LayoutRect::new(LayoutPoint::new(20.0, 200.0),
                                                 LayoutSize::new(50.0, 50.0));
         let offset = LayoutPoint::new(10.0, 10.0);
--- a/gfx/webrender/examples/blob.rs
+++ b/gfx/webrender/examples/blob.rs
@@ -189,19 +189,20 @@ fn main() {
     api.add_image(
         blob_img2,
         ImageDescriptor::new(200, 200, ImageFormat::RGBA8, true),
         ImageData::new_blob_image(serialize_blob(ColorU::new(50, 150, 50, 255))),
         None,
     );
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -95,19 +95,20 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -229,19 +229,20 @@ fn main() {
 
     let notifier = Box::new(Notifier::new(window.create_window_proxy()));
     renderer.set_render_notifier(notifier);
 
     let epoch = Epoch(0);
     let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
 
     let pipeline_id = PipelineId(0, 0);
-    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+    let layout_size = LayoutSize::new(width as f32, height as f32);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id, layout_size);
 
-    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    let bounds = LayoutRect::new(LayoutPoint::zero(), layout_size);
     builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
                                   bounds,
                                   None,
                                   TransformStyle::Flat,
                                   None,
                                   webrender_traits::MixBlendMode::Normal,
                                   Vec::new());
 
--- a/gfx/webrender/res/cs_clip_border.fs.glsl
+++ b/gfx/webrender/res/cs_clip_border.fs.glsl
@@ -16,15 +16,25 @@ void main(void) {
     float d1 = distance_to_line(vPoint_Tangent1.xy,
                                 vPoint_Tangent1.zw,
                                 clip_relative_pos);
 
     // Get AA widths based on zoom / scale etc.
     vec2 fw = fwidth(local_pos);
     float afwidth = length(fw);
 
+    // SDF subtract edges for dash clip
+    float dash_distance = max(d0, -d1);
+
+    // Get distance from dot.
+    float dot_distance = distance(clip_relative_pos, vDotParams.xy) - vDotParams.z;
+
+    // Select between dot/dash clip based on mode.
+    float d = mix(dash_distance, dot_distance, vAlphaMask.x);
+
     // Apply AA over half a device pixel for the clip.
-    float d = smoothstep(-0.5 * afwidth,
-                         0.5 * afwidth,
-                         max(d0, -d1));
+    d = 1.0 - smoothstep(0.0, 0.5 * afwidth, d);
+
+    // Completely mask out clip if zero'ing out the rect.
+    d = d * vAlphaMask.y;
 
     oFragColor = vec4(d, 0.0, 0.0, 1.0);
 }
--- a/gfx/webrender/res/cs_clip_border.glsl
+++ b/gfx/webrender/res/cs_clip_border.glsl
@@ -5,8 +5,10 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec3 vPos;
 
 flat varying vec2 vClipCenter;
 
 flat varying vec4 vPoint_Tangent0;
 flat varying vec4 vPoint_Tangent1;
+flat varying vec3 vDotParams;
+flat varying vec2 vAlphaMask;
--- a/gfx/webrender/res/cs_clip_border.vs.glsl
+++ b/gfx/webrender/res/cs_clip_border.vs.glsl
@@ -1,52 +1,112 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// Matches BorderCorner enum in border.rs
+#define CORNER_TOP_LEFT     0
+#define CORNER_TOP_RIGHT    1
+#define CORNER_BOTTOM_LEFT  2
+#define CORNER_BOTTOM_RIGHT 3
+
+// Matches BorderCornerClipKind enum in border.rs
+#define CLIP_MODE_DASH      0
+#define CLIP_MODE_DOT       1
+
 // Header for a border corner clip.
 struct BorderCorner {
     RectWithSize rect;
     vec2 clip_center;
-    vec2 sign_modifier;
+    int corner;
+    int clip_mode;
 };
 
 BorderCorner fetch_border_corner(int index) {
     vec4 data[2] = fetch_data_2(index);
     return BorderCorner(RectWithSize(data[0].xy, data[0].zw),
                         data[1].xy,
-                        data[1].zw);
+                        int(data[1].z),
+                        int(data[1].w));
 }
 
 // Per-dash clip information.
-// TODO: Expand this to handle dots in the future!
-struct BorderClip {
+struct BorderClipDash {
     vec4 point_tangent_0;
     vec4 point_tangent_1;
 };
 
-BorderClip fetch_border_clip(int index) {
+BorderClipDash fetch_border_clip_dash(int index) {
     vec4 data[2] = fetch_data_2(index);
-    return BorderClip(data[0], data[1]);
+    return BorderClipDash(data[0], data[1]);
+}
+
+// Per-dot clip information.
+struct BorderClipDot {
+    vec3 center_radius;
+};
+
+BorderClipDot fetch_border_clip_dot(int index) {
+    vec4 data[2] = fetch_data_2(index);
+    return BorderClipDot(data[0].xyz);
 }
 
 void main(void) {
     CacheClipInstance cci = fetch_clip_item(gl_InstanceID);
     ClipArea area = fetch_clip_area(cci.render_task_index);
     Layer layer = fetch_layer(cci.layer_index);
 
     // Fetch the header information for this corner clip.
     BorderCorner corner = fetch_border_corner(cci.data_index);
     vClipCenter = corner.clip_center;
 
-    // Fetch the information about this particular dash.
-    BorderClip clip = fetch_border_clip(cci.data_index + cci.segment_index + 1);
-    vPoint_Tangent0 = clip.point_tangent_0 * corner.sign_modifier.xyxy;
-    vPoint_Tangent1 = clip.point_tangent_1 * corner.sign_modifier.xyxy;
+    if (cci.segment_index == 0) {
+        // The first segment is used to zero out the border corner.
+        vAlphaMask = vec2(0.0);
+        vDotParams = vec3(0.0);
+        vPoint_Tangent0 = vec4(1.0);
+        vPoint_Tangent1 = vec4(1.0);
+    } else {
+        vec2 sign_modifier;
+        switch (corner.corner) {
+            case CORNER_TOP_LEFT:
+                sign_modifier = vec2(-1.0);
+                break;
+            case CORNER_TOP_RIGHT:
+                sign_modifier = vec2(1.0, -1.0);
+                break;
+            case CORNER_BOTTOM_RIGHT:
+                sign_modifier = vec2(1.0);
+                break;
+            case CORNER_BOTTOM_LEFT:
+                sign_modifier = vec2(-1.0, 1.0);
+                break;
+        };
+
+        switch (corner.clip_mode) {
+            case CLIP_MODE_DASH: {
+                // Fetch the information about this particular dash.
+                BorderClipDash dash = fetch_border_clip_dash(cci.data_index + cci.segment_index);
+                vPoint_Tangent0 = dash.point_tangent_0 * sign_modifier.xyxy;
+                vPoint_Tangent1 = dash.point_tangent_1 * sign_modifier.xyxy;
+                vDotParams = vec3(0.0);
+                vAlphaMask = vec2(0.0, 1.0);
+                break;
+            }
+            case CLIP_MODE_DOT: {
+                BorderClipDot cdot = fetch_border_clip_dot(cci.data_index + cci.segment_index);
+                vPoint_Tangent0 = vec4(1.0);
+                vPoint_Tangent1 = vec4(1.0);
+                vDotParams = vec3(cdot.center_radius.xy * sign_modifier, cdot.center_radius.z);
+                vAlphaMask = vec2(1.0, 1.0);
+                break;
+            }
+        }
+    }
 
     // Get local vertex position for the corner rect.
     // TODO(gw): We could reduce the number of pixels written here
     // by calculating a tight fitting bounding box of the dash itself.
     vec2 pos = corner.rect.p0 + aPosition.xy * corner.rect.size;
 
     // Transform to world pos
     vec4 world_pos = layer.transform * vec4(pos, 0.0, 1.0);
--- a/gfx/webrender/res/cs_clip_rectangle.fs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.fs.glsl
@@ -1,41 +1,57 @@
 /* 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/. */
 
-float rounded_rect(vec2 pos) {
-    vec2 ref_tl = vClipRect.xy + vec2( vClipRadius.x,  vClipRadius.x);
-    vec2 ref_tr = vClipRect.zy + vec2(-vClipRadius.y,  vClipRadius.y);
-    vec2 ref_br = vClipRect.zw + vec2(-vClipRadius.z, -vClipRadius.z);
-    vec2 ref_bl = vClipRect.xw + vec2( vClipRadius.w, -vClipRadius.w);
+float clip_against_ellipse_if_needed(vec2 pos,
+                                     float current_distance,
+                                     vec4 ellipse_center_radius,
+                                     vec2 sign_modifier,
+                                     float afwidth) {
+    float ellipse_distance = distance_to_ellipse(pos - ellipse_center_radius.xy,
+                                                 ellipse_center_radius.zw);
 
-    float d_tl = distance(pos, ref_tl);
-    float d_tr = distance(pos, ref_tr);
-    float d_br = distance(pos, ref_br);
-    float d_bl = distance(pos, ref_bl);
+    return mix(current_distance,
+               ellipse_distance + afwidth,
+               all(lessThan(sign_modifier * pos, sign_modifier * ellipse_center_radius.xy)));
+}
 
-    float pixels_per_fragment = length(fwidth(pos.xy));
-    float nudge = 0.5 * pixels_per_fragment;
-    vec4 distances = vec4(d_tl, d_tr, d_br, d_bl) - vClipRadius + nudge;
+float rounded_rect(vec2 pos) {
+    float current_distance = 0.0;
+
+    // Apply AA
+    float afwidth = 0.5 * length(fwidth(pos));
 
-    bvec4 is_out = bvec4(pos.x < ref_tl.x && pos.y < ref_tl.y,
-                         pos.x > ref_tr.x && pos.y < ref_tr.y,
-                         pos.x > ref_br.x && pos.y > ref_br.y,
-                         pos.x < ref_bl.x && pos.y > ref_bl.y);
+    // Clip against each ellipse.
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_TL,
+                                                      vec2(1.0),
+                                                      afwidth);
+
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_TR,
+                                                      vec2(-1.0, 1.0),
+                                                      afwidth);
 
-    float distance_from_border = dot(vec4(is_out),
-                                     max(vec4(0.0, 0.0, 0.0, 0.0), distances));
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_BR,
+                                                      vec2(-1.0),
+                                                      afwidth);
 
-    // Move the distance back into pixels.
-    distance_from_border /= pixels_per_fragment;
-    // Apply a more gradual fade out to transparent.
-    //distance_from_border -= 0.5;
+    current_distance = clip_against_ellipse_if_needed(pos,
+                                                      current_distance,
+                                                      vClipCenter_Radius_BL,
+                                                      vec2(1.0, -1.0),
+                                                      afwidth);
 
-    return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
+    return smoothstep(0.0, afwidth, 1.0 - current_distance);
 }
 
 
 void main(void) {
     float alpha = 1.f;
     vec2 local_pos = init_transform_fs(vPos, alpha);
 
     float clip_alpha = rounded_rect(local_pos);
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -1,10 +1,12 @@
 #line 1
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec3 vPos;
-flat varying vec4 vClipRect;
-flat varying vec4 vClipRadius;
 flat varying float vClipMode;
+flat varying vec4 vClipCenter_Radius_TL;
+flat varying vec4 vClipCenter_Radius_TR;
+flat varying vec4 vClipCenter_Radius_BL;
+flat varying vec4 vClipCenter_Radius_BR;
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.vs.glsl
@@ -52,14 +52,25 @@ void main(void) {
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
                                                cci.segment_index);
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
-    vClipRect = vec4(local_rect.p0, local_rect.p0 + local_rect.size);
-    vClipRadius = vec4(clip.top_left.outer_inner_radius.x,
-                       clip.top_right.outer_inner_radius.x,
-                       clip.bottom_right.outer_inner_radius.x,
-                       clip.bottom_left.outer_inner_radius.x);
+
+    RectWithEndpoint clip_rect = to_rect_with_endpoint(local_rect);
+
+    vClipCenter_Radius_TL = vec4(clip_rect.p0 + clip.top_left.outer_inner_radius.xy,
+                                 clip.top_left.outer_inner_radius.xy);
+
+    vClipCenter_Radius_TR = vec4(clip_rect.p1.x - clip.top_right.outer_inner_radius.x,
+                                 clip_rect.p0.y + clip.top_right.outer_inner_radius.y,
+                                 clip.top_right.outer_inner_radius.xy);
+
+    vClipCenter_Radius_BR = vec4(clip_rect.p1 - clip.bottom_right.outer_inner_radius.xy,
+                                 clip.bottom_right.outer_inner_radius.xy);
+
+    vClipCenter_Radius_BL = vec4(clip_rect.p0.x + clip.bottom_left.outer_inner_radius.x,
+                                 clip_rect.p1.y - clip.bottom_left.outer_inner_radius.y,
+                                 clip.bottom_left.outer_inner_radius.xy);
 }
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -257,16 +257,33 @@ AlphaBatchTask fetch_alpha_batch_task(in
     task.render_target_origin = data.data0.xy;
     task.size = data.data0.zw;
     task.screen_space_origin = data.data1.xy;
     task.render_target_layer_index = data.data1.z;
 
     return task;
 }
 
+struct ReadbackTask {
+    vec2 render_target_origin;
+    vec2 size;
+    float render_target_layer_index;
+};
+
+ReadbackTask fetch_readback_task(int index) {
+    RenderTaskData data = fetch_render_task(index);
+
+    ReadbackTask task;
+    task.render_target_origin = data.data0.xy;
+    task.size = data.data0.zw;
+    task.render_target_layer_index = data.data1.x;
+
+    return task;
+}
+
 struct ClipArea {
     vec4 task_bounds;
     vec4 screen_origin_target_index;
     vec4 inner_rect;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
@@ -319,18 +336,18 @@ RadialGradient fetch_radial_gradient(int
 
 struct Border {
     vec4 style;
     vec4 widths;
     vec4 colors[4];
     vec4 radii[2];
 };
 
-vec4 get_effective_border_widths(Border border) {
-    switch (int(border.style.x)) {
+vec4 get_effective_border_widths(Border border, int style) {
+    switch (style) {
         case BORDER_STYLE_DOUBLE:
             // Calculate the width of a border segment in a style: double
             // border. Round to the nearest CSS pixel.
 
             // The CSS spec doesn't define what width each of the segments
             // in a style: double border should be. It only says that the
             // sum of the segments should be equal to the total border
             // width. We pick to make the segments (almost) equal thirds
@@ -795,31 +812,21 @@ float signed_distance_rect(vec2 pos, vec
     vec2 d = max(p0 - pos, pos - p1);
     return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
 }
 
 vec2 init_transform_fs(vec3 local_pos, out float fragment_alpha) {
     fragment_alpha = 1.0;
     vec2 pos = local_pos.xy / local_pos.z;
 
-    // Because the local rect is placed on whole coordinates, but the interpolation
-    // occurs at pixel centers, we need to offset the signed distance by that amount.
-    // In the simple case of no zoom, and no transform, this is 0.5. However, we
-    // need to scale this by the amount that the local rect is changing by per
-    // fragment, based on the current zoom and transform.
-    vec2 fw = fwidth(pos.xy);
-    vec2 dxdy = 0.5 * fw;
-
-    // Now get the actual signed distance. Inset the local rect by the offset amount
-    // above to get correct distance values. This ensures that we only apply
-    // anti-aliasing when the fragment has partial coverage.
-    float d = signed_distance_rect(pos, vLocalBounds.xy + dxdy, vLocalBounds.zw - dxdy);
+    // Now get the actual signed distance.
+    float d = signed_distance_rect(pos, vLocalBounds.xy, vLocalBounds.zw);
 
     // Find the appropriate distance to apply the AA smoothstep over.
-    float afwidth = 0.5 / length(fw);
+    float afwidth = 0.5 * length(fwidth(pos.xy));
 
     // Only apply AA to fragments outside the signed distance field.
     fragment_alpha = 1.0 - smoothstep(0.0, afwidth, d);
 
     return pos;
 }
 #endif //WR_FEATURE_TRANSFORM
 
@@ -866,9 +873,71 @@ vec4 sample_gradient(float offset, float
     // Gradient color entries are encoded with high bits in one row and low bits in the next
     // So use linear filtering to mix (gradient_index + 1) with (gradient_index)
     float y = gradient_index * 2.0 + 0.5 + 1.0 / 256.0;
 
     // Finally sample and apply dithering
     return dither(texture(sGradients, vec2(x, y) / gradient_size));
 }
 
+//
+// Signed distance to an ellipse.
+// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
+// Note that this fails for exact circles.
+//
+float sdEllipse( vec2 p, in vec2 ab ) {
+    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
+    float l = ab.y*ab.y - ab.x*ab.x;
+
+    float m = ab.x*p.x/l;
+    float n = ab.y*p.y/l;
+    float m2 = m*m;
+    float n2 = n*n;
+
+    float c = (m2 + n2 - 1.0)/3.0;
+    float c3 = c*c*c;
+
+    float q = c3 + m2*n2*2.0;
+    float d = c3 + m2*n2;
+    float g = m + m*n2;
+
+    float co;
+
+    if( d<0.0 )
+    {
+        float p = acos(q/c3)/3.0;
+        float s = cos(p);
+        float t = sin(p)*sqrt(3.0);
+        float rx = sqrt( -c*(s + t + 2.0) + m2 );
+        float ry = sqrt( -c*(s - t + 2.0) + m2 );
+        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
+    }
+    else
+    {
+        float h = 2.0*m*n*sqrt( d );
+        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
+        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
+        float rx = -s - u - c*4.0 + 2.0*m2;
+        float ry = (s - u)*sqrt(3.0);
+        float rm = sqrt( rx*rx + ry*ry );
+        float p = ry/sqrt(rm-rx);
+        co = (p + 2.0*g/rm - m)/2.0;
+    }
+
+    float si = sqrt( 1.0 - co*co );
+
+    vec2 r = vec2( ab.x*co, ab.y*si );
+
+    return length(r - p ) * sign(p.y-r.y);
+}
+
+float distance_to_ellipse(vec2 p, vec2 radii) {
+    // sdEllipse fails on exact circles, so handle equal
+    // radii here. The branch coherency should make this
+    // a performance win for the circle case too.
+    if (radii.x == radii.y) {
+        return length(p) - radii.x;
+    } else {
+        return sdEllipse(p, radii);
+    }
+}
+
 #endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/ps_blend.vs.glsl
+++ b/gfx/webrender/res/ps_blend.vs.glsl
@@ -12,18 +12,20 @@ void main(void) {
                        dest_task.screen_space_origin +
                        src_task.screen_space_origin;
 
     vec2 local_pos = mix(dest_origin,
                          dest_origin + src_task.size,
                          aPosition.xy);
 
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
-    vec2 st0 = src_task.render_target_origin / texture_size;
-    vec2 st1 = (src_task.render_target_origin + src_task.size) / texture_size;
-    vUv = vec3(mix(st0, st1, aPosition.xy), src_task.render_target_layer_index);
-    vUvBounds = vec4(st0 + 0.5 / texture_size, st1 - 0.5 / texture_size);
+    vec2 st0 = src_task.render_target_origin;
+    vec2 st1 = src_task.render_target_origin + src_task.size;
+
+    vec2 uv = src_task.render_target_origin + aPosition.xy * src_task.size;
+    vUv = vec3(uv / texture_size, src_task.render_target_layer_index);
+    vUvBounds = vec4(st0 + 0.5, st1 - 0.5) / texture_size.xyxy;
 
     vOp = pi.sub_index;
     vAmount = float(pi.user_data.y) / 65535.0;
 
     gl_Position = uTransform * vec4(local_pos, pi.z, 1.0);
 }
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.fs.glsl
+++ /dev/null
@@ -1,434 +0,0 @@
-#line 1
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-void discard_pixels_in_rounded_borders(vec2 local_pos) {
-  float distanceFromRef = distance(vRefPoint, local_pos);
-  if (vRadii.x > 0.0 && (distanceFromRef > vRadii.x || distanceFromRef < vRadii.z)) {
-      discard;
-  }
-}
-
-vec4 get_fragment_color(float distanceFromMixLine, float pixelsPerFragment) {
-  // Here we are mixing between the two border colors. We need to convert
-  // distanceFromMixLine it to pixel space to properly anti-alias and then push
-  // it between the limits accepted by `mix`.
-  float colorMix = min(max(distanceFromMixLine / pixelsPerFragment, -0.5), 0.5) + 0.5;
-  return mix(vHorizontalColor, vVerticalColor, colorMix);
-}
-
-float alpha_for_solid_border(float distance_from_ref,
-                             float inner_radius,
-                             float outer_radius,
-                             float pixels_per_fragment) {
-  // We want to start anti-aliasing one pixel in from the border.
-  float nudge = pixels_per_fragment;
-  inner_radius += nudge;
-  outer_radius -= nudge;
-
-  if (distance_from_ref < outer_radius && distance_from_ref > inner_radius) {
-    return 1.0;
-  }
-
-  float distance_from_border = max(distance_from_ref - outer_radius,
-                                   inner_radius - distance_from_ref);
-
-  // Move the distance back into pixels.
-  distance_from_border /= pixels_per_fragment;
-
-  // Apply a more gradual fade out to transparent.
-  // distance_from_border -= 0.5;
-
-  return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
-}
-
-float alpha_for_solid_ellipse_border(vec2 local_pos,
-                                     vec2 inner_radius,
-                                     vec2 outer_radius,
-                                     float pixels_per_fragment) {
-  vec2 distance_from_ref = local_pos - vRefPoint;
-
-  float nudge = pixels_per_fragment;
-  inner_radius += nudge;
-  outer_radius -= nudge;
-
-  float inner_ellipse = distance_from_ref.x * distance_from_ref.x / inner_radius.x / inner_radius.x +
-                        distance_from_ref.y * distance_from_ref.y / inner_radius.y / inner_radius.y;
-  float outer_ellipse = distance_from_ref.x * distance_from_ref.x / outer_radius.x / outer_radius.x +
-                        distance_from_ref.y * distance_from_ref.y / outer_radius.y / outer_radius.y;
-  if (inner_ellipse > 1.0 && outer_ellipse < 1.0) {
-      return 1.0;
-  }
-
-  vec2 offset = step(inner_radius.yx, inner_radius.xy) *
-                (sqrt(abs(inner_radius.x * inner_radius.x - inner_radius.y * inner_radius.y)));
-  vec2 focus1 = vRefPoint + offset;
-  vec2 focus2 = vRefPoint - offset;
-
-  float inner_distance_from_border = max(inner_radius.x, inner_radius.y) -
-                                     (distance(focus1, local_pos) + distance(focus2, local_pos)) / 2.0;
-
-  offset = step(outer_radius.yx, outer_radius.xy) *
-           (sqrt(abs(outer_radius.x * outer_radius.x - outer_radius.y * outer_radius.y)));
-  focus1 = vRefPoint + offset;
-  focus2 = vRefPoint - offset;
-  float outer_distance_from_border = (distance(focus1, local_pos) + distance(focus2, local_pos)) / 2.0 -
-                                     max(outer_radius.x, outer_radius.y);
-
-  float distance_from_border = max(inner_distance_from_border, outer_distance_from_border);
-
-  // Move the distance back into pixels.
-  distance_from_border /= pixels_per_fragment;
-
-  return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
-}
-
-float alpha_for_solid_border_corner(vec2 local_pos,
-                                    vec2 inner_radius,
-                                    vec2 outer_radius,
-                                    float pixels_per_fragment) {
-  if (inner_radius.x == inner_radius.y && outer_radius.x == outer_radius.y) {
-    float distance_from_ref = distance(vRefPoint, local_pos);
-    return alpha_for_solid_border(distance_from_ref, inner_radius.x, outer_radius.x, pixels_per_fragment);
-  } else {
-    return alpha_for_solid_ellipse_border(local_pos, inner_radius, outer_radius, pixels_per_fragment);
-  }
-}
-
-vec4 draw_dotted_edge(vec2 local_pos, vec4 piece_rect, float pixels_per_fragment) {
-  // We don't use pixels_per_fragment here, since it can change along the edge
-  // of a transformed border edge. We want this calculation to be consistent
-  // across the entire edge so that the positioning of the dots stays the same.
-  float two_pixels = 2.0 * length(fwidth(vLocalPos.xy));
-
-  // Circle diameter is stroke width, minus a couple pixels to account for anti-aliasing.
-  float circle_diameter = max(piece_rect.z - two_pixels, min(piece_rect.z, two_pixels));
-
-  // We want to spread the circles across the edge, but keep one circle diameter at the end
-  // reserved for two half-circles which connect to the corners.
-  float edge_available = piece_rect.w - (circle_diameter * 2.0);
-  float number_of_circles = floor(edge_available / (circle_diameter * 2.0));
-
-  // Here we are initializing the distance from the y coordinate of the center of the circle to
-  // the closest end half-circle.
-  vec2 relative_pos = local_pos - piece_rect.xy;
-  float y_distance = min(relative_pos.y, piece_rect.w - relative_pos.y);
-
-  if (number_of_circles > 0.0) {
-    // Spread the circles throughout the edge, to distribute the extra space evenly. We want
-    // to ensure that we have at last two pixels of space for each circle so that they aren't
-    // touching.
-    float space_for_each_circle = ceil(max(edge_available / number_of_circles, two_pixels));
-
-    float first_half_circle_space = circle_diameter;
-
-    float circle_index = (relative_pos.y - first_half_circle_space) / space_for_each_circle;
-    circle_index = floor(clamp(circle_index, 0.0, number_of_circles - 1.0));
-
-    float circle_y_pos =
-      circle_index * space_for_each_circle + (space_for_each_circle / 2.0) + circle_diameter;
-    y_distance = min(abs(circle_y_pos - relative_pos.y), y_distance);
-  }
-
-  float distance_from_circle_center = length(vec2(relative_pos.x - (piece_rect.z / 2.0), y_distance));
-  float distance_from_circle_edge = distance_from_circle_center - (circle_diameter / 2.0);
-
-  // Don't anti-alias if the circle diameter is small to avoid a blur of color.
-  if (circle_diameter < two_pixels && distance_from_circle_edge > 0.0)
-    return vec4(0.0);
-
-  // Move the distance back into pixels.
-  distance_from_circle_edge /= pixels_per_fragment;
-
-  float alpha = 1.0 - smoothstep(0.0, 1.0, min(1.0, max(0.0, distance_from_circle_edge)));
-  return vHorizontalColor * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_dashed_edge(float position, float border_width, float pixels_per_fragment) {
-  // TODO: Investigate exactly what FF does.
-  float size = border_width * 3.0;
-  float segment = floor(position / size);
-
-  float alpha = alpha_for_solid_border(position,
-                                       segment * size,
-                                       (segment + 1.0) * size,
-                                       pixels_per_fragment);
-
-  if (mod(segment + 2.0, 2.0) == 0.0) {
-    return vHorizontalColor * vec4(1.0, 1.0, 1.0, 1.0 - alpha);
-  } else {
-    return vHorizontalColor * vec4(1.0, 1.0, 1.0, alpha);
-  }
-}
-
-vec4 draw_dashed_or_dotted_border(vec2 local_pos, float distance_from_mix_line) {
-  // This is the conversion factor for transformations and device pixel scaling.
-  float pixels_per_fragment = length(fwidth(local_pos.xy));
-
-  switch (vBorderPart) {
-    // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT:
-    {
-      vec4 color = get_fragment_color(distance_from_mix_line, pixels_per_fragment);
-      if (vRadii.x > 0.0) {
-        color.a *= alpha_for_solid_border_corner(local_pos,
-                                                 vRadii.zw,
-                                                 vRadii.xy,
-                                                 pixels_per_fragment);
-      }
-      return color;
-    }
-    case PST_BOTTOM:
-    case PST_TOP: {
-      return vBorderStyle == BORDER_STYLE_DASHED ?
-        draw_dashed_edge(vLocalPos.x - vPieceRect.x, vPieceRect.w, pixels_per_fragment) :
-        draw_dotted_edge(local_pos.yx, vPieceRect.yxwz, pixels_per_fragment);
-    }
-    case PST_LEFT:
-    case PST_RIGHT:
-    {
-      return vBorderStyle == BORDER_STYLE_DASHED ?
-        draw_dashed_edge(vLocalPos.y - vPieceRect.y, vPieceRect.z, pixels_per_fragment) :
-        draw_dotted_edge(local_pos.xy, vPieceRect.xyzw, pixels_per_fragment);
-    }
-  }
-
-  return vec4(0.0);
-}
-
-vec4 draw_double_edge(float pos,
-                      float len,
-                      float distance_from_mix_line,
-                      float pixels_per_fragment) {
-  float total_border_width = len;
-  float one_third_width = total_border_width / 3.0;
-
-  // Contribution of the outer border segment.
-  float alpha = alpha_for_solid_border(pos,
-                                       total_border_width - one_third_width,
-                                       total_border_width,
-                                       pixels_per_fragment);
-
-  // Contribution of the inner border segment.
-  alpha += alpha_for_solid_border(pos, 0.0, one_third_width, pixels_per_fragment);
-  return get_fragment_color(distance_from_mix_line, pixels_per_fragment) * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_double_edge_vertical(vec2 local_pos,
-                               float distance_from_mix_line,
-                               float pixels_per_fragment) {
-  // Get our position within this specific segment
-  float position = abs(local_pos.x - vRefPoint.x);
-  return draw_double_edge(position, abs(vPieceRect.z), distance_from_mix_line, pixels_per_fragment);
-}
-
-vec4 draw_double_edge_horizontal(vec2 local_pos,
-                                 float distance_from_mix_line,
-                                 float pixels_per_fragment) {
-  // Get our position within this specific segment
-  float position = abs(local_pos.y - vRefPoint.y);
-  return draw_double_edge(position, abs(vPieceRect.w), distance_from_mix_line, pixels_per_fragment);
-}
-
-vec4 draw_double_edge_corner_with_radius(vec2 local_pos,
-                                         float distance_from_mix_line,
-                                         float pixels_per_fragment) {
-  float total_border_width = vRadii.x - vRadii.z;
-  float one_third_width = total_border_width / 3.0;
-  float total_border_height = vRadii.y - vRadii.w;
-  float one_third_height = total_border_height / 3.0;
-
-  // Contribution of the outer border segment.
-  float alpha = alpha_for_solid_border_corner(local_pos,
-                                              vec2(vRadii.x - one_third_width,
-                                                   vRadii.y - one_third_height),
-                                              vec2(vRadii.x, vRadii.y),
-                                              pixels_per_fragment);
-
-  // Contribution of the inner border segment.
-  alpha += alpha_for_solid_border_corner(local_pos,
-                                         vec2(vRadii.z, vRadii.w),
-                                         vec2(vRadii.z + one_third_width, vRadii.w + one_third_height),
-                                         pixels_per_fragment);
-  return get_fragment_color(distance_from_mix_line, pixels_per_fragment) * vec4(1.0, 1.0, 1.0, alpha);
-}
-
-vec4 draw_double_edge_corner(vec2 local_pos,
-                             float distance_from_mix_line,
-                             float pixels_per_fragment) {
-  if (vRadii.x > 0.0) {
-      return draw_double_edge_corner_with_radius(local_pos,
-                                                 distance_from_mix_line,
-                                                 pixels_per_fragment);
-  }
-
-  bool is_vertical = (vBorderPart == PST_TOP_LEFT) ? distance_from_mix_line < 0.0 :
-                                                     distance_from_mix_line >= 0.0;
-  if (is_vertical) {
-    return draw_double_edge_vertical(local_pos, distance_from_mix_line, pixels_per_fragment);
-  } else {
-    return draw_double_edge_horizontal(local_pos, distance_from_mix_line, pixels_per_fragment);
-  }
-}
-
-vec4 draw_double_border(float distance_from_mix_line, vec2 local_pos) {
-  float pixels_per_fragment = length(fwidth(local_pos.xy));
-  switch (vBorderPart) {
-    // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT:
-      return draw_double_edge_corner(local_pos, distance_from_mix_line, pixels_per_fragment);
-    case PST_BOTTOM:
-    case PST_TOP:
-      return draw_double_edge_horizontal(local_pos,
-                                         distance_from_mix_line,
-                                         pixels_per_fragment);
-    case PST_LEFT:
-    case PST_RIGHT:
-      return draw_double_edge_vertical(local_pos,
-                                       distance_from_mix_line,
-                                       pixels_per_fragment);
-  }
-  return vec4(0.0);
-}
-
-vec4 draw_solid_border(float distanceFromMixLine, vec2 localPos) {
-  switch (vBorderPart) {
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT: {
-      // This is the conversion factor for transformations and device pixel scaling.
-      float pixelsPerFragment = length(fwidth(localPos.xy));
-      vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
-
-      if (vRadii.x > 0.0) {
-        color.a *= alpha_for_solid_border_corner(localPos, vRadii.zw, vRadii.xy, pixelsPerFragment);
-      }
-
-      return color;
-    }
-    default:
-      discard_pixels_in_rounded_borders(localPos);
-      return vHorizontalColor;
-  }
-}
-
-vec4 draw_mixed_edge(float distance, float border_len, vec4 color, vec2 brightness_mod) {
-  float modulator = distance / border_len > 0.5 ? brightness_mod.x : brightness_mod.y;
-  return vec4(color.xyz * modulator, color.a);
-}
-
-vec4 draw_mixed_border(float distanceFromMixLine, float distanceFromMiddle, vec2 localPos, vec2 brightness_mod) {
-  switch (vBorderPart) {
-    case PST_TOP_LEFT:
-    case PST_TOP_RIGHT:
-    case PST_BOTTOM_LEFT:
-    case PST_BOTTOM_RIGHT: {
-      // This is the conversion factor for transformations and device pixel scaling.
-      float pixelsPerFragment = length(fwidth(localPos.xy));
-      vec4 color = get_fragment_color(distanceFromMixLine, pixelsPerFragment);
-
-      if (vRadii.x > 0.0) {
-        float distance = distance(vRefPoint, localPos) - vRadii.z;
-        float length = vRadii.x - vRadii.z;
-        if (distanceFromMiddle < 0.0) {
-          distance = length - distance;
-        }
-
-        return 0.0 <= distance && distance <= length ?
-          draw_mixed_edge(distance, length, color, brightness_mod) :
-          vec4(0.0, 0.0, 0.0, 0.0);
-      }
-
-      bool is_vertical = (vBorderPart == PST_TOP_LEFT) ? distanceFromMixLine < 0.0 :
-                                                         distanceFromMixLine >= 0.0;
-      float distance = is_vertical ? abs(localPos.x - vRefPoint.x) : abs(localPos.y - vRefPoint.y);
-      float length = is_vertical ? abs(vPieceRect.z) : abs(vPieceRect.w);
-      if (distanceFromMiddle > 0.0) {
-          distance = length - distance;
-      }
-
-      return 0.0 <= distance && distance <= length ?
-        draw_mixed_edge(distance, length, color, brightness_mod) :
-        vec4(0.0, 0.0, 0.0, 0.0);
-    }
-    case PST_BOTTOM:
-    case PST_TOP:
-      return draw_mixed_edge(localPos.y - vPieceRect.y, vPieceRect.w, vVerticalColor, brightness_mod);
-    case PST_LEFT:
-    case PST_RIGHT:
-      return draw_mixed_edge(localPos.x - vPieceRect.x, vPieceRect.z, vHorizontalColor, brightness_mod);
-  }
-  return vec4(0.0);
-}
-
-vec4 draw_complete_border(vec2 local_pos, float distance_from_mix_line, float distance_from_middle) {
-  vec2 brightness_mod = vec2(0.7, 1.3);
-
-  // Note: we can't pass-through in the following cases,
-  // because Angle doesn't support it and fails to compile the shaders.
-  switch (vBorderStyle) {
-    case BORDER_STYLE_DASHED:
-      return draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
-    case BORDER_STYLE_DOTTED:
-      return draw_dashed_or_dotted_border(local_pos, distance_from_mix_line);
-    case BORDER_STYLE_DOUBLE:
-      return draw_double_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_OUTSET:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_INSET:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_SOLID:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_NONE:
-      return draw_solid_border(distance_from_mix_line, local_pos);
-    case BORDER_STYLE_GROOVE:
-      return draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.yx);
-    case BORDER_STYLE_RIDGE:
-      return draw_mixed_border(distance_from_mix_line, distance_from_middle, local_pos, brightness_mod.xy);
-    case BORDER_STYLE_HIDDEN:
-    default:
-      break;
-  }
-
-  // Note: Workaround for Angle on Windows,
-  // because non-empty case statements must have break or return.
-  discard;
-  return vec4(0.0);
-}
-
-// TODO: Investigate performance of this shader and see
-//       if it's worthwhile splitting it / removing branches etc.
-void main(void) {
-#ifdef WR_FEATURE_TRANSFORM
-    float alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
-#else
-    float alpha = 1.0;
-    vec2 local_pos = vLocalPos;
-#endif
-
-#ifdef WR_FEATURE_TRANSFORM
-    // TODO(gw): Support other border styles for transformed elements.
-    float distance_from_mix_line = (local_pos.x - vPieceRect.x) * vPieceRect.w -
-                                   (local_pos.y - vPieceRect.y) * vPieceRect.z;
-    distance_from_mix_line /= vPieceRectHypotenuseLength;
-    float distance_from_middle = (local_pos.x - vBorderRect.x) +
-                                 (local_pos.y - vBorderRect.y) -
-                                 0.5 * (vBorderRect.z + vBorderRect.w);
-#else
-    float distance_from_mix_line = vDistanceFromMixLine;
-    float distance_from_middle = vDistanceFromMiddle;
-#endif
-
-    oFragColor = draw_complete_border(local_pos, distance_from_mix_line, distance_from_middle);
-    oFragColor.a *= min(alpha, do_clip());
-}
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.glsl
+++ /dev/null
@@ -1,33 +0,0 @@
-#line 1
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// These are not changing.
-flat varying vec4 vVerticalColor;      // The vertical color, e.g. top/bottom
-flat varying vec4 vHorizontalColor;    // The horizontal color e.g. left/right
-flat varying vec4 vRadii;              // The border radius from CSS border-radius
-flat varying vec4 vBorderRect;         // The rect of the border in local space.
-
-// for corners, this is the beginning of the corner.
-// For the lines, this is the top left of the line.
-flat varying vec2 vRefPoint;
-flat varying int vBorderStyle;
-flat varying int vBorderPart; // Which part of the border we're drawing.
-
-flat varying vec4 vPieceRect;
-
-// These are in device space
-#ifdef WR_FEATURE_TRANSFORM
-varying vec3 vLocalPos;     // The clamped position in local space.
-flat varying float vPieceRectHypotenuseLength;
-#else
-varying vec2 vLocalPos;     // The clamped position in local space.
-
-// These two are interpolated
-varying float vDistanceFromMixLine;  // This is the distance from the line where two colors
-                                     // meet in border corners.
-varying float vDistanceFromMiddle;   // This is the distance from the line between the top
-                                     // left corner and the bottom right.
-#endif
deleted file mode 100644
--- a/gfx/webrender/res/ps_border.vs.glsl
+++ /dev/null
@@ -1,190 +0,0 @@
-#line 1
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-void main(void) {
-    Primitive prim = load_primitive();
-    Border border = fetch_border(prim.prim_index);
-    int sub_part = prim.sub_index;
-    vBorderRect = vec4(prim.local_rect.p0, prim.local_rect.size);
-
-    vec2 tl_outer = vBorderRect.xy;
-    vec2 tl_inner = tl_outer + vec2(max(border.radii[0].x, border.widths.x),
-                                    max(border.radii[0].y, border.widths.y));
-
-    vec2 tr_outer = vec2(vBorderRect.x + vBorderRect.z,
-                         vBorderRect.y);
-    vec2 tr_inner = tr_outer + vec2(-max(border.radii[0].z, border.widths.z),
-                                    max(border.radii[0].w, border.widths.y));
-
-    vec2 br_outer = vec2(vBorderRect.x + vBorderRect.z,
-                         vBorderRect.y + vBorderRect.w);
-    vec2 br_inner = br_outer - vec2(max(border.radii[1].x, border.widths.z),
-                                    max(border.radii[1].y, border.widths.w));
-
-    vec2 bl_outer = vec2(vBorderRect.x,
-                         vBorderRect.y + vBorderRect.w);
-    vec2 bl_inner = bl_outer + vec2(max(border.radii[1].z, border.widths.x),
-                                    -max(border.radii[1].w, border.widths.w));
-
-    RectWithSize segment_rect;
-    switch (sub_part) {
-        case PST_TOP_LEFT:
-            segment_rect.p0 = tl_outer;
-            segment_rect.size = tl_inner - tl_outer;
-            vBorderStyle = int(border.style.x);
-            vHorizontalColor = border.colors[BORDER_LEFT];
-            vVerticalColor = border.colors[BORDER_TOP];
-            vRadii = vec4(border.radii[0].xy,
-                          border.radii[0].xy - border.widths.xy);
-            break;
-        case PST_TOP_RIGHT:
-            segment_rect.p0 = vec2(tr_inner.x, tr_outer.y);
-            segment_rect.size = vec2(tr_outer.x - tr_inner.x, tr_inner.y - tr_outer.y);
-            vBorderStyle = int(border.style.y);
-            vHorizontalColor = border.colors[BORDER_TOP];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(border.radii[0].zw,
-                          border.radii[0].zw - border.widths.zy);
-            break;
-        case PST_BOTTOM_RIGHT:
-            segment_rect.p0 = br_inner;
-            segment_rect.size = br_outer - br_inner;
-            vBorderStyle = int(border.style.z);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(border.radii[1].xy,
-                          border.radii[1].xy - border.widths.zw);
-            break;
-        case PST_BOTTOM_LEFT:
-            segment_rect.p0 = vec2(bl_outer.x, bl_inner.y);
-            segment_rect.size = vec2(bl_inner.x - bl_outer.x, bl_outer.y - bl_inner.y);
-            vBorderStyle = int(border.style.w);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_LEFT];
-            vRadii = vec4(border.radii[1].zw,
-                          border.radii[1].zw - border.widths.xw);
-            break;
-        case PST_LEFT:
-            segment_rect.p0 = vec2(tl_outer.x, tl_inner.y);
-            segment_rect.size = vec2(border.widths.x, bl_inner.y - tl_inner.y);
-            vBorderStyle = int(border.style.x);
-            vHorizontalColor = border.colors[BORDER_LEFT];
-            vVerticalColor = border.colors[BORDER_LEFT];
-            vRadii = vec4(0.0);
-            break;
-        case PST_RIGHT:
-            segment_rect.p0 = vec2(tr_outer.x - border.widths.z, tr_inner.y);
-            segment_rect.size = vec2(border.widths.z, br_inner.y - tr_inner.y);
-            vBorderStyle = int(border.style.z);
-            vHorizontalColor = border.colors[BORDER_RIGHT];
-            vVerticalColor = border.colors[BORDER_RIGHT];
-            vRadii = vec4(0.0);
-            break;
-        case PST_BOTTOM:
-            segment_rect.p0 = vec2(bl_inner.x, bl_outer.y - border.widths.w);
-            segment_rect.size = vec2(br_inner.x - bl_inner.x, border.widths.w);
-            vBorderStyle = int(border.style.w);
-            vHorizontalColor = border.colors[BORDER_BOTTOM];
-            vVerticalColor = border.colors[BORDER_BOTTOM];
-            vRadii = vec4(0.0);
-            break;
-        case PST_TOP:
-            segment_rect.p0 = vec2(tl_inner.x, tl_outer.y);
-            segment_rect.size = vec2(tr_inner.x - tl_inner.x, border.widths.y);
-            vBorderStyle = int(border.style.y);
-            vHorizontalColor = border.colors[BORDER_TOP];
-            vVerticalColor = border.colors[BORDER_TOP];
-            vRadii = vec4(0.0);
-            break;
-    }
-
-#ifdef WR_FEATURE_TRANSFORM
-    TransformVertexInfo vi = write_transform_vertex(segment_rect,
-                                                    prim.local_clip_rect,
-                                                    prim.z,
-                                                    prim.layer,
-                                                    prim.task,
-                                                    prim.local_rect.p0);
-#else
-    VertexInfo vi = write_vertex(segment_rect,
-                                 prim.local_clip_rect,
-                                 prim.z,
-                                 prim.layer,
-                                 prim.task,
-                                 prim.local_rect.p0);
-#endif
-
-    vLocalPos = vi.local_pos;
-    write_clip(vi.screen_pos, prim.clip_area);
-
-    float x0, y0, x1, y1;
-    switch (sub_part) {
-        // These are the layer tile part PrimitivePart as uploaded by the tiling.rs
-        case PST_TOP_LEFT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            // These are width / heights
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-
-            // The radius here is the border-radius. This is 0, so vRefPoint will
-            // just be the top left (x,y) corner.
-            vRefPoint = vec2(x0, y0) + vRadii.xy;
-            break;
-        case PST_TOP_RIGHT:
-            x0 = segment_rect.p0.x + segment_rect.size.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            vRefPoint = vec2(x0, y0) + vec2(-vRadii.x, vRadii.y);
-            break;
-        case PST_BOTTOM_LEFT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y + segment_rect.size.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y;
-            vRefPoint = vec2(x0, y0) + vec2(vRadii.x, -vRadii.y);
-            break;
-        case PST_BOTTOM_RIGHT:
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            vRefPoint = vec2(x1, y1) + vec2(-vRadii.x, -vRadii.y);
-            break;
-        case PST_TOP:
-        case PST_LEFT:
-        case PST_BOTTOM:
-        case PST_RIGHT:
-            vRefPoint = segment_rect.p0.xy;
-            x0 = segment_rect.p0.x;
-            y0 = segment_rect.p0.y;
-            x1 = segment_rect.p0.x + segment_rect.size.x;
-            y1 = segment_rect.p0.y + segment_rect.size.y;
-            break;
-    }
-
-    // y1 - y0 is the height of the corner / line
-    // x1 - x0 is the width of the corner / line.
-    float width = x1 - x0;
-    float height = y1 - y0;
-
-    vBorderPart = sub_part;
-    vPieceRect = vec4(x0, y0, width, height);
-
-    // The fragment shader needs to calculate the distance from the bisecting line
-    // to properly mix border colors. For transformed borders, we calculate this distance
-    // in the fragment shader itself. For non-transformed borders, we can use the
-    // interpolator.
-#ifdef WR_FEATURE_TRANSFORM
-    vPieceRectHypotenuseLength = sqrt(pow(width, 2.0) + pow(height, 2.0));
-#else
-    vDistanceFromMixLine = (vi.local_pos.x - x0) * height -
-                           (vi.local_pos.y - y0) * width;
-    vDistanceFromMiddle = (vi.local_pos.x - vBorderRect.x) +
-                          (vi.local_pos.y - vBorderRect.y) -
-                          0.5 * (vBorderRect.z + vBorderRect.w);
-#endif
-}
--- a/gfx/webrender/res/ps_border_corner.fs.glsl
+++ b/gfx/webrender/res/ps_border_corner.fs.glsl
@@ -1,75 +1,13 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-//
-// Signed distance to an ellipse.
-// Taken from http://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
-// Note that this fails for exact circles.
-//
-float sdEllipse( vec2 p, in vec2 ab ) {
-    p = abs( p ); if( p.x > p.y ){ p=p.yx; ab=ab.yx; }
-    float l = ab.y*ab.y - ab.x*ab.x;
-
-    float m = ab.x*p.x/l;
-    float n = ab.y*p.y/l;
-    float m2 = m*m;
-    float n2 = n*n;
-
-    float c = (m2 + n2 - 1.0)/3.0;
-    float c3 = c*c*c;
-
-    float q = c3 + m2*n2*2.0;
-    float d = c3 + m2*n2;
-    float g = m + m*n2;
-
-    float co;
-
-    if( d<0.0 )
-    {
-        float p = acos(q/c3)/3.0;
-        float s = cos(p);
-        float t = sin(p)*sqrt(3.0);
-        float rx = sqrt( -c*(s + t + 2.0) + m2 );
-        float ry = sqrt( -c*(s - t + 2.0) + m2 );
-        co = ( ry + sign(l)*rx + abs(g)/(rx*ry) - m)/2.0;
-    }
-    else
-    {
-        float h = 2.0*m*n*sqrt( d );
-        float s = sign(q+h)*pow( abs(q+h), 1.0/3.0 );
-        float u = sign(q-h)*pow( abs(q-h), 1.0/3.0 );
-        float rx = -s - u - c*4.0 + 2.0*m2;
-        float ry = (s - u)*sqrt(3.0);
-        float rm = sqrt( rx*rx + ry*ry );
-        float p = ry/sqrt(rm-rx);
-        co = (p + 2.0*g/rm - m)/2.0;
-    }
-
-    float si = sqrt( 1.0 - co*co );
-
-    vec2 r = vec2( ab.x*co, ab.y*si );
-
-    return length(r - p ) * sign(p.y-r.y);
-}
-
-float distance_to_ellipse(vec2 p, vec2 radii) {
-    // sdEllipse fails on exact circles, so handle equal
-    // radii here. The branch coherency should make this
-    // a performance win for the circle case too.
-    if (radii.x == radii.y) {
-        return length(p) - radii.x;
-    } else {
-        return sdEllipse(p, radii);
-    }
-}
-
 void main(void) {
     float alpha = 1.0;
 #ifdef WR_FEATURE_TRANSFORM
     alpha = 0.0;
     vec2 local_pos = init_transform_fs(vLocalPos, alpha);
 #else
     vec2 local_pos = vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_corner.vs.glsl
+++ b/gfx/webrender/res/ps_border_corner.vs.glsl
@@ -1,29 +1,34 @@
 #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/. */
 
+// Matches BorderCornerSide enum in border.rs
+#define SIDE_BOTH       0
+#define SIDE_FIRST      1
+#define SIDE_SECOND     2
+
 vec2 get_radii(vec2 radius, vec2 invalid) {
     if (all(greaterThan(radius, vec2(0.0)))) {
         return radius;
     }
 
     return invalid;
 }
 
-void set_radii(float style,
+void set_radii(int style,
                vec2 radii,
                vec2 widths,
                vec2 adjusted_widths) {
     vRadii0.xy = get_radii(radii, 2.0 * widths);
     vRadii0.zw = get_radii(radii - widths, -widths);
 
-    switch (int(style)) {
+    switch (style) {
         case BORDER_STYLE_RIDGE:
         case BORDER_STYLE_GROOVE:
             vRadii1.xy = radii - adjusted_widths;
             vRadii1.zw = -widths;
             break;
         case BORDER_STYLE_DOUBLE:
             vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
             vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
@@ -37,17 +42,17 @@ void set_radii(float style,
 
 void set_edge_line(vec2 border_width,
                    vec2 outer_corner,
                    vec2 gradient_sign) {
     vec2 gradient = border_width * gradient_sign;
     vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
 }
 
-void write_color(vec4 color0, vec4 color1, int style, vec2 delta) {
+void write_color(vec4 color0, vec4 color1, int style, vec2 delta, int instance_kind) {
     vec4 modulate;
 
     switch (style) {
         case BORDER_STYLE_GROOVE:
             modulate = vec4(1.0 - 0.3 * delta.x,
                             1.0 + 0.3 * delta.x,
                             1.0 - 0.3 * delta.y,
                             1.0 + 0.3 * delta.y);
@@ -59,150 +64,201 @@ void write_color(vec4 color0, vec4 color
                             1.0 + 0.3 * delta.y,
                             1.0 - 0.3 * delta.y);
             break;
         default:
             modulate = vec4(1.0);
             break;
     }
 
+    // Optionally mask out one side of the border corner,
+    // depending on the instance kind.
+    switch (instance_kind) {
+        case SIDE_FIRST:
+            color0.a = 0.0;
+            break;
+        case SIDE_SECOND:
+            color1.a = 0.0;
+            break;
+    }
+
     vColor00 = vec4(color0.rgb * modulate.x, color0.a);
     vColor01 = vec4(color0.rgb * modulate.y, color0.a);
     vColor10 = vec4(color1.rgb * modulate.z, color1.a);
     vColor11 = vec4(color1.rgb * modulate.w, color1.a);
 }
 
+int select_style(int color_select, vec2 fstyle) {
+    ivec2 style = ivec2(fstyle);
+
+    switch (color_select) {
+        case SIDE_BOTH:
+            // TODO(gw): A temporary hack! While we don't support
+            //           border corners that have dots or dashes
+            //           with another style, pretend they are solid
+            //           border corners.
+            bool has_dots = style.x == BORDER_STYLE_DOTTED ||
+                            style.y == BORDER_STYLE_DOTTED;
+            bool has_dashes = style.x == BORDER_STYLE_DASHED ||
+                              style.y == BORDER_STYLE_DASHED;
+            if (style.x != style.y && (has_dots || has_dashes))
+                return BORDER_STYLE_SOLID;
+            return style.x;
+        case SIDE_FIRST:
+            return style.x;
+        case SIDE_SECOND:
+            return style.y;
+    }
+}
+
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
 
-    vec4 adjusted_widths = get_effective_border_widths(border);
-    vec4 inv_adjusted_widths = border.widths - adjusted_widths;
     vec2 p0, p1;
 
     // TODO(gw): We'll need to pass through multiple styles
     //           once we support style transitions per corner.
     int style;
     vec4 edge_distances;
     vec4 color0, color1;
     vec2 color_delta;
 
+    // TODO(gw): Now that all border styles are supported, the switch
+    //           statement below can be tidied up quite a bit.
+
     switch (sub_part) {
         case 0: {
             p0 = corners.tl_outer;
             p1 = corners.tl_inner;
             color0 = border.colors[0];
             color1 = border.colors[1];
             vClipCenter = corners.tl_outer + border.radii[0].xy;
             vClipSign = vec2(1.0);
-            set_radii(border.style.x,
+            style = select_style(prim.user_data.x, border.style.yx);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[0].xy,
                       border.widths.xy,
                       adjusted_widths.xy);
             set_edge_line(border.widths.xy,
                           corners.tl_outer,
                           vec2(1.0, 1.0));
-            style = int(border.style.x);
             edge_distances = vec4(p0 + adjusted_widths.xy,
                                   p0 + inv_adjusted_widths.xy);
             color_delta = vec2(1.0);
             break;
         }
         case 1: {
             p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
             p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
             color0 = border.colors[1];
             color1 = border.colors[2];
             vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
             vClipSign = vec2(-1.0, 1.0);
-            set_radii(border.style.y,
+            style = select_style(prim.user_data.x, border.style.zy);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[0].zw,
                       border.widths.zy,
                       adjusted_widths.zy);
             set_edge_line(border.widths.zy,
                           corners.tr_outer,
                           vec2(-1.0, 1.0));
-            style = int(border.style.y);
             edge_distances = vec4(p1.x - adjusted_widths.z,
                                   p0.y + adjusted_widths.y,
                                   p1.x - border.widths.z + adjusted_widths.z,
                                   p0.y + inv_adjusted_widths.y);
             color_delta = vec2(1.0, -1.0);
             break;
         }
         case 2: {
             p0 = corners.br_inner;
             p1 = corners.br_outer;
             color0 = border.colors[2];
             color1 = border.colors[3];
             vClipCenter = corners.br_outer - border.radii[1].xy;
             vClipSign = vec2(-1.0, -1.0);
-            set_radii(border.style.z,
+            style = select_style(prim.user_data.x, border.style.wz);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[1].xy,
                       border.widths.zw,
                       adjusted_widths.zw);
             set_edge_line(border.widths.zw,
                           corners.br_outer,
                           vec2(-1.0, -1.0));
-            style = int(border.style.z);
             edge_distances = vec4(p1.x - adjusted_widths.z,
                                   p1.y - adjusted_widths.w,
                                   p1.x - border.widths.z + adjusted_widths.z,
                                   p1.y - border.widths.w + adjusted_widths.w);
             color_delta = vec2(-1.0);
             break;
         }
         case 3: {
             p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
             p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
             color0 = border.colors[3];
             color1 = border.colors[0];
             vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
             vClipSign = vec2(1.0, -1.0);
-            set_radii(border.style.w,
+            style = select_style(prim.user_data.x, border.style.xw);
+            vec4 adjusted_widths = get_effective_border_widths(border, style);
+            vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+            set_radii(style,
                       border.radii[1].zw,
                       border.widths.xw,
                       adjusted_widths.xw);
             set_edge_line(border.widths.xw,
                           corners.bl_outer,
                           vec2(1.0, -1.0));
-            style = int(border.style.w);
             edge_distances = vec4(p0.x + adjusted_widths.x,
                                   p1.y - adjusted_widths.w,
                                   p0.x + inv_adjusted_widths.x,
                                   p1.y - border.widths.w + adjusted_widths.w);
             color_delta = vec2(-1.0, 1.0);
             break;
         }
     }
 
-    switch (int(style)) {
+    switch (style) {
         case BORDER_STYLE_DOUBLE: {
             vEdgeDistance = edge_distances;
             vAlphaSelect = 0.0;
             vSDFSelect = 0.0;
             break;
         }
         case BORDER_STYLE_GROOVE:
         case BORDER_STYLE_RIDGE:
             vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
             vAlphaSelect = 1.0;
             vSDFSelect = 1.0;
             break;
+        case BORDER_STYLE_DOTTED:
+            // Disable normal clip radii for dotted corners, since
+            // all the clipping is handled by the clip mask.
+            vClipSign = vec2(0.0);
+            vEdgeDistance = vec4(0.0);
+            vAlphaSelect = 1.0;
+            vSDFSelect = 0.0;
+            break;
         default: {
             vEdgeDistance = vec4(0.0);
             vAlphaSelect = 1.0;
             vSDFSelect = 0.0;
             break;
         }
     }
 
-    write_color(color0, color1, style, color_delta);
+    write_color(color0, color1, style, color_delta, prim.user_data.x);
 
     RectWithSize segment_rect;
     segment_rect.p0 = p0;
     segment_rect.size = p1 - p0;
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
--- a/gfx/webrender/res/ps_border_edge.fs.glsl
+++ b/gfx/webrender/res/ps_border_edge.fs.glsl
@@ -12,16 +12,17 @@ void main(void) {
 #else
     vec2 local_pos = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // Find the appropriate distance to apply the step over.
     vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
 
     // Applies the math necessary to draw a style: double
     // border. In the case of a solid border, the vertex
     // shader sets interpolator values that make this have
     // no effect.
 
     // Select the x/y coord, depending on which axis this edge is.
     vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
@@ -37,13 +38,28 @@ void main(void) {
     // No AA here, since we know we're on a straight edge
     // and the width is rounded to a whole CSS pixel.
     alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
 
     // Mix color based on first distance.
     // TODO(gw): Support AA for groove/ridge border edge with transforms.
     vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
 
-    // Apply dashing parameters.
-    alpha = min(alpha, step(mod(pos.y - vDashParams.x, vDashParams.y), vDashParams.z));
+    // Apply dashing / dotting parameters.
+
+    // Get the main-axis position relative to closest dot or dash.
+    float x = mod(pos.y - vClipParams.x, vClipParams.y);
+
+    // Calculate dash alpha (on/off) based on dash length
+    float dash_alpha = step(x, vClipParams.z);
+
+    // Get the dot alpha
+    vec2 dot_relative_pos = vec2(x, pos.x) - vClipParams.zw;
+    float dot_distance = length(dot_relative_pos) - vClipParams.z;
+    float dot_alpha = 1.0 - smoothstep(-0.5 * afwidth,
+                                        0.5 * afwidth,
+                                        dot_distance);
+
+    // Select between dot/dash alpha based on clip mode.
+    alpha = min(alpha, mix(dash_alpha, dot_alpha, vClipSelect));
 
     oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
 }
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -2,15 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 flat varying vec4 vColor0;
 flat varying vec4 vColor1;
 flat varying vec2 vEdgeDistance;
 flat varying float vAxisSelect;
 flat varying float vAlphaSelect;
-flat varying vec3 vDashParams;
+flat varying vec4 vClipParams;
+flat varying float vClipSelect;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -51,84 +51,134 @@ void write_color(vec4 color, float style
             modulate = vec2(1.0);
             break;
     }
 
     vColor0 = vec4(color.rgb * modulate.x, color.a);
     vColor1 = vec4(color.rgb * modulate.y, color.a);
 }
 
-void write_dash_params(float style,
+void write_clip_params(float style,
                        float border_width,
                        float edge_length,
-                       float edge_offset) {
+                       float edge_offset,
+                       float center_line) {
     // x = offset
     // y = dash on + off length
     // z = dash length
+    // w = center line of edge cross-axis (for dots only)
     switch (int(style)) {
         case BORDER_STYLE_DASHED: {
             float desired_dash_length = border_width * 3.0;
             // Consider half total length since there is an equal on/off for each dash.
             float dash_count = ceil(0.5 * edge_length / desired_dash_length);
             float dash_length = 0.5 * edge_length / dash_count;
-            vDashParams = vec3(edge_offset - 0.5 * dash_length,
+            vClipParams = vec4(edge_offset - 0.5 * dash_length,
                                2.0 * dash_length,
-                               dash_length);
+                               dash_length,
+                               0.0);
+            vClipSelect = 0.0;
+            break;
+        }
+        case BORDER_STYLE_DOTTED: {
+            float diameter = border_width;
+            float radius = 0.5 * diameter;
+            float dot_count = ceil(0.5 * edge_length / diameter);
+            float empty_space = edge_length - dot_count * diameter;
+            float distance_between_centers = diameter + empty_space / dot_count;
+            vClipParams = vec4(edge_offset - radius,
+                               distance_between_centers,
+                               radius,
+                               center_line);
+            vClipSelect = 1.0;
             break;
         }
         default:
-            vDashParams = vec3(1.0);
+            vClipParams = vec4(1.0);
+            vClipSelect = 0.0;
             break;
     }
 }
 
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
-    vec4 adjusted_widths = get_effective_border_widths(border);
     vec4 color = border.colors[sub_part];
 
+    // TODO(gw): Now that all border styles are supported, the switch
+    //           statement below can be tidied up quite a bit.
+
+    float style;
+    bool color_flip;
+
     RectWithSize segment_rect;
     switch (sub_part) {
-        case 0:
+        case 0: {
             segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
             segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.x));
             write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
-            write_alpha_select(border.style.x);
-            write_color(color, border.style.x, false);
-            write_dash_params(border.style.x, border.widths.x, segment_rect.size.y, segment_rect.p0.y);
+            style = border.style.x;
+            color_flip = false;
+            write_clip_params(border.style.x,
+                              border.widths.x,
+                              segment_rect.size.y,
+                              segment_rect.p0.y,
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
             break;
-        case 1:
+        }
+        case 1: {
             segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
             segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.y));
             write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
-            write_alpha_select(border.style.y);
-            write_color(color, border.style.y, false);
-            write_dash_params(border.style.y, border.widths.y, segment_rect.size.x, segment_rect.p0.x);
+            style = border.style.y;
+            color_flip = false;
+            write_clip_params(border.style.y,
+                              border.widths.y,
+                              segment_rect.size.x,
+                              segment_rect.p0.x,
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
             break;
-        case 2:
+        }
+        case 2: {
             segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
             segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.z));
             write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
-            write_alpha_select(border.style.z);
-            write_color(color, border.style.z, true);
-            write_dash_params(border.style.z, border.widths.z, segment_rect.size.y, segment_rect.p0.y);
+            style = border.style.z;
+            color_flip = true;
+            write_clip_params(border.style.z,
+                              border.widths.z,
+                              segment_rect.size.y,
+                              segment_rect.p0.y,
+                              segment_rect.p0.x + 0.5 * segment_rect.size.x);
             break;
-        case 3:
+        }
+        case 3: {
             segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
             segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
+            vec4 adjusted_widths = get_effective_border_widths(border, int(border.style.w));
             write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
-            write_alpha_select(border.style.w);
-            write_color(color, border.style.w, true);
-            write_dash_params(border.style.w, border.widths.w, segment_rect.size.x, segment_rect.p0.x);
+            style = border.style.w;
+            color_flip = true;
+            write_clip_params(border.style.w,
+                              border.widths.w,
+                              segment_rect.size.x,
+                              segment_rect.p0.x,
+                              segment_rect.p0.y + 0.5 * segment_rect.size.y);
             break;
+        }
     }
 
+    write_alpha_select(style);
+    write_color(color, style, color_flip);
+
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
 #else
--- a/gfx/webrender/res/ps_composite.vs.glsl
+++ b/gfx/webrender/res/ps_composite.vs.glsl
@@ -1,17 +1,17 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     PrimitiveInstance pi = fetch_prim_instance();
     AlphaBatchTask dest_task = fetch_alpha_batch_task(pi.render_task_index);
-    AlphaBatchTask backdrop_task = fetch_alpha_batch_task(pi.user_data.x);
+    ReadbackTask backdrop_task = fetch_readback_task(pi.user_data.x);
     AlphaBatchTask src_task = fetch_alpha_batch_task(pi.user_data.y);
 
     vec2 dest_origin = dest_task.render_target_origin -
                        dest_task.screen_space_origin +
                        src_task.screen_space_origin;
 
     vec2 local_pos = mix(dest_origin,
                          dest_origin + src_task.size,
--- a/gfx/webrender/res/ps_split_composite.fs.glsl
+++ b/gfx/webrender/res/ps_split_composite.fs.glsl
@@ -1,9 +1,15 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
-    vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
-    oFragColor = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
+    bvec4 inside = lessThanEqual(vec4(vUvTaskBounds.xy, vUv.xy),
+                                 vec4(vUv.xy, vUvTaskBounds.zw));
+    if (all(inside)) {
+        vec2 uv = clamp(vUv.xy, vUvSampleBounds.xy, vUvSampleBounds.zw);
+        oFragColor = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
+    } else {
+        oFragColor = vec4(0.0);
+    }
 }
--- a/gfx/webrender/res/ps_split_composite.glsl
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -1,7 +1,8 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec3 vUv;
-flat varying vec4 vUvBounds;
+flat varying vec4 vUvTaskBounds;
+flat varying vec4 vUvSampleBounds;
--- a/gfx/webrender/res/ps_split_composite.vs.glsl
+++ b/gfx/webrender/res/ps_split_composite.vs.glsl
@@ -35,14 +35,15 @@ void main(void) {
 
     vec3 world_pos = bilerp(geometry.points[0], geometry.points[1],
                             geometry.points[3], geometry.points[2],
                             aPosition.y, aPosition.x);
     vec4 final_pos = vec4(world_pos.xy * uDevicePixelRatio, pi.z, 1.0);
 
     gl_Position = uTransform * final_pos;
 
-    vec2 uv_origin = src_task.render_target_origin - src_task.screen_space_origin;
-    vec2 uv_pos = uv_origin + world_pos.xy;
+    vec2 uv_origin = src_task.render_target_origin;
+    vec2 uv_pos = uv_origin + world_pos.xy - src_task.screen_space_origin;
     vec2 texture_size = vec2(textureSize(sCacheRGBA8, 0));
     vUv = vec3(uv_pos / texture_size, src_task.render_target_layer_index);
-    vUvBounds = vec4((uv_origin + 0.5) / texture_size, (uv_origin + src_task.size - 0.5) / texture_size);
+    vUvTaskBounds = vec4(uv_origin, uv_origin + src_task.size) / texture_size.xyxy;
+    vUvSampleBounds = vec4(uv_origin + 0.5, uv_origin + src_task.size - 0.5) / texture_size.xyxy;
 }
--- a/gfx/webrender/res/ps_yuv_image.fs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.fs.glsl
@@ -10,32 +10,36 @@
 // The constants added to the Y, U and V components are applied in the fragment shader.
 #if defined(WR_FEATURE_YUV_REC601)
 // From Rec601:
 // [R]   [1.1643835616438356,  0.0,                 1.5960267857142858   ]   [Y -  16]
 // [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708   ] x [U - 128]
 // [B]   [1.1643835616438356,  2.017232142857143,   8.862867620416422e-17]   [V - 128]
 //
 // For the range [0,1] instead of [0,255].
+//
+// The matrix is stored in column-major.
 const mat3 YuvColorMatrix = mat3(
-    1.16438,  0.0,      1.59603,
-    1.16438, -0.39176, -0.81297,
-    1.16438,  2.01723,  0.0
+    1.16438,  1.16438, 1.16438,
+    0.0,     -0.39176, 2.01723,
+    1.59603, -0.81297, 0.0
 );
 #elif defined(WR_FEATURE_YUV_REC709)
 // From Rec709:
 // [R]   [1.1643835616438356,  4.2781193979771426e-17, 1.7927410714285714]   [Y -  16]
 // [G] = [1.1643835616438358, -0.21324861427372963,   -0.532909328559444 ] x [U - 128]
 // [B]   [1.1643835616438356,  2.1124017857142854,     0.0               ]   [V - 128]
 //
 // For the range [0,1] instead of [0,255]:
+//
+// The matrix is stored in column-major.
 const mat3 YuvColorMatrix = mat3(
-    1.16438,  0.0,      1.79274,
-    1.16438, -0.21325, -0.53291,
-    1.16438,  2.11240,  0.0
+    1.16438,  1.16438,  1.16438,
+    0.0    , -0.21325,  2.11240,
+    1.79274, -0.53291,  0.0
 );
 #endif
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
     vec2 pos = init_transform_fs(vLocalPos, alpha);
 
@@ -49,26 +53,33 @@ void main(void) {
 
     alpha = min(alpha, do_clip());
 
     // 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_y = vTextureOffsetY + clamp(
         relative_pos_in_rect / vStretchSize * vTextureSizeY,
         vHalfTexelY, vTextureSizeY - vHalfTexelY);
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     vec2 uv_offset = clamp(
         relative_pos_in_rect / vStretchSize * vTextureSizeUv,
         vHalfTexelUv, vTextureSizeUv - vHalfTexelUv);
     // NV12 only uses 2 textures. The sColor0 is for y and sColor1 is for uv.
     // The texture coordinates of u and v are the same. So, we could skip the
     // st_v if the format is NV12.
     vec2 st_u = vTextureOffsetU + uv_offset;
+#endif
 
     vec3 yuv_value;
-#ifdef WR_FEATURE_NV12
+#ifdef WR_FEATURE_INTERLEAVED_Y_CB_CR
+    // "The Y, Cb and Cr color channels within the 422 data are mapped into
+    // the existing green, blue and red color channels."
+    // https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_rgb_422.txt
+    yuv_value = TEX_SAMPLE(sColor0, st_y).gbr;
+#elif defined(WR_FEATURE_NV12)
     yuv_value.x = TEX_SAMPLE(sColor0, st_y).r;
     yuv_value.yz = TEX_SAMPLE(sColor1, st_u).rg;
 #else
     // The yuv_planar format should have this third texture coordinate.
     vec2 st_v = vTextureOffsetV + uv_offset;
 
     yuv_value.x = TEX_SAMPLE(sColor0, st_y).r;
     yuv_value.y = TEX_SAMPLE(sColor1, st_u).r;
--- a/gfx/webrender/res/ps_yuv_image.vs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.vs.glsl
@@ -21,34 +21,37 @@ void main(void) {
                                  prim.task,
                                  prim.local_rect.p0);
     vLocalPos = vi.local_pos - prim.local_rect.p0;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 
     ResourceRect y_rect = fetch_resource_rect(prim.user_data.x);
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR  // only 1 channel
     ResourceRect u_rect = fetch_resource_rect(prim.user_data.x + 1);
-#ifndef WR_FEATURE_NV12
+#ifndef WR_FEATURE_NV12 // 2 channel
     ResourceRect v_rect = fetch_resource_rect(prim.user_data.x + 2);
 #endif
+#endif
 
     // If this is in WR_FEATURE_TEXTURE_RECT mode, the rect and size use
     // non-normalized texture coordinates.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 y_texture_size_normalization_factor = vec2(1, 1);
 #else
     vec2 y_texture_size_normalization_factor = vec2(textureSize(sColor0, 0));
 #endif
     vec2 y_st0 = y_rect.uv_rect.xy / y_texture_size_normalization_factor;
     vec2 y_st1 = y_rect.uv_rect.zw / y_texture_size_normalization_factor;
 
     vTextureSizeY = y_st1 - y_st0;
     vTextureOffsetY = y_st0;
 
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     // This assumes the U and V surfaces have the same size.
 #ifdef WR_FEATURE_TEXTURE_RECT
     vec2 uv_texture_size_normalization_factor = vec2(1, 1);
 #else
     vec2 uv_texture_size_normalization_factor = vec2(textureSize(sColor1, 0));
 #endif
     vec2 u_st0 = u_rect.uv_rect.xy / uv_texture_size_normalization_factor;
     vec2 u_st1 = u_rect.uv_rect.zw / uv_texture_size_normalization_factor;
@@ -57,15 +60,18 @@ void main(void) {
     vec2 v_st0 = v_rect.uv_rect.xy / uv_texture_size_normalization_factor;
 #endif
 
     vTextureSizeUv = u_st1 - u_st0;
     vTextureOffsetU = u_st0;
 #ifndef WR_FEATURE_NV12
     vTextureOffsetV = v_st0;
 #endif
+#endif
 
     YuvImage image = fetch_yuv_image(prim.prim_index);
     vStretchSize = image.size;
 
     vHalfTexelY = vec2(0.5) / y_texture_size_normalization_factor;
+#ifndef WR_FEATURE_INTERLEAVED_Y_CB_CR
     vHalfTexelUv = vec2(0.5) / uv_texture_size_normalization_factor;
+#endif
 }
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -2,42 +2,104 @@
  * 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 ellipse::Ellipse;
 use frame_builder::FrameBuilder;
 use mask_cache::{ClipSource};
 use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, GpuBlock32, PrimitiveContainer};
 use tiling::PrimitiveFlags;
-use util::pack_as_float;
+use util::{lerp, pack_as_float};
 use webrender_traits::{BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ClipRegion};
 use webrender_traits::{ColorF, LayerPoint, LayerRect, LayerSize, NormalBorder};
 
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum BorderCornerInstance {
+    Single,     // Single instance needed - corner styles are same or similar.
+    Double,     // Different corner styles. Draw two instances, one per style.
+}
+
+#[repr(C)]
+pub enum BorderCornerSide {
+    Both,
+    First,
+    Second,
+}
+
+#[repr(C)]
 enum BorderCorner {
     TopLeft,
     TopRight,
     BottomLeft,
     BottomRight,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub enum BorderCornerKind {
     None,
     Solid,
-    Clip,
-    Mask(BorderCornerClipData, LayerSize, LayerSize),
-    Unhandled,
+    Clip(BorderCornerInstance),
+    Mask(BorderCornerClipData, LayerSize, LayerSize, BorderCornerClipKind),
+}
+
+impl BorderCornerKind {
+    fn new_mask(kind: BorderCornerClipKind,
+                width0: f32,
+                width1: f32,
+                corner: BorderCorner,
+                radius: LayerSize,
+                border_rect: LayerRect) -> BorderCornerKind {
+        let size = LayerSize::new(width0.max(radius.width), width1.max(radius.height));
+        let (origin, clip_center) = match corner {
+            BorderCorner::TopLeft => {
+                let origin = border_rect.origin;
+                let clip_center = origin + size;
+                (origin, clip_center)
+            }
+            BorderCorner::TopRight => {
+                let origin = LayerPoint::new(border_rect.origin.x +
+                                             border_rect.size.width -
+                                             size.width,
+                                             border_rect.origin.y);
+                let clip_center = origin + LayerSize::new(0.0, size.height);
+                (origin, clip_center)
+            }
+            BorderCorner::BottomRight => {
+                let origin = border_rect.origin + (border_rect.size - size);
+                let clip_center = origin;
+                (origin, clip_center)
+            }
+            BorderCorner::BottomLeft => {
+                let origin = LayerPoint::new(border_rect.origin.x,
+                                             border_rect.origin.y +
+                                             border_rect.size.height -
+                                             size.height);
+                let clip_center = origin + LayerSize::new(size.width, 0.0);
+                (origin, clip_center)
+            }
+        };
+        let clip_data = BorderCornerClipData {
+            corner_rect: LayerRect::new(origin, size),
+            clip_center: clip_center,
+            corner: pack_as_float(corner as u32),
+            kind: pack_as_float(kind as u32),
+        };
+        BorderCornerKind::Mask(clip_data,
+                               radius,
+                               LayerSize::new(width0, width1),
+                               kind)
+    }
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderEdgeKind {
     None,
     Solid,
     Clip,
-    Unhandled,
 }
 
 trait NormalBorderHelpers {
     fn get_corner(&self,
                   edge0: &BorderSide,
                   width0: f32,
                   edge1: &BorderSide,
                   width1: f32,
@@ -75,77 +137,66 @@ impl NormalBorderHelpers for NormalBorde
             (BorderStyle::Hidden, _) | (_, BorderStyle::Hidden) => BorderCornerKind::None,
 
             // If both borders are solid, we can draw them with a simple rectangle if
             // both the colors match and there is no radius.
             (BorderStyle::Solid, BorderStyle::Solid) => {
                 if edge0.color == edge1.color && radius.width == 0.0 && radius.height == 0.0 {
                     BorderCornerKind::Solid
                 } else {
-                    BorderCornerKind::Clip
+                    BorderCornerKind::Clip(BorderCornerInstance::Single)
                 }
             }
 
             // Inset / outset borders just modtify the color of edges, so can be
             // drawn with the normal border corner shader.
             (BorderStyle::Outset, BorderStyle::Outset) |
             (BorderStyle::Inset, BorderStyle::Inset) |
             (BorderStyle::Double, BorderStyle::Double) |
             (BorderStyle::Groove, BorderStyle::Groove) |
-            (BorderStyle::Ridge, BorderStyle::Ridge) => BorderCornerKind::Clip,
-
-            // Dashed border corners get drawn into a clip mask.
-            (BorderStyle::Dashed, BorderStyle::Dashed) => {
-                let size = LayerSize::new(width0.max(radius.width), width1.max(radius.height));
-                let (origin, clip_center, sign_modifier) = match corner {
-                    BorderCorner::TopLeft => {
-                        let origin = border_rect.origin;
-                        let clip_center = origin + size;
-                        (origin, clip_center, LayerPoint::new(-1.0, -1.0))
-                    }
-                    BorderCorner::TopRight => {
-                        let origin = LayerPoint::new(border_rect.origin.x +
-                                                     border_rect.size.width -
-                                                     size.width,
-                                                     border_rect.origin.y);
-                        let clip_center = origin + LayerSize::new(0.0, size.height);
-                        (origin, clip_center, LayerPoint::new(1.0, -1.0))
-                    }
-                    BorderCorner::BottomRight => {
-                        let origin = border_rect.origin + (border_rect.size - size);
-                        let clip_center = origin;
-                        (origin, clip_center, LayerPoint::new(1.0, 1.0))
-                    }
-                    BorderCorner::BottomLeft => {
-                        let origin = LayerPoint::new(border_rect.origin.x,
-                                                     border_rect.origin.y +
-                                                     border_rect.size.height -
-                                                     size.height);
-                        let clip_center = origin + LayerSize::new(size.width, 0.0);
-                        (origin, clip_center, LayerPoint::new(-1.0, 1.0))
-                    }
-                };
-                let clip_data = BorderCornerClipData {
-                    corner_rect: LayerRect::new(origin, size),
-                    clip_center: clip_center,
-                    sign_modifier: sign_modifier,
-                };
-                BorderCornerKind::Mask(clip_data, *radius, LayerSize::new(width0, width1))
+            (BorderStyle::Ridge, BorderStyle::Ridge) => {
+                BorderCornerKind::Clip(BorderCornerInstance::Single)
             }
 
-            // Assume complex for these cases.
-            // TODO(gw): There are some cases in here that can be handled with a fast path.
-            // For example, with inset/outset borders, two of the four corners are solid.
-            (BorderStyle::Dotted, _) | (_, BorderStyle::Dotted) => BorderCornerKind::Unhandled,
-            (BorderStyle::Dashed, _) | (_, BorderStyle::Dashed) => BorderCornerKind::Unhandled,
-            (BorderStyle::Double, _) | (_, BorderStyle::Double) => BorderCornerKind::Unhandled,
-            (BorderStyle::Groove, _) | (_, BorderStyle::Groove) => BorderCornerKind::Unhandled,
-            (BorderStyle::Ridge, _) | (_, BorderStyle::Ridge) => BorderCornerKind::Unhandled,
-            (BorderStyle::Outset, _) | (_, BorderStyle::Outset) => BorderCornerKind::Unhandled,
-            (BorderStyle::Inset, _) | (_, BorderStyle::Inset) => BorderCornerKind::Unhandled,
+            // Dashed and dotted border corners get drawn into a clip mask.
+            (BorderStyle::Dashed, BorderStyle::Dashed) => {
+                BorderCornerKind::new_mask(BorderCornerClipKind::Dash,
+                                           width0,
+                                           width1,
+                                           corner,
+                                           *radius,
+                                           *border_rect)
+            }
+            (BorderStyle::Dotted, BorderStyle::Dotted) => {
+                BorderCornerKind::new_mask(BorderCornerClipKind::Dot,
+                                           width0,
+                                           width1,
+                                           corner,
+                                           *radius,
+                                           *border_rect)
+            }
+
+            // Draw border transitions with dots and/or dashes as
+            // solid segments. The old border path didn't support
+            // this anyway, so we might as well start using the new
+            // border path here, since the dashing in the edges is
+            // much higher quality anyway.
+            (BorderStyle::Dotted, _) |
+            (_, BorderStyle::Dotted) |
+            (BorderStyle::Dashed, _) |
+            (_, BorderStyle::Dashed) => {
+                BorderCornerKind::Clip(BorderCornerInstance::Single)
+            }
+
+            // Everything else can be handled by drawing the corner twice,
+            // where the shader outputs zero alpha for the side it's not
+            // drawing. This is somewhat inefficient in terms of pixels
+            // written, but it's a fairly rare case, and we can optimize
+            // this case later.
+            _ => BorderCornerKind::Clip(BorderCornerInstance::Double),
         }
     }
 
     fn get_edge(&self,
                 edge: &BorderSide,
                 width: f32) -> (BorderEdgeKind, f32) {
         if width == 0.0 {
             return (BorderEdgeKind::None, 0.0);
@@ -157,46 +208,45 @@ impl NormalBorderHelpers for NormalBorde
 
             BorderStyle::Solid |
             BorderStyle::Inset |
             BorderStyle::Outset => (BorderEdgeKind::Solid, width),
 
             BorderStyle::Double |
             BorderStyle::Groove |
             BorderStyle::Ridge |
-            BorderStyle::Dashed => (BorderEdgeKind::Clip, width),
-
-            BorderStyle::Dotted => (BorderEdgeKind::Unhandled, width),
+            BorderStyle::Dashed |
+            BorderStyle::Dotted => (BorderEdgeKind::Clip, width),
         }
     }
 }
 
 impl FrameBuilder {
     fn add_normal_border_primitive(&mut self,
                                    rect: &LayerRect,
                                    border: &NormalBorder,
                                    widths: &BorderWidths,
                                    clip_and_scroll: ClipAndScrollInfo,
                                    clip_region: &ClipRegion,
-                                   use_new_border_path: bool,
+                                   corner_instances: [BorderCornerInstance; 4],
                                    extra_clips: &[ClipSource]) {
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
         // These colors are used during inset/outset scaling.
         let left_color      = left.border_color(1.0, 2.0/3.0, 0.3, 0.7);
         let top_color       = top.border_color(1.0, 2.0/3.0, 0.3, 0.7);
         let right_color     = right.border_color(2.0/3.0, 1.0, 0.7, 0.3);
         let bottom_color    = bottom.border_color(2.0/3.0, 1.0, 0.7, 0.3);
 
         let prim_cpu = BorderPrimitiveCpu {
-            use_new_border_path: use_new_border_path,
+            corner_instances: corner_instances,
         };
 
         let prim_gpu = BorderPrimitiveGpu {
             colors: [ left_color, top_color, right_color, bottom_color ],
             widths: [ widths.left,
                       widths.top,
                       widths.right,
                       widths.bottom ],
@@ -272,52 +322,28 @@ impl FrameBuilder {
                               widths.left,
                               bottom,
                               widths.bottom,
                               &radius.bottom_left,
                               BorderCorner::BottomLeft,
                               rect),
         ];
 
-        // If any of the corners are unhandled, fall back to slow path for now.
-        if corners.iter().any(|c| *c == BorderCornerKind::Unhandled) {
-            self.add_normal_border_primitive(rect,
-                                             border,
-                                             widths,
-                                             clip_and_scroll,
-                                             clip_region,
-                                             false,
-                                             &[]);
-            return;
-        }
-
         let (left_edge, left_len) = border.get_edge(left, widths.left);
         let (top_edge, top_len) = border.get_edge(top, widths.top);
         let (right_edge, right_len) = border.get_edge(right, widths.right);
         let (bottom_edge, bottom_len) = border.get_edge(bottom, widths.bottom);
 
         let edges = [
             left_edge,
             top_edge,
             right_edge,
             bottom_edge,
         ];
 
-        // If any of the edges are unhandled, fall back to slow path for now.
-        if edges.iter().any(|e| *e == BorderEdgeKind::Unhandled) {
-            self.add_normal_border_primitive(rect,
-                                             border,
-                                             widths,
-                                             clip_and_scroll,
-                                             clip_region,
-                                             false,
-                                             &[]);
-            return;
-        }
-
         // Use a simple rectangle case when all edges and corners are either
         // solid or none.
         let all_corners_simple = corners.iter().all(|c| {
             *c == BorderCornerKind::Solid || *c == BorderCornerKind::None
         });
         let all_edges_simple = edges.iter().all(|e| {
             *e == BorderEdgeKind::Solid || *e == BorderEdgeKind::None
         });
@@ -361,32 +387,40 @@ impl FrameBuilder {
                                                          LayerSize::new(rect_width, bottom_len)),
                                          clip_region,
                                          &border.bottom.color,
                                          PrimitiveFlags::None);
             }
         } else {
             // Create clip masks for border corners, if required.
             let mut extra_clips = Vec::new();
+            let mut corner_instances = [BorderCornerInstance::Single; 4];
 
-            for corner in corners.iter() {
-                if let &BorderCornerKind::Mask(corner_data, corner_radius, widths) = corner {
-                    let clip_source = BorderCornerClipSource::new(corner_data,
-                                                                  corner_radius,
-                                                                  widths);
-                    extra_clips.push(ClipSource::BorderCorner(clip_source));
+            for (i, corner) in corners.iter().enumerate() {
+                match corner {
+                    &BorderCornerKind::Mask(corner_data, corner_radius, widths, kind) => {
+                        let clip_source = BorderCornerClipSource::new(corner_data,
+                                                                      corner_radius,
+                                                                      widths,
+                                                                      kind);
+                        extra_clips.push(ClipSource::BorderCorner(clip_source));
+                    }
+                    &BorderCornerKind::Clip(instance_kind) => {
+                        corner_instances[i] = instance_kind;
+                    }
+                    _ => {}
                 }
             }
 
             self.add_normal_border_primitive(rect,
                                              border,
                                              widths,
                                              clip_and_scroll,
                                              clip_region,
-                                             true,
+                                             corner_instances,
                                              &extra_clips);
         }
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self,
                     scale_factor_0: f32,
@@ -416,105 +450,217 @@ impl BorderSideHelpers for BorderSide {
                     ColorF::new(black_color_1, black_color_1, black_color_1, self.color.a)
                 }
             }
             _ => self.color,
         }
     }
 }
 
+/// The kind of border corner clip.
+#[repr(C)]
+#[derive(Copy, Debug, Clone, PartialEq)]
+pub enum BorderCornerClipKind {
+    Dash,
+    Dot,
+}
+
 /// The source data for a border corner clip mask.
 #[derive(Debug, Clone)]
 pub struct BorderCornerClipSource {
     pub corner_data: BorderCornerClipData,
-    pub dash_count: usize,
-    dash_arc_length: f32,
+    pub max_clip_count: usize,
+    pub actual_clip_count: usize,
+    kind: BorderCornerClipKind,
+    widths: LayerSize,
     ellipse: Ellipse,
 }
 
 impl BorderCornerClipSource {
     pub fn new(corner_data: BorderCornerClipData,
                corner_radius: LayerSize,
-               widths: LayerSize) -> BorderCornerClipSource {
-        let ellipse = Ellipse::new(corner_radius);
-
+               widths: LayerSize,
+               kind: BorderCornerClipKind) -> BorderCornerClipSource {
         // Work out a dash length (and therefore dash count)
         // based on the width of the border edges. The "correct"
         // dash length is not mentioned in the CSS borders
         // spec. The calculation below is similar, but not exactly
         // the same as what Gecko uses.
         // TODO(gw): Iterate on this to get it closer to what Gecko
         //           uses for dash length.
 
-        // Approximate the total arc length of the quarter ellipse.
-        let total_arc_length = ellipse.get_quarter_arc_length();
+        let (ellipse, max_clip_count) = match kind {
+            BorderCornerClipKind::Dash => {
+                let ellipse = Ellipse::new(corner_radius);
 
-        // The desired dash length is ~3x the border width.
-        let average_border_width = 0.5 * (widths.width + widths.height);
-        let desired_dash_arc_length = average_border_width * 3.0;
+                // The desired dash length is ~3x the border width.
+                let average_border_width = 0.5 * (widths.width + widths.height);
+                let desired_dash_arc_length = average_border_width * 3.0;
+
+                // Get the ideal number of dashes for that arc length.
+                // This is scaled by 0.5 since there is an on/off length
+                // for each dash.
+                let desired_count = 0.5 * ellipse.total_arc_length / desired_dash_arc_length;
 
-        // Get the ideal number of dashes for that arc length.
-        // This is scaled by 0.5 since there is an on/off length
-        // for each dash.
-        let desired_count = 0.5 * total_arc_length / desired_dash_arc_length;
+                // Round that up to the nearest integer, so that the dash length
+                // doesn't exceed the ratio above. Add one extra dash to cover
+                // the last half-dash of the arc.
+                (ellipse, 1 + desired_count.ceil() as usize)
+            }
+            BorderCornerClipKind::Dot => {
+                // The centers of dots follow an ellipse along the middle of the
+                // border radius.
+                let inner_radius = corner_radius - widths * 0.5;
+                let ellipse = Ellipse::new(inner_radius);
 
-        // Round that up to the nearest integer, so that the dash length
-        // doesn't exceed the ratio above.
-        let actual_count = desired_count.ceil();
+                // Allocate a "worst case" number of dot clips. This can be
+                // calculated by taking the minimum edge radius, since that
+                // will result in the maximum number of dots along the path.
+                let min_diameter = widths.width.min(widths.height);
 
-        // Get the correct dash arc length.
-        let dash_arc_length = 0.5 * total_arc_length / actual_count;
+                // Get the number of circles (assuming spacing of one diameter
+                // between dots).
+                let max_dot_count = 0.5 * ellipse.total_arc_length / min_diameter;
 
-        // Get the number of dashes we'll need to fit.
-        let dash_count = actual_count as usize;
+                // Add space for one extra dot since they are centered at the
+                // start of the arc.
+                (ellipse, 1 + max_dot_count.ceil() as usize)
+            }
+        };
 
         BorderCornerClipSource {
+            kind: kind,
             corner_data: corner_data,
-            dash_count: dash_count,
+            max_clip_count: max_clip_count,
+            actual_clip_count: 0,
             ellipse: ellipse,
-            dash_arc_length: dash_arc_length,
+            widths: widths,
         }
     }
 
-    pub fn populate_gpu_data(&self, slice: &mut [GpuBlock32]) {
-        let (header, dashes) = slice.split_first_mut().unwrap();
+    pub fn populate_gpu_data(&mut self, slice: &mut [GpuBlock32]) {
+        let (header, clips) = slice.split_first_mut().unwrap();
         *header = self.corner_data.into();
 
-        let mut current_arc_length = self.dash_arc_length * 0.5;
-        for dash_index in 0..self.dash_count {
-            let arc_length0 = current_arc_length;
-            current_arc_length += self.dash_arc_length;
+        match self.kind {
+            BorderCornerClipKind::Dash => {
+                // Get the correct dash arc length.
+                self.actual_clip_count = self.max_clip_count;
+                let dash_arc_length = 0.5 * self.ellipse.total_arc_length / (self.actual_clip_count - 1) as f32;
+                let mut current_arc_length = -0.5 * dash_arc_length;
+                for dash_index in 0..self.actual_clip_count {
+                    let arc_length0 = current_arc_length;
+                    current_arc_length += dash_arc_length;
+
+                    let arc_length1 = current_arc_length;
+                    current_arc_length += dash_arc_length;
+
+                    let dash_data = BorderCornerDashClipData::new(arc_length0,
+                                                                  arc_length1,
+                                                                  &self.ellipse);
+
+                    clips[dash_index] = dash_data.into();
+                }
+            }
+            BorderCornerClipKind::Dot => {
+                let mut forward_dots = Vec::new();
+                let mut back_dots = Vec::new();
+                let mut leftover_arc_length = 0.0;
+
+                // Alternate between adding dots at the start and end of the
+                // ellipse arc. This ensures that we always end up with an exact
+                // half dot at each end of the arc, to match up with the edges.
+                forward_dots.push(DotInfo::new(0.0, self.widths.width));
+                back_dots.push(DotInfo::new(self.ellipse.total_arc_length, self.widths.height));
+
+                for dot_index in 0..self.max_clip_count {
+                    let prev_forward_pos = *forward_dots.last().unwrap();
+                    let prev_back_pos = *back_dots.last().unwrap();
+
+                    // Select which end of the arc to place a dot from.
+                    // This just alternates between the start and end of
+                    // the arc, which ensures that there is always an
+                    // exact half-dot at each end of the ellipse.
+                    let going_forward = dot_index & 1 == 0;
+
+                    let (next_dot_pos, leftover) = if going_forward {
+                        let next_dot_pos = prev_forward_pos.arc_pos + 2.0 * prev_forward_pos.diameter;
+                        (next_dot_pos, prev_back_pos.arc_pos - next_dot_pos)
+                    } else {
+                        let next_dot_pos = prev_back_pos.arc_pos - 2.0 * prev_back_pos.diameter;
+                        (next_dot_pos, next_dot_pos - prev_forward_pos.arc_pos)
+                    };
 
-            let arc_length1 = current_arc_length;
-            current_arc_length += self.dash_arc_length;
+                    // Use a lerp between each edge's dot
+                    // diameter, based on the linear distance
+                    // along the arc to get the diameter of the
+                    // dot at this arc position.
+                    let t = next_dot_pos / self.ellipse.total_arc_length;
+                    let dot_diameter = lerp(self.widths.width, self.widths.height, t);
+
+                    // If we can't fit a dot, bail out.
+                    if leftover < dot_diameter {
+                        leftover_arc_length = leftover;
+                        break;
+                    }
+
+                    // We can place a dot!
+                    let dot = DotInfo::new(next_dot_pos, dot_diameter);
+                    if going_forward {
+                        forward_dots.push(dot);
+                    } else {
+                        back_dots.push(dot);
+                    }
+                }
 
-            let dash_data = BorderCornerDashClipData::new(arc_length0,
-                                                          arc_length1,
-                                                          &self.ellipse);
-            dashes[dash_index] = dash_data.into();
+                // Now step through the dots, and distribute any extra
+                // leftover space on the arc between them evenly. Once
+                // the final arc position is determined, generate the correct
+                // arc positions and angles that get passed to the clip shader.
+                self.actual_clip_count = 0;
+                let dot_count = forward_dots.len() + back_dots.len();
+                let extra_space_per_dot = leftover_arc_length / (dot_count - 1) as f32;
+
+                for (i, dot) in forward_dots.iter().enumerate() {
+                    let extra_dist = i as f32 * extra_space_per_dot;
+                    let dot = BorderCornerDotClipData::new(dot.arc_pos + extra_dist,
+                                                           0.5 * dot.diameter,
+                                                           &self.ellipse);
+                    clips[self.actual_clip_count] = dot.into();
+                    self.actual_clip_count += 1;
+                }
+
+                for (i, dot) in back_dots.iter().enumerate() {
+                    let extra_dist = i as f32 * extra_space_per_dot;
+                    let dot = BorderCornerDotClipData::new(dot.arc_pos - extra_dist,
+                                                           0.5 * dot.diameter,
+                                                           &self.ellipse);
+                    clips[self.actual_clip_count] = dot.into();
+                    self.actual_clip_count += 1;
+                }
+            }
         }
     }
 }
 
 /// Represents the common GPU data for writing a
 /// clip mask for a border corner.
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[repr(C)]
 pub struct BorderCornerClipData {
     /// Local space rect of the border corner.
     corner_rect: LayerRect,
     /// Local space point that is the center of the
     /// circle or ellipse that we are clipping against.
     clip_center: LayerPoint,
-    /// A constant that flips the local space points
-    /// and tangents of the ellipse for this specific
-    /// corner. This is used since the ellipse points
-    /// and tangents are always generated for a single
-    /// quadrant only.
-    sign_modifier: LayerPoint,
+    /// The shader needs to know which corner, to
+    /// be able to flip the dash tangents to the
+    /// right orientation.
+    corner: f32,        // Of type BorderCorner enum
+    kind: f32,          // Of type BorderCornerClipKind enum
 }
 
 /// Represents the GPU data for drawing a single dash
 /// to a clip mask. A dash clip is defined by two lines.
 /// We store a point on the ellipse curve, and a tangent
 /// to that point, which allows for efficient line-distance
 /// calculations in the fragment shader.
 #[derive(Debug, Clone)]
@@ -539,8 +685,48 @@ impl BorderCornerDashClipData {
         BorderCornerDashClipData {
             point0: p0,
             tangent0: t0,
             point1: p1,
             tangent1: t1,
         }
     }
 }
+
+/// Represents the GPU data for drawing a single dot
+/// to a clip mask.
+#[derive(Debug, Clone)]
+#[repr(C)]
+pub struct BorderCornerDotClipData {
+    pub center: LayerPoint,
+    pub radius: f32,
+    pub padding: [f32; 5],
+}
+
+impl BorderCornerDotClipData {
+    pub fn new(arc_length: f32,
+               radius: f32,
+               ellipse: &Ellipse) -> BorderCornerDotClipData {
+        let theta = ellipse.find_angle_for_arc_length(arc_length);
+        let (center, _) = ellipse.get_point_and_tangent(theta);
+
+        BorderCornerDotClipData {
+            center: center,
+            radius: radius,
+            padding: [0.0; 5],
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+struct DotInfo {
+    arc_pos: f32,
+    diameter: f32,
+}
+
+impl DotInfo {
+    fn new(arc_pos: f32, diameter: f32) -> DotInfo {
+        DotInfo {
+            arc_pos: arc_pos,
+            diameter: diameter,
+        }
+    }
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -5,20 +5,20 @@
 use euclid::Point3D;
 use geometry::ray_intersects_rect;
 use mask_cache::{ClipSource, MaskCacheInfo, RegionMode};
 use prim_store::GpuBlock32;
 use renderer::VertexDataStore;
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::TransformedRectKind;
-use webrender_traits::{ClipId, ClipRegion, LayerPixel, LayerPoint, LayerRect, LayerSize};
-use webrender_traits::{LayerToScrollTransform, LayerToWorldTransform, PipelineId};
-use webrender_traits::{ScrollEventPhase, ScrollLayerRect, ScrollLocation, WorldPoint};
-use webrender_traits::{DeviceIntRect, WorldPoint4D};
+use webrender_traits::{ClipId, ClipRegion, DeviceIntRect, LayerPixel, LayerPoint, LayerRect};
+use webrender_traits::{LayerSize, LayerToScrollTransform, LayerToWorldTransform, PipelineId};
+use webrender_traits::{ScrollClamping, ScrollEventPhase, ScrollLayerRect, ScrollLocation};
+use webrender_traits::{WorldPoint, WorldPoint4D};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
 
 #[cfg(not(target_os = "macos"))]
 const CAN_OVERSCROLL: bool = false;
 
 #[derive(Clone, Debug)]
@@ -193,34 +193,42 @@ impl ClipScrollNode {
             -scrollable_height - self.scrolling.offset.y
         } else {
             0.0
         };
 
         LayerSize::new(overscroll_x, overscroll_y)
     }
 
-    pub fn set_scroll_origin(&mut self, origin: &LayerPoint) -> bool {
+    pub fn set_scroll_origin(&mut self, origin: &LayerPoint, clamp: ScrollClamping) -> bool {
         match self.node_type {
             NodeType::ReferenceFrame(_) => {
                 warn!("Tried to scroll a reference frame.");
                 return false;
             }
             NodeType::Clip(_) => {}
         };
 
 
         let scrollable_height = self.scrollable_height();
         let scrollable_width = self.scrollable_width();
-        if scrollable_height <= 0. && scrollable_width <= 0. {
-            return false;
-        }
+
+        let new_offset = match clamp {
+            ScrollClamping::ToContentBounds => {
+                if scrollable_height <= 0. && scrollable_width <= 0. {
+                    return false;
+                }
 
-        let new_offset = LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
-                                         (-origin.y).max(-scrollable_height).min(0.0).round());
+                let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
+                LayerPoint::new((-origin.x).max(-scrollable_width).min(0.0).round(),
+                                (-origin.y).max(-scrollable_height).min(0.0).round())
+            }
+            ScrollClamping::NoClamping => LayerPoint::zero() - *origin,
+        };
+
         if new_offset == self.scrolling.offset {
             return false;
         }
 
         self.scrolling.offset = new_offset;
         self.scrolling.bouncing_back = false;
         self.scrolling.started_bouncing_back = false;
         true
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,25 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
 use fnv::FnvHasher;
+use print_tree::PrintTree;
 use std::collections::{HashMap, HashSet};
 use std::hash::BuildHasherDefault;
 use webrender_traits::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform};
-use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollEventPhase, ScrollLayerRect};
-use webrender_traits::{ScrollLayerState, ScrollLocation, WorldPoint, as_scroll_parent_rect};
+use webrender_traits::{LayerToWorldTransform, PipelineId, ScrollClamping, ScrollEventPhase};
+use webrender_traits::{ScrollLayerRect, ScrollLayerState, ScrollLocation, WorldPoint};
+use webrender_traits::as_scroll_parent_rect;
 
 pub type ScrollStates = HashMap<ClipId, ScrollingState, BuildHasherDefault<FnvHasher>>;
 
 pub struct ClipScrollTree {
     pub nodes: HashMap<ClipId, ClipScrollNode, BuildHasherDefault<FnvHasher>>,
-    pub pending_scroll_offsets: HashMap<ClipId, LayerPoint>,
+    pub pending_scroll_offsets: HashMap<ClipId, (LayerPoint, ScrollClamping)>,
 
     /// The ClipId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
     pub currently_scrolling_node_id: Option<ClipId>,
 
     /// The current reference frame id, used for giving a unique id to all new
     /// reference frames. The ClipScrollTree increments this by one every time a
     /// reference frame is created.
@@ -126,33 +128,32 @@ impl ClipScrollTree {
                 scroll_states.insert(layer_id, old_node.scrolling);
             }
         }
 
         self.pipelines_to_discard.clear();
         scroll_states
     }
 
-    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ClipId) -> bool {
+    pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
         if id.is_reference_frame() {
             warn!("Tried to scroll a reference frame.");
             return false;
         }
 
         if self.nodes.is_empty() {
-            self.pending_scroll_offsets.insert(id, origin);
+            self.pending_scroll_offsets.insert(id, (origin, clamp));
             return false;
         }
 
-        let origin = LayerPoint::new(origin.x.max(0.0), origin.y.max(0.0));
         if let Some(node) = self.nodes.get_mut(&id) {
-            return node.set_scroll_origin(&origin);
+            return node.set_scroll_origin(&origin, clamp);
         }
 
-        self.pending_scroll_offsets.insert(id, origin);
+        self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
 
     pub fn scroll(&mut self,
                   scroll_location: ScrollLocation,
                   cursor: WorldPoint,
                   phase: ScrollEventPhase)
                   -> bool {
@@ -303,18 +304,18 @@ impl ClipScrollTree {
         for (clip_id, node) in &mut self.nodes {
             let scrolling_state = match old_states.get(clip_id) {
                 Some(old_scrolling_state) => *old_scrolling_state,
                 None => ScrollingState::new(),
             };
 
             node.finalize(&scrolling_state);
 
-            if let Some(pending_offset) = self.pending_scroll_offsets.remove(clip_id) {
-                node.set_scroll_origin(&pending_offset);
+            if let Some((pending_offset, clamping)) = self.pending_scroll_offsets.remove(clip_id) {
+                node.set_scroll_origin(&pending_offset, clamping);
             }
         }
 
     }
 
     pub fn add_reference_frame(&mut self,
                                rect: &LayerRect,
                                transform: &LayerToScrollTransform,
@@ -349,10 +350,52 @@ impl ClipScrollTree {
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.pipelines_to_discard.insert(pipeline_id);
 
         match self.currently_scrolling_node_id {
             Some(id) if id.pipeline_id() == pipeline_id => self.currently_scrolling_node_id = None,
             _ => {}
         }
     }
+
+    fn print_node(&self, id: &ClipId, pt: &mut PrintTree) {
+        let node = self.nodes.get(id).unwrap();
+
+        match node.node_type {
+            NodeType::Clip(ref info) => {
+                pt.new_level("Clip".to_owned());
+                pt.add_item(format!("screen_bounding_rect: {:?}", info.screen_bounding_rect));
+
+                pt.new_level(format!("Clip Sources [{}]", info.clip_sources.len()));
+                for source in &info.clip_sources {
+                    pt.add_item(format!("{:?}", source));
+                }
+                pt.end_level();
+            }
+            NodeType::ReferenceFrame(ref transform) => {
+                pt.new_level(format!("ReferenceFrame {:?}", transform));
+            }
+        }
+
+        pt.add_item(format!("content_size: {:?}", node.content_size));
+        pt.add_item(format!("scroll.offset: {:?}", node.scrolling.offset));
+        pt.add_item(format!("combined_local_viewport_rect: {:?}", node.combined_local_viewport_rect));
+        pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
+        pt.add_item(format!("local_clip_rect: {:?}", node.local_clip_rect));
+        pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
+        pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
+
+        for child_id in &node.children {
+            self.print_node(child_id, pt);
+        }
+
+        pt.end_level();
+    }
+
+    #[allow(dead_code)]
+    pub fn print(&self) {
+        if !self.nodes.is_empty() {
+            let mut pt = PrintTree::new("clip_scroll tree");
+            self.print_node(&self.root_reference_frame_id, &mut pt);
+        }
+    }
 }
 
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -40,16 +40,17 @@ const SHADER_VERSION_GL: &'static str = 
 
 const SHADER_VERSION_GLES: &'static str = "#version 300 es\n";
 
 static SHADER_PREAMBLE: &'static str = "shared";
 
 #[repr(u32)]
 pub enum DepthFunction {
     Less = gl::LESS,
+    LessEqual = gl::LEQUAL,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum TextureTarget {
     Default,
     Array,
     Rect,
     External,
@@ -443,20 +444,20 @@ impl Program {
                 self.gl.bind_attrib_location(self.id, ClipAttribute::DataIndex as gl::GLuint, "aClipDataIndex");
                 self.gl.bind_attrib_location(self.id, ClipAttribute::SegmentIndex as gl::GLuint, "aClipSegmentIndex");
             }
         }
 
         self.gl.link_program(self.id);
         if self.gl.get_program_iv(self.id, gl::LINK_STATUS) == (0 as gl::GLint) {
             let error_log = self.gl.get_program_info_log(self.id);
-            println!("Failed to link shader program: {}", error_log);
+            println!("Failed to link shader program: {:?}\n{}", self.name, error_log);
             self.gl.detach_shader(self.id, vs_id);
             self.gl.detach_shader(self.id, fs_id);
-            return Err(ShaderError::Link(error_log));
+            return Err(ShaderError::Link(self.name.clone(), error_log));
         }
 
         Ok(())
     }
 }
 
 impl Drop for Program {
     fn drop(&mut self) {
@@ -854,17 +855,17 @@ impl FileWatcherThread {
 pub struct Capabilities {
     pub max_ubo_size: usize,
     pub supports_multisampling: bool,
 }
 
 #[derive(Clone, Debug)]
 pub enum ShaderError {
     Compilation(String, String), // name, error mssage
-    Link(String), // error message
+    Link(String, String), // name, error message
 }
 
 pub struct Device {
     gl: Rc<gl::Gl>,
     // device state
     bound_textures: [TextureId; 16],
     bound_program: ProgramId,
     bound_vao: VAOId,
@@ -1707,22 +1708,17 @@ impl Device {
                           data: &[u8]) {
         debug_assert!(self.inside_frame);
 
         let mut expanded_data = Vec::new();
 
         let (gl_format, bpp, data) = match self.textures.get(&texture_id).unwrap().format {
             ImageFormat::A8 => {
                 if cfg!(any(target_arch="arm", target_arch="aarch64")) {
-                    for byte in data {
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                        expanded_data.push(*byte);
-                    }
+                    expanded_data.extend(data.iter().flat_map(|byte| repeat(*byte).take(4)));
                     (get_gl_format_bgra(self.gl()), 4, expanded_data.as_slice())
                 } else {
                     (GL_FORMAT_A, 1, data)
                 }
             }
             ImageFormat::RGB8 => (gl::RGB, 3, data),
             ImageFormat::RGBA8 => (get_gl_format_bgra(self.gl()), 4, data),
             ImageFormat::RG8 => (gl::RG, 2, data),
@@ -1732,18 +1728,16 @@ impl Device {
         let row_length = match stride {
             Some(value) => value / bpp,
             None => width,
         };
 
         // Take the stride into account for all rows, except the last one.
         let len = bpp * row_length * (height - 1)
                 + width * bpp;
-
-        assert!(data.len() as u32 >= len);
         let data = &data[0..len as usize];
 
         if let Some(..) = stride {
             self.gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, row_length as gl::GLint);
         }
 
         self.bind_texture(DEFAULT_TEXTURE, texture_id);
         self.update_image_for_2d_texture(texture_id.target,
--- a/gfx/webrender/src/ellipse.rs
+++ b/gfx/webrender/src/ellipse.rs
@@ -7,88 +7,94 @@ use std::f32::consts::FRAC_PI_2;
 
 /// Number of steps to integrate arc length over.
 const STEP_COUNT: usize = 20;
 
 /// Represents an ellipse centred at a local space origin.
 #[derive(Debug, Clone)]
 pub struct Ellipse {
     pub radius: LayerSize,
+    pub total_arc_length: f32,
 }
 
 impl Ellipse {
     pub fn new(radius: LayerSize) -> Ellipse {
+        // Approximate the total length of the first quadrant of this ellipse.
+        let total_arc_length = get_simpson_length(FRAC_PI_2,
+                                                  radius.width,
+                                                  radius.height);
+
         Ellipse {
             radius: radius,
+            total_arc_length: total_arc_length,
         }
     }
 
-    /// Use Simpsons rule to approximate the arc length of
-    /// part of an ellipse. Note that this only works over
-    /// the range of [0, pi/2].
-    // TODO(gw): This is a simplistic way to estimate the
-    // arc length of an ellipse segment. We can probably use
-    // a faster / more accurate method!
-    fn get_simpson_length(&self, theta: f32) -> f32 {
-        let df = theta / STEP_COUNT as f32;
-        let mut sum = 0.0;
-
-        for i in 0..(STEP_COUNT+1) {
-            let (sin_theta, cos_theta) = (i as f32 * df).sin_cos();
-            let a = self.radius.width * sin_theta;
-            let b = self.radius.height * cos_theta;
-            let y = (a*a + b*b).sqrt();
-            let q = if i == 0 || i == STEP_COUNT {
-                1.0
-            } else if i % 2 == 0 {
-                2.0
-            } else {
-                4.0
-            };
-
-            sum += q * y;
-        }
-
-        (df / 3.0) * sum
-    }
-
     /// Binary search to estimate the angle of an ellipse
     /// for a given arc length. This only searches over the
     /// first quadrant of an ellipse.
     pub fn find_angle_for_arc_length(&self, arc_length: f32) -> f32 {
+        // Clamp arc length to [0, pi].
+        let arc_length = arc_length.max(0.0).min(self.total_arc_length);
+
         let epsilon = 0.01;
         let mut low = 0.0;
         let mut high = FRAC_PI_2;
         let mut theta = 0.0;
 
         while low <= high {
             theta = 0.5 * (low + high);
-            let length = self.get_simpson_length(theta);
+            let length = get_simpson_length(theta,
+                                            self.radius.width,
+                                            self.radius.height);
 
             if (length - arc_length).abs() < epsilon {
                 break;
             } else if length < arc_length {
                 low = theta;
             } else {
                 high = theta;
             }
         }
 
         theta
     }
 
-    /// Approximate the total length of the first quadrant of
-    /// this ellipse.
-    pub fn get_quarter_arc_length(&self) -> f32 {
-        self.get_simpson_length(FRAC_PI_2)
-    }
-
     /// Get a point and tangent on this ellipse from a given angle.
     /// This only works for the first quadrant of the ellipse.
     pub fn get_point_and_tangent(&self, theta: f32) -> (LayerPoint, LayerPoint) {
         let (sin_theta, cos_theta) = theta.sin_cos();
         let point = LayerPoint::new(self.radius.width * cos_theta,
                                     self.radius.height * sin_theta);
         let tangent = LayerPoint::new(-self.radius.width * sin_theta,
                                        self.radius.height * cos_theta);
         (point, tangent)
     }
 }
+
+/// Use Simpsons rule to approximate the arc length of
+/// part of an ellipse. Note that this only works over
+/// the range of [0, pi/2].
+// TODO(gw): This is a simplistic way to estimate the
+// arc length of an ellipse segment. We can probably use
+// a faster / more accurate method!
+fn get_simpson_length(theta: f32, rx: f32, ry: f32) -> f32 {
+    let df = theta / STEP_COUNT as f32;
+    let mut sum = 0.0;
+
+    for i in 0..(STEP_COUNT+1) {
+        let (sin_theta, cos_theta) = (i as f32 * df).sin_cos();
+        let a = rx * sin_theta;
+        let b = ry * cos_theta;
+        let y = (a*a + b*b).sqrt();
+        let q = if i == 0 || i == STEP_COUNT {
+            1.0
+        } else if i % 2 == 0 {
+            2.0
+        } else {
+            4.0
+        };
+
+        sum += q * y;
+    }
+
+    (df / 3.0) * sum
+}
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -13,21 +13,22 @@ use clip_scroll_tree::{ClipScrollTree, S
 use profiler::TextureCacheProfileCounters;
 use resource_cache::ResourceCache;
 use scene::{Scene, SceneProperties};
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use tiling::{CompositeOps, DisplayListMap, PrimitiveFlags};
 use util::{ComplexClipRegionHelpers, subtract_rect};
 use webrender_traits::{BuiltDisplayList, BuiltDisplayListIter, ClipAndScrollInfo, ClipDisplayItem};
-use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceUintRect, DeviceUintSize, Epoch, FilterOp};
-use webrender_traits::{ImageDisplayItem, ItemRange, LayerPoint, LayerRect, LayerSize, LayerToScrollTransform};
-use webrender_traits::{LayoutTransform, MixBlendMode, PipelineId, ScrollEventPhase};
+use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceUintRect, DeviceUintSize, DisplayItemRef};
+use webrender_traits::{Epoch, FilterOp, ImageDisplayItem, ItemRange, LayerPoint, LayerRect};
+use webrender_traits::{LayerSize, LayerToScrollTransform, LayoutSize, LayoutTransform};
+use webrender_traits::{MixBlendMode, PipelineId, ScrollClamping, ScrollEventPhase};
 use webrender_traits::{ScrollLayerState, ScrollLocation, ScrollPolicy, SpecificDisplayItem};
-use webrender_traits::{StackingContext, TileOffset, WorldPoint};
+use webrender_traits::{StackingContext, TileOffset, TransformStyle, WorldPoint};
 
 #[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
 pub struct FrameId(pub u32);
 
 static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { r: 0.3, g: 0.3, b: 0.3, a: 0.6 };
 
 struct FlattenContext<'a> {
     scene: &'a Scene,
@@ -172,19 +173,19 @@ impl Frame {
 
         self.clip_scroll_tree.drain()
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollLayerState> {
         self.clip_scroll_tree.get_scroll_node_state()
     }
 
-    /// Returns true if any nodes actually changed position or false otherwise.
-    pub fn scroll_nodes(&mut self, origin: LayerPoint, id: ClipId) -> bool {
-        self.clip_scroll_tree.scroll_nodes(origin, id)
+    /// Returns true if the node actually changed position or false otherwise.
+    pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool {
+        self.clip_scroll_tree.scroll_node(origin, id, clamp)
     }
 
     /// Returns true if any nodes actually changed position or false otherwise.
     pub fn scroll(&mut self,
                   scroll_location: ScrollLocation,
                   cursor: WorldPoint,
                   phase: ScrollEventPhase)
                   -> bool {
@@ -224,62 +225,46 @@ impl Frame {
         if window_size.width == 0 || window_size.height == 0 {
             error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
         }
 
         let old_scrolling_states = self.reset();
 
         self.pipeline_epoch_map.insert(root_pipeline_id, root_pipeline.epoch);
 
-        let mut traversal = display_list.iter();
-
-        let (root_stacking_context, root_bounds, root_filters) =
-          match traversal.starting_stacking_context() {
-              Some(some) => some,
-              None => {
-                  warn!("Pipeline display list does not start with a stacking context.");
-                  return;
-              }
-          };
-
         let background_color = root_pipeline.background_color.and_then(|color| {
             if color.a > 0.0 {
                 Some(color)
             } else {
                 None
             }
         });
 
         let mut frame_builder = FrameBuilder::new(self.frame_builder.take(),
                                                   window_size,
                                                   background_color,
                                                   self.frame_builder_config);
 
         {
             let mut context = FlattenContext::new(scene, &mut frame_builder, resource_cache);
 
-            let clip_id = context.builder.push_root(root_pipeline_id,
-                                                    &root_pipeline.viewport_size,
-                                                    &root_bounds.size,
-                                                    &mut self.clip_scroll_tree);
+            context.builder.push_root(root_pipeline_id,
+                                      &root_pipeline.viewport_size,
+                                      &root_pipeline.content_size,
+                                      &mut self.clip_scroll_tree);
 
             context.builder.setup_viewport_offset(window_size,
                                                   inner_rect,
                                                   device_pixel_ratio,
                                                   &mut self.clip_scroll_tree);
 
-            self.flatten_stacking_context(&mut traversal,
-                                          root_pipeline_id,
-                                          &mut context,
-                                          clip_id,
-                                          LayerPoint::zero(),
-                                          0,
-                                          &root_bounds,
-                                          &root_stacking_context,
-                                          root_filters);
+            self.flatten_root(&mut display_list.iter(),
+                              root_pipeline_id,
+                              &mut context,
+                              &root_pipeline.content_size);
         }
 
         self.frame_builder = Some(frame_builder);
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
     }
 
     fn flatten_clip<'a>(&mut self,
                         context: &mut FlattenContext,
@@ -298,17 +283,16 @@ impl Frame {
     }
 
     fn flatten_stacking_context<'a>(&mut self,
                                     traversal: &mut BuiltDisplayListIter<'a>,
                                     pipeline_id: PipelineId,
                                     context: &mut FlattenContext,
                                     context_scroll_node_id: ClipId,
                                     mut reference_frame_relative_offset: LayerPoint,
-                                    level: i32,
                                     bounds: &LayerRect,
                                     stacking_context: &StackingContext,
                                     filters: ItemRange<FilterOp>) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
@@ -361,56 +345,26 @@ impl Frame {
             context.replacements.push((context_scroll_node_id, clip_id));
             reference_frame_relative_offset = LayerPoint::zero();
         } else {
             reference_frame_relative_offset = LayerPoint::new(
                 reference_frame_relative_offset.x + bounds.origin.x,
                 reference_frame_relative_offset.y + bounds.origin.y);
         }
 
-        // TODO(gw): Int with overflow etc
         context.builder.push_stacking_context(&reference_frame_relative_offset,
                                               pipeline_id,
-                                              level == 0,
                                               composition_operations,
                                               *bounds,
                                               stacking_context.transform_style);
 
-        // For the root pipeline, there's no need to add a full screen rectangle
-        // here, as it's handled by the framebuffer clear.
-        if level == 0 && context.scene.root_pipeline_id.unwrap() != pipeline_id {
-            if let Some(pipeline) = context.scene.pipeline_map.get(&pipeline_id) {
-                if let Some(bg_color) = pipeline.background_color {
-                    // Note: we don't use the original clip region here,
-                    // it's already processed by the node we just pushed.
-                    let background_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
-                    context.builder.add_solid_rectangle(ClipAndScrollInfo::simple(clip_id),
-                                                        bounds,
-                                                        &ClipRegion::simple(&background_rect),
-                                                        &bg_color,
-                                                        PrimitiveFlags::None);
-                }
-            }
-        }
-
         self.flatten_items(traversal,
                            pipeline_id,
                            context,
-                           reference_frame_relative_offset,
-                           level);
-
-        if level == 0 && self.frame_builder_config.enable_scrollbars {
-            let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
-            context.builder.add_solid_rectangle(
-                ClipAndScrollInfo::simple(clip_id),
-                &scrollbar_rect,
-                &ClipRegion::simple(&scrollbar_rect),
-                &DEFAULT_SCROLLBAR_COLOR,
-                PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0));
-        }
+                           reference_frame_relative_offset);
 
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
             context.replacements.pop();
         }
 
         if is_reference_frame {
             context.replacements.pop();
             context.builder.pop_reference_frame();
@@ -431,253 +385,295 @@ impl Frame {
         };
 
         let display_list = context.scene.display_lists.get(&pipeline_id);
         let display_list = match display_list {
             Some(display_list) => display_list,
             None => return,
         };
 
-        let mut traversal = display_list.iter();
-
-        let (iframe_stacking_context,
-             iframe_stacking_context_bounds,
-             iframe_filters) = match traversal.starting_stacking_context() {
-            Some(some) => some,
-            None => {
-                warn!("Pipeline display list does not start with a stacking context.");
-                return;
-            }
-        };
-
         self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch);
 
         let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
         let transform = LayerToScrollTransform::create_translation(
             reference_frame_relative_offset.x + bounds.origin.x,
             reference_frame_relative_offset.y + bounds.origin.y,
             0.0);
 
         let iframe_reference_frame_id =
             context.builder.push_reference_frame(Some(parent_id),
                                                  pipeline_id,
                                                  &iframe_rect,
                                                  &transform,
                                                  &mut self.clip_scroll_tree);
 
-        let iframe_clip_id = ClipId::root_scroll_node(pipeline_id);
         context.builder.add_clip_scroll_node(
-            iframe_clip_id,
+            ClipId::root_scroll_node(pipeline_id),
             iframe_reference_frame_id,
             pipeline_id,
-            &LayerRect::new(LayerPoint::zero(), iframe_stacking_context_bounds.size),
+            &LayerRect::new(LayerPoint::zero(), pipeline.content_size),
             &ClipRegion::simple(&iframe_rect),
             &mut self.clip_scroll_tree);
 
-        self.flatten_stacking_context(&mut traversal,
-                                      pipeline_id,
-                                      context,
-                                      iframe_clip_id,
-                                      LayerPoint::zero(),
-                                      0,
-                                      &iframe_stacking_context_bounds,
-                                      &iframe_stacking_context,
-                                      iframe_filters);
+        self.flatten_root(&mut display_list.iter(), pipeline_id, context, &pipeline.content_size);
 
         context.builder.pop_reference_frame();
     }
 
+    fn flatten_item<'a, 'b>(&mut self,
+                            item: DisplayItemRef<'a, 'b>,
+                            pipeline_id: PipelineId,
+                            context: &mut FlattenContext,
+                            reference_frame_relative_offset: LayerPoint)
+                            -> Option<BuiltDisplayListIter<'a>> {
+        let mut clip_and_scroll = item.clip_and_scroll();
+        clip_and_scroll.scroll_node_id =
+            context.clip_id_with_replacement(clip_and_scroll.scroll_node_id);
+
+        match *item.item() {
+            SpecificDisplayItem::WebGL(ref info) => {
+                context.builder.add_webgl_rectangle(clip_and_scroll,
+                                                    item.rect(),
+                                                    item.clip_region(),
+                                                    info.context_id);
+            }
+            SpecificDisplayItem::Image(ref info) => {
+                let image = context.resource_cache.get_image_properties(info.image_key);
+                if let Some(tile_size) = image.tiling {
+                    // The image resource is tiled. We have to generate an image primitive
+                    // for each tile.
+                    let image_size = DeviceUintSize::new(image.descriptor.width,
+                                                         image.descriptor.height);
+                    self.decompose_image(clip_and_scroll,
+                                         context,
+                                         &item.rect(),
+                                         item.clip_region(),
+                                         info,
+                                         image_size,
+                                         tile_size as u32);
+                } else {
+                    context.builder.add_image(clip_and_scroll,
+                                              item.rect(),
+                                              item.clip_region(),
+                                              &info.stretch_size,
+                                              &info.tile_spacing,
+                                              None,
+                                              info.image_key,
+                                              info.image_rendering,
+                                              None);
+                }
+            }
+            SpecificDisplayItem::YuvImage(ref info) => {
+                context.builder.add_yuv_image(clip_and_scroll,
+                                              item.rect(),
+                                              item.clip_region(),
+                                              info.yuv_data,
+                                              info.color_space);
+            }
+            SpecificDisplayItem::Text(ref text_info) => {
+                context.builder.add_text(clip_and_scroll,
+                                         item.rect(),
+                                         item.clip_region(),
+                                         text_info.font_key,
+                                         text_info.size,
+                                         text_info.blur_radius,
+                                         &text_info.color,
+                                         item.glyphs(),
+                                         item.display_list().get(item.glyphs()).count(),
+                                         text_info.glyph_options);
+            }
+            SpecificDisplayItem::Rectangle(ref info) => {
+                let display_list = context.scene.display_lists
+                                          .get(&pipeline_id)
+                                          .expect("No display list?!");
+                // Try to extract the opaque inner rectangle out of the clipped primitive.
+                if let Some(opaque_rect) = clip_intersection(&item.rect(),
+                                                             item.clip_region(),
+                                                             display_list) {
+                    let mut results = Vec::new();
+                    subtract_rect(&item.rect(), &opaque_rect, &mut results);
+                    // The inner rectangle is considered opaque within this layer.
+                    // It may still inherit some masking from the clip stack.
+                    context.builder.add_solid_rectangle(clip_and_scroll,
+                                                        &opaque_rect,
+                                                        &ClipRegion::simple(&item.clip_region().main),
+                                                        &info.color,
+                                                        PrimitiveFlags::None);
+                    for transparent_rect in &results {
+                        context.builder.add_solid_rectangle(clip_and_scroll,
+                                                            transparent_rect,
+                                                            item.clip_region(),
+                                                            &info.color,
+                                                            PrimitiveFlags::None);
+                    }
+                } else {
+                    context.builder.add_solid_rectangle(clip_and_scroll,
+                                                        &item.rect(),
+                                                        item.clip_region(),
+                                                        &info.color,
+                                                        PrimitiveFlags::None);
+                }
+            }
+            SpecificDisplayItem::Gradient(ref info) => {
+                context.builder.add_gradient(clip_and_scroll,
+                                             item.rect(),
+                                             item.clip_region(),
+                                             info.gradient.start_point,
+                                             info.gradient.end_point,
+                                             item.gradient_stops(),
+                                             item.display_list()
+                                                 .get(item.gradient_stops()).count(),
+                                             info.gradient.extend_mode,
+                                             info.tile_size,
+                                             info.tile_spacing);
+            }
+            SpecificDisplayItem::RadialGradient(ref info) => {
+                context.builder.add_radial_gradient(clip_and_scroll,
+                                                    item.rect(),
+                                                    item.clip_region(),
+                                                    info.gradient.start_center,
+                                                    info.gradient.start_radius,
+                                                    info.gradient.end_center,
+                                                    info.gradient.end_radius,
+                                                    info.gradient.ratio_xy,
+                                                    item.gradient_stops(),
+                                                    info.gradient.extend_mode,
+                                                    info.tile_size,
+                                                    info.tile_spacing);
+            }
+            SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
+                context.builder.add_box_shadow(clip_and_scroll,
+                                               &box_shadow_info.box_bounds,
+                                               item.clip_region(),
+                                               &box_shadow_info.offset,
+                                               &box_shadow_info.color,
+                                               box_shadow_info.blur_radius,
+                                               box_shadow_info.spread_radius,
+                                               box_shadow_info.border_radius,
+                                               box_shadow_info.clip_mode);
+            }
+            SpecificDisplayItem::Border(ref info) => {
+                context.builder.add_border(clip_and_scroll,
+                                           item.rect(),
+                                           item.clip_region(),
+                                           info,
+                                           item.gradient_stops(),
+                                           item.display_list()
+                                               .get(item.gradient_stops()).count());
+            }
+            SpecificDisplayItem::PushStackingContext(ref info) => {
+                let mut subtraversal = item.sub_iter();
+                self.flatten_stacking_context(&mut subtraversal,
+                                              pipeline_id,
+                                              context,
+                                              item.clip_and_scroll().scroll_node_id,
+                                              reference_frame_relative_offset,
+                                              &item.rect(),
+                                              &info.stacking_context,
+                                              item.filters());
+                return Some(subtraversal);
+            }
+            SpecificDisplayItem::Iframe(ref info) => {
+                self.flatten_iframe(info.pipeline_id,
+                                    clip_and_scroll.scroll_node_id,
+                                    &item.rect(),
+                                    context,
+                                    reference_frame_relative_offset);
+            }
+            SpecificDisplayItem::Clip(ref info) => {
+                let content_rect = &item.rect().translate(&reference_frame_relative_offset);
+                self.flatten_clip(context,
+                                  pipeline_id,
+                                  clip_and_scroll.scroll_node_id,
+                                  &info,
+                                  &content_rect,
+                                  item.clip_region());
+            }
+
+            // Do nothing; these are dummy items for the display list parser
+            SpecificDisplayItem::SetGradientStops | SpecificDisplayItem::SetClipRegion(_) => { }
+
+            SpecificDisplayItem::PopStackingContext =>
+                unreachable!("Should have returned in parent method."),
+        }
+        None
+    }
+
+    fn flatten_root<'a>(&mut self,
+                        traversal: &mut BuiltDisplayListIter<'a>,
+                        pipeline_id: PipelineId,
+                        context: &mut FlattenContext,
+                        content_size: &LayoutSize) {
+        let root_bounds = LayerRect::new(LayerPoint::zero(), *content_size);
+        context.builder.push_stacking_context(&LayerPoint::zero(),
+                                              pipeline_id,
+                                              CompositeOps::default(),
+                                              root_bounds,
+                                              TransformStyle::Flat);
+
+        // We do this here, rather than above because we want any of the top-level
+        // stacking contexts in the display list to be treated like root stacking contexts.
+        // FIXME(mrobinson): Currently only the first one will, which for the moment is
+        // sufficient for all our use cases.
+        context.builder.notify_waiting_for_root_stacking_context();
+
+        // For the root pipeline, there's no need to add a full screen rectangle
+        // here, as it's handled by the framebuffer clear.
+        let clip_id = ClipId::root_scroll_node(pipeline_id);
+        if 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 {
+                    context.builder.add_solid_rectangle(ClipAndScrollInfo::simple(clip_id),
+                                                        &root_bounds,
+                                                        &ClipRegion::simple(&root_bounds),
+                                                        &bg_color,
+                                                        PrimitiveFlags::None);
+                }
+            }
+        }
+
+
+        self.flatten_items(traversal, pipeline_id, context, LayerPoint::zero());
+
+        if self.frame_builder_config.enable_scrollbars {
+            let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
+            context.builder.add_solid_rectangle(
+                ClipAndScrollInfo::simple(clip_id),
+                &scrollbar_rect,
+                &ClipRegion::simple(&scrollbar_rect),
+                &DEFAULT_SCROLLBAR_COLOR,
+                PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0));
+        }
+
+        context.builder.pop_stacking_context();
+    }
+
     fn flatten_items<'a>(&mut self,
                          traversal: &mut BuiltDisplayListIter<'a>,
                          pipeline_id: PipelineId,
                          context: &mut FlattenContext,
-                         reference_frame_relative_offset: LayerPoint,
-                         level: i32) {
+                         reference_frame_relative_offset: LayerPoint) {
+        loop {
+            let subtraversal = {
+                let item = match traversal.next() {
+                    Some(item) => item,
+                    None => break,
+                };
 
-        // All this continue_traversal stuff is a big borrowck hack ;_;
-        // Non-lexical lifetimes should fix it.
-        //
-        // Basic idea: when we recurse, we make an independent traversal, then assign
-        // it to this variable to be swapped in at the start of the loop. We can't
-        // while-let because we need a brief period where the item doesn't exist.
-        let mut continue_traversal: Option<BuiltDisplayListIter<'a>> = None;
-        loop {
-            if let Some(trav) = continue_traversal.take() {
-                *traversal = trav;
-            }
-            let item = match traversal.next() {
-                Some(item) => item,
-                None => break,
+                if SpecificDisplayItem::PopStackingContext == *item.item() {
+                    return;
+                }
+
+                self.flatten_item(item, pipeline_id, context, reference_frame_relative_offset)
             };
 
-            let mut clip_and_scroll = item.clip_and_scroll();
-            clip_and_scroll.scroll_node_id =
-                context.clip_id_with_replacement(clip_and_scroll.scroll_node_id);
-            match *item.item() {
-                SpecificDisplayItem::WebGL(ref info) => {
-                    context.builder.add_webgl_rectangle(clip_and_scroll,
-                                                        item.rect(),
-                                                        item.clip_region(),
-                                                        info.context_id);
-                }
-                SpecificDisplayItem::Image(ref info) => {
-                    let image = context.resource_cache.get_image_properties(info.image_key);
-                    if let Some(tile_size) = image.tiling {
-                        // The image resource is tiled. We have to generate an image primitive
-                        // for each tile.
-                        let image_size = DeviceUintSize::new(image.descriptor.width, image.descriptor.height);
-                        self.decompose_image(clip_and_scroll,
-                                             context,
-                                             &item.rect(),
-                                             item.clip_region(),
-                                             info,
-                                             image_size,
-                                             tile_size as u32);
-                    } else {
-                        context.builder.add_image(clip_and_scroll,
-                                                  item.rect(),
-                                                  item.clip_region(),
-                                                  &info.stretch_size,
-                                                  &info.tile_spacing,
-                                                  None,
-                                                  info.image_key,
-                                                  info.image_rendering,
-                                                  None);
-                    }
-                }
-                SpecificDisplayItem::YuvImage(ref info) => {
-                    context.builder.add_yuv_image(clip_and_scroll,
-                                                  item.rect(),
-                                                  item.clip_region(),
-                                                  info.yuv_data,
-                                                  info.color_space);
-                }
-                SpecificDisplayItem::Text(ref text_info) => {
-                    context.builder.add_text(clip_and_scroll,
-                                             item.rect(),
-                                             item.clip_region(),
-                                             text_info.font_key,
-                                             text_info.size,
-                                             text_info.blur_radius,
-                                             &text_info.color,
-                                             item.glyphs(),
-                                             item.display_list().get(item.glyphs()).count(),
-                                             text_info.glyph_options);
-                }
-                SpecificDisplayItem::Rectangle(ref info) => {
-                    let display_list = context.scene.display_lists
-                                              .get(&pipeline_id)
-                                              .expect("No display list?!");
-                    // Try to extract the opaque inner rectangle out of the clipped primitive.
-                    if let Some(opaque_rect) = clip_intersection(&item.rect(), item.clip_region(), display_list) {
-                        let mut results = Vec::new();
-                        subtract_rect(&item.rect(), &opaque_rect, &mut results);
-                        // The inner rectangle is considered opaque within this layer.
-                        // It may still inherit some masking from the clip stack.
-                        context.builder.add_solid_rectangle(clip_and_scroll,
-                                                            &opaque_rect,
-                                                            &ClipRegion::simple(&item.clip_region().main),
-                                                            &info.color,
-                                                            PrimitiveFlags::None);
-                        for transparent_rect in &results {
-                            context.builder.add_solid_rectangle(clip_and_scroll,
-                                                                transparent_rect,
-                                                                item.clip_region(),
-                                                                &info.color,
-                                                                PrimitiveFlags::None);
-                        }
-                    } else {
-                        context.builder.add_solid_rectangle(clip_and_scroll,
-                                                            &item.rect(),
-                                                            item.clip_region(),
-                                                            &info.color,
-                                                            PrimitiveFlags::None);
-                    }
-                }
-                SpecificDisplayItem::Gradient(ref info) => {
-                    context.builder.add_gradient(clip_and_scroll,
-                                                 item.rect(),
-                                                 item.clip_region(),
-                                                 info.gradient.start_point,
-                                                 info.gradient.end_point,
-                                                 item.gradient_stops(),
-                                                 item.display_list()
-                                                     .get(item.gradient_stops()).count(),
-                                                 info.gradient.extend_mode,
-                                                 info.tile_size,
-                                                 info.tile_spacing);
-                }
-                SpecificDisplayItem::RadialGradient(ref info) => {
-                    context.builder.add_radial_gradient(clip_and_scroll,
-                                                        item.rect(),
-                                                        item.clip_region(),
-                                                        info.gradient.start_center,
-                                                        info.gradient.start_radius,
-                                                        info.gradient.end_center,
-                                                        info.gradient.end_radius,
-                                                        info.gradient.ratio_xy,
-                                                        item.gradient_stops(),
-                                                        info.gradient.extend_mode,
-                                                        info.tile_size,
-                                                        info.tile_spacing);
-                }
-                SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
-                    context.builder.add_box_shadow(clip_and_scroll,
-                                                   &box_shadow_info.box_bounds,
-                                                   item.clip_region(),
-                                                   &box_shadow_info.offset,
-                                                   &box_shadow_info.color,
-                                                   box_shadow_info.blur_radius,
-                                                   box_shadow_info.spread_radius,
-                                                   box_shadow_info.border_radius,
-                                                   box_shadow_info.clip_mode);
-                }
-                SpecificDisplayItem::Border(ref info) => {
-
-                    context.builder.add_border(clip_and_scroll,
-                                               item.rect(),
-                                               item.clip_region(),
-                                               info,
-                                               item.gradient_stops(),
-                                               item.display_list()
-                                                   .get(item.gradient_stops()).count());
-                }
-                SpecificDisplayItem::PushStackingContext(ref info) => {
-                    let mut subtraversal = item.sub_iter();
-                    self.flatten_stacking_context(&mut subtraversal,
-                                                  pipeline_id,
-                                                  context,
-                                                  item.clip_and_scroll().scroll_node_id,
-                                                  reference_frame_relative_offset,
-                                                  level + 1,
-                                                  &item.rect(),
-                                                  &info.stacking_context,
-                                                  item.filters());
-                    continue_traversal = Some(subtraversal);
-                }
-                SpecificDisplayItem::Iframe(ref info) => {
-                    self.flatten_iframe(info.pipeline_id,
-                                        clip_and_scroll.scroll_node_id,
-                                        &item.rect(),
-                                        context,
-                                        reference_frame_relative_offset);
-                }
-                SpecificDisplayItem::Clip(ref info) => {
-                    let content_rect = &item.rect().translate(&reference_frame_relative_offset);
-                    self.flatten_clip(context,
-                                      pipeline_id,
-                                      clip_and_scroll.scroll_node_id,
-                                      &info,
-                                      &content_rect,
-                                      item.clip_region());
-                }
-                SpecificDisplayItem::PopStackingContext => return,
-                SpecificDisplayItem::SetGradientStops | SpecificDisplayItem::SetClipRegion(_) => {
-                    // Do nothing; these are dummy items for the display list parser
-                }
+            // If flatten_item created a sub-traversal, we need `traversal` to have the
+            // same state as the completed subtraversal, so we reinitialize it here.
+            if let Some(subtraversal) = subtraversal {
+                *traversal = subtraversal;
             }
         }
     }
 
     /// Decomposes an image display item that is repeated into an image per individual repetition.
     /// We need to do this when we are unable to perform the repetition in the shader,
     /// for example if the image is tiled.
     ///
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -10,21 +10,22 @@ use mask_cache::{ClipMode, ClipSource, M
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{GradientPrimitiveCpu, GradientPrimitiveGpu, ImagePrimitiveCpu, ImagePrimitiveGpu};
 use prim_store::{ImagePrimitiveKind, PrimitiveContainer, PrimitiveGeometry, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu, RadialGradientPrimitiveGpu};
 use prim_store::{RectanglePrimitive, SplitGeometry, TextRunPrimitiveCpu, TextRunPrimitiveGpu};
 use prim_store::{BoxShadowPrimitiveGpu, TexelRect, YuvImagePrimitiveCpu, YuvImagePrimitiveGpu};
 use profiler::{FrameProfileCounters, TextureCacheProfileCounters};
 use render_task::{AlphaRenderItem, MaskCacheKey, MaskResult, RenderTask, RenderTaskIndex};
-use render_task::RenderTaskLocation;
+use render_task::{RenderTaskId, RenderTaskLocation};
 use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
+use std::collections::HashMap;
 use euclid::{SideOffsets2D, TypedPoint3D};
 use tiling::{ContextIsolation, StackingContextIndex};
 use tiling::{ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, DisplayListMap, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, subtract_rect, recycle_vec};
 use util::RectHelpers;
 use webrender_traits::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo};
@@ -83,36 +84,41 @@ impl ImageBorderSegment {
     }
 }
 
 /// Construct a polygon from stacking context boundaries.
 /// `anchor` here is an index that's going to be preserved in all the
 /// splits of the polygon.
 fn make_polygon(sc: &StackingContext, node: &ClipScrollNode, anchor: usize)
                 -> Polygon<f32, WorldPixel> {
-    let mut bounds = sc.local_bounds;
-    bounds.origin = bounds.origin + sc.reference_frame_offset;
+    //TODO: only work with `sc.local_bounds` worth of space
+    // This can be achieved by moving the `sc.local_bounds.origin` shift
+    // from the primitive local coordinates into the layer transformation.
+    // Which in turn needs it to be a render task property obeyed by all primitives
+    // upon rendering, possibly not limited to `write_*_vertex` implementations.
+    let size = sc.local_bounds.bottom_right();
+    let bounds = LayerRect::new(sc.reference_frame_offset, LayerSize::new(size.x, size.y));
     Polygon::from_transformed_rect(bounds, node.world_content_transform, anchor)
 }
 
 #[derive(Clone, Copy)]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
-    pub enable_subpixel_aa: bool,
+    pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
 }
 
 impl FrameBuilderConfig {
     pub fn new(enable_scrollbars: bool,
-               enable_subpixel_aa: bool,
+               default_font_render_mode: FontRenderMode,
                debug: bool)
                -> FrameBuilderConfig {
         FrameBuilderConfig {
             enable_scrollbars: enable_scrollbars,
-            enable_subpixel_aa: enable_subpixel_aa,
+            default_font_render_mode: default_font_render_mode,
             debug: debug,
         }
     }
 }
 
 pub struct FrameBuilder {
     screen_size: DeviceUintSize,
     background_color: Option<ColorF>,
@@ -128,16 +134,20 @@ pub struct FrameBuilder {
 
     /// A stack of scroll nodes used during display list processing to properly
     /// parent new scroll nodes.
     reference_frame_stack: Vec<ClipId>,
 
     /// A stack of stacking contexts used for creating ClipScrollGroups as
     /// primitives are added to the frame.
     stacking_context_stack: Vec<StackingContextIndex>,
+
+    /// Whether or not we've pushed a root stacking context for the current pipeline.
+    has_root_stacking_context: bool,
+
 }
 
 impl FrameBuilder {
     pub fn new(previous: Option<FrameBuilder>,
                screen_size: DeviceUintSize,
                background_color: Option<ColorF>,
                config: FrameBuilderConfig) -> FrameBuilder {
         match previous {
@@ -149,31 +159,33 @@ impl FrameBuilder {
                     packed_layers: recycle_vec(prev.packed_layers),
                     scrollbar_prims: recycle_vec(prev.scrollbar_prims),
                     reference_frame_stack: recycle_vec(prev.reference_frame_stack),
                     stacking_context_stack: recycle_vec(prev.stacking_context_stack),
                     prim_store: prev.prim_store.recycle(),
                     screen_size: screen_size,
                     background_color: background_color,
                     config: config,
+                    has_root_stacking_context: false,
                 }
             }
             None => {
                 FrameBuilder {
                     stacking_context_store: Vec::new(),
                     clip_scroll_group_store: Vec::new(),
                     cmds: Vec::new(),
                     packed_layers: Vec::new(),
                     scrollbar_prims: Vec::new(),
                     reference_frame_stack: Vec::new(),
                     stacking_context_stack: Vec::new(),
                     prim_store: PrimitiveStore::new(),
                     screen_size: screen_size,
                     background_color: background_color,
                     config: config,
+                    has_root_stacking_context: false,
                 }
             }
         }
     }
 
     pub fn create_clip_scroll_group_if_necessary(&mut self,
                                                  stacking_context_index: StackingContextIndex,
                                                  info: ClipAndScrollInfo) {
@@ -245,20 +257,23 @@ impl FrameBuilder {
             clip_node_id: info.clip_node_id(),
             packed_layer_index: packed_layer_index,
             screen_bounding_rect: None,
          });
 
         ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, info)
     }
 
+    pub fn notify_waiting_for_root_stacking_context(&mut self) {
+        self.has_root_stacking_context = false;
+    }
+
     pub fn push_stacking_context(&mut self,
                                  reference_frame_offset: &LayerPoint,
                                  pipeline_id: PipelineId,
-                                 is_page_root: bool,
                                  composite_ops: CompositeOps,
                                  local_bounds: LayerRect,
                                  transform_style: TransformStyle) {
         if let Some(parent_index) = self.stacking_context_stack.last() {
             let parent_is_root = self.stacking_context_store[parent_index.0].is_page_root;
 
             if composite_ops.mix_blend_mode.is_some() && !parent_is_root {
                 // the parent stacking context of a stacking context with mix-blend-mode
@@ -271,21 +286,22 @@ impl FrameBuilder {
                 *isolation = ContextIsolation::Full;
             }
         }
 
         let stacking_context_index = StackingContextIndex(self.stacking_context_store.len());
         let reference_frame_id = self.current_reference_frame_id();
         self.stacking_context_store.push(StackingContext::new(pipeline_id,
                                                               *reference_frame_offset,
-                                                              is_page_root,
+                                                              !self.has_root_stacking_context,
                                                               reference_frame_id,
                                                               local_bounds,
                                                               transform_style,
                                                               composite_ops));
+        self.has_root_stacking_context = true;
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
         self.stacking_context_stack.push(stacking_context_index);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
         self.stacking_context_stack.pop();
     }
@@ -720,38 +736,40 @@ impl FrameBuilder {
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
                     rect: LayerRect,
                     clip_region: &ClipRegion,
                     font_key: FontKey,
                     size: Au,
-                    blur_radius: Au,
+                    blur_radius: f32,
                     color: &ColorF,
                     glyph_range: ItemRange<GlyphInstance>,
                     glyph_count: usize,
                     glyph_options: Option<GlyphOptions>) {
         if color.a == 0.0 {
             return
         }
 
         if size.0 <= 0 {
             return
         }
 
         // TODO(gw): Use a proper algorithm to select
         // whether this item should be rendered with
         // subpixel AA!
-        let render_mode = if blur_radius == Au(0) &&
-                             self.config.enable_subpixel_aa {
-            FontRenderMode::Subpixel
-        } else {
-            FontRenderMode::Alpha
-        };
+        let mut render_mode = self.config.default_font_render_mode;
+
+        // If we're using sub-pixel AA by default, but we are
+        // doing a text-blur, we need to force alpha AA for the blur
+        // to produce correct results.
+        if render_mode == FontRenderMode::Subpixel && blur_radius != 0.0 {
+            render_mode = FontRenderMode::Alpha;
+        }
 
         let prim_cpu = TextRunPrimitiveCpu {
             font_key: font_key,
             logical_font_size: size,
             blur_radius: blur_radius,
             glyph_range: glyph_range,
             glyph_count: glyph_count,
             cache_dirty: true,
@@ -845,17 +863,17 @@ impl FrameBuilder {
             self.add_solid_rectangle(clip_and_scroll,
                                      box_bounds,
                                      clip_region,
                                      color,
                                      PrimitiveFlags::None);
             return;
         }
 
-        if blur_radius == 0.0 && spread_radius == 0.0 && border_radius != 0.0 {
+        if blur_radius == 0.0 && border_radius != 0.0 {
             self.fill_box_shadow_rect(clip_and_scroll,
                                       box_bounds,
                                       bs_rect,
                                       clip_region,
                                       color,
                                       border_radius,
                                       clip_mode);
             return;
@@ -928,16 +946,17 @@ impl FrameBuilder {
                     self.add_solid_rectangle(clip_and_scroll,
                                              rect,
                                              clip_region,
                                              color,
                                              PrimitiveFlags::None)
                 }
             }
             BoxShadowKind::Shadow(rects) => {
+                assert!(blur_radius > 0.0);
                 if clip_mode == BoxShadowClipMode::Inset {
                     self.fill_box_shadow_rect(clip_and_scroll,
                                               box_bounds,
                                               bs_rect,
                                               clip_region,
                                               color,
                                               border_radius,
                                               clip_mode);
@@ -1043,16 +1062,18 @@ impl FrameBuilder {
                          clip_region: &ClipRegion,
                          yuv_data: YuvData,
                          color_space: YuvColorSpace) {
         let format = yuv_data.get_format();
         let yuv_key = match yuv_data {
             YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::new(0, 0)],
             YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) =>
                 [plane_0, plane_1, plane_2],
+            YuvData::InterleavedYCbCr(plane_0) =>
+                [plane_0, ImageKey::new(0, 0), ImageKey::new(0, 0)],
         };
 
         let prim_cpu = YuvImagePrimitiveCpu {
             yuv_key: yuv_key,
             yuv_texture_id: [SourceTexture::Invalid, SourceTexture::Invalid, SourceTexture::Invalid],
             yuv_resource_address: GpuStoreAddress(0),
             format: format,
             color_space: color_space,
@@ -1133,124 +1154,95 @@ impl FrameBuilder {
 
     fn build_render_task(&mut self, clip_scroll_tree: &ClipScrollTree)
                          -> (RenderTask, usize) {
         profile_scope!("build_render_task");
 
         let mut next_z = 0;
         let mut next_task_index = RenderTaskIndex(0);
 
-        let mut sc_stack = Vec::new();
+        let mut sc_stack: Vec<StackingContextIndex> = Vec::new();
         let mut current_task = RenderTask::new_alpha_batch(next_task_index,
                                                            DeviceIntPoint::zero(),
                                                            RenderTaskLocation::Fixed);
         next_task_index.0 += 1;
         // A stack of the alpha batcher tasks. We create them on the way down,
         // and then actually populate with items and dependencies on the way up.
         let mut alpha_task_stack = Vec::new();
-        // The stack of "preserve-3d" contexts. We are baking these into render targets
-        // and onlu compositing once we are out of "preserve-3d" hierarchy.
-        // That is why we stack those contexts.
-        let mut preserve_3d_stack = Vec::new();
+        // A map of "preserve-3d" contexts. We are baking these into render targets
+        // and only compositing once we are out of "preserve-3d" hierarchy.
+        // The stacking contexts that fall into this category are
+        //  - ones with `ContextIsolation::Items`, for their actual items to be backed
+        //  - immediate children of `ContextIsolation::Items`
+        let mut preserve_3d_map: HashMap<StackingContextIndex, RenderTask> = HashMap::new();
         // The plane splitter, using a simple BSP tree.
         let mut splitter = BspSplitter::new();
 
         self.prim_store.gpu_split_geometry.clear();
+        debug!("build_render_task()");
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
+                    let parent_isolation = sc_stack.last()
+                                                   .map(|index| self.stacking_context_store[index.0].isolation);
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
                     sc_stack.push(stacking_context_index);
 
                     if !stacking_context.is_visible {
                         continue;
                     }
 
+                    debug!("\tpush {:?} {:?}", stacking_context_index, stacking_context.isolation);
+
                     let stacking_context_rect = &stacking_context.screen_bounds;
                     let composite_count = stacking_context.composite_ops.count();
 
-                    let new_task = match stacking_context.isolation {
-                        ContextIsolation::Items => Some(
-                            RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect)
-                        ),
-                        ContextIsolation::Full if composite_count == 0 => Some(
-                            RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect)
-                        ),
-                        ContextIsolation::Full | ContextIsolation::None => None,
-                    };
+                    if stacking_context.isolation == ContextIsolation::Full && composite_count == 0 {
+                        alpha_task_stack.push(current_task);
+                        current_task = RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect);
+                        next_task_index.0 += 1;
+                    }
 
-                    if let Some(task) = new_task {
+                    if parent_isolation == Some(ContextIsolation::Items) ||
+                       stacking_context.isolation == ContextIsolation::Items {
+                        alpha_task_stack.push(current_task);
+                        current_task = RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect);
                         next_task_index.0 += 1;
-                        let prev_task = mem::replace(&mut current_task, task);
-                        alpha_task_stack.push(prev_task);
+                        //Note: technically, we shouldn't make a new alpha task for "preserve-3d" contexts
+                        // that have no child items (only other stacking contexts). However, we don't know if
+                        // there are any items at this time (in `PushStackingContext`).
+                        //Note: the reason we add the polygon for splitting during `Push*` as opposed to `Pop*`
+                        // is because we need to preserve the order of drawing for planes that match together.
+                        let scroll_node = clip_scroll_tree.nodes.get(&stacking_context.reference_frame_id).unwrap();
+                        let sc_polygon = make_polygon(stacking_context, scroll_node, stacking_context_index.0);
+                        debug!("\tadd {:?} -> {:?}", stacking_context_index, sc_polygon);
+                        splitter.add(sc_polygon);
                     }
 
                     for _ in 0..composite_count {
-                        let new_task = RenderTask::new_dynamic_alpha_batch(next_task_index,
-                                                                           stacking_context_rect);
+                        alpha_task_stack.push(current_task);
+                        current_task = RenderTask::new_dynamic_alpha_batch(next_task_index, stacking_context_rect);
                         next_task_index.0 += 1;
-                        let prev_task = mem::replace(&mut current_task, new_task);
-                        alpha_task_stack.push(prev_task);
                     }
                 }
                 PrimitiveRunCmd::PopStackingContext => {
                     let stacking_context_index = sc_stack.pop().unwrap();
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
+                    let composite_count = stacking_context.composite_ops.count();
 
                     if !stacking_context.is_visible {
                         continue;
                     }
 
-                    // Handle the `Item` isolation type first. Once we are out of an isolated
-                    // sub-tree of stacking contexts, we do plane splitting and compositing.
-                    match stacking_context.isolation {
-                        ContextIsolation::Items => {
-                            let prev_task = alpha_task_stack.pop().unwrap();
-                            let mut old_current = mem::replace(&mut current_task, prev_task);
-                            // If there are any items or non-preserve-3d sub-contexts, we have the
-                            // contents to bake and plane split. Note: if the `old_current` is also preserve-3d,
-                            // means we are inside the `ContextIsolation::Items` sub-tree, which only
-                            // gets added as task dependencies on exit (see the next match arm).
-                            if !old_current.as_alpha_batch().items.is_empty() || !old_current.children.is_empty() {
-                                let stacking_context = &self.stacking_context_store[stacking_context_index.0];
-                                let scroll_node = clip_scroll_tree.nodes.get(&stacking_context.reference_frame_id).unwrap();
-                                let sc_polygon = make_polygon(stacking_context, scroll_node, preserve_3d_stack.len());
-                                splitter.add(sc_polygon);
-                                preserve_3d_stack.push((stacking_context_index, old_current));
-                            } else if !old_current.children.is_empty() {
-                                current_task.children.push(old_current);
-                            }
-                        },
-                        ContextIsolation::None | ContextIsolation::Full => {
-                            // We are back from a "preserve-3d" sub-domain.
-                            // Time to split those stacking context planes.
-                            current_task.children.extend(preserve_3d_stack.iter().map(|&(_, ref task)| task.clone()));
-                            for poly in splitter.sort(TypedPoint3D::new(0.0, 0.0, -1.0)) {
-                                let (sc_index, ref task) = preserve_3d_stack[poly.anchor];
-                                let pp = &poly.points;
-                                let split_geo = SplitGeometry {
-                                    data: [pp[0].x, pp[0].y, pp[0].z,
-                                           pp[1].x, pp[1].y, pp[1].z,
-                                           pp[2].x, pp[2].y, pp[2].z,
-                                           pp[3].x, pp[3].y, pp[3].z],
-                                };
-                                let gpu_index = self.prim_store.gpu_split_geometry.push(split_geo);
-                                let item = AlphaRenderItem::SplitComposite(sc_index, task.id, gpu_index, next_z);
-                                current_task.as_alpha_batch().items.push(item);
-                            }
-                            splitter.reset();
-                            preserve_3d_stack.clear();
-                            next_z += 1;
-                        },
-                    }
+                    debug!("\tpop {:?}", stacking_context_index);
+                    let parent_isolation = sc_stack.last()
+                                                   .map(|index| self.stacking_context_store[index.0].isolation);
 
-                    let composite_count = stacking_context.composite_ops.count();
-
-                    if composite_count == 0 && stacking_context.isolation == ContextIsolation::Full {
+                    if stacking_context.isolation == ContextIsolation::Full && composite_count == 0 {
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let item = AlphaRenderItem::HardwareComposite(stacking_context_index,
                                                                       current_task.id,
                                                                       HardwareCompositeOp::PremultipliedAlpha,
                                                                       next_z);
                         next_z += 1;
                         prev_task.as_alpha_batch().items.push(item);
                         prev_task.children.push(current_task);
@@ -1281,32 +1273,69 @@ impl FrameBuilder {
                                                               mix_blend_mode,
                                                               next_z);
                         next_z += 1;
                         prev_task.as_alpha_batch().items.push(item);
                         prev_task.children.push(current_task);
                         prev_task.children.push(readback_task);
                         current_task = prev_task;
                     }
+
+                    if parent_isolation == Some(ContextIsolation::Items) ||
+                       stacking_context.isolation == ContextIsolation::Items {
+                        //Note: we don't register the dependent tasks here. It's only done
+                        // when we are out of the `preserve-3d` branch (see the code below),
+                        // since this is only where the parent task is known.
+                        preserve_3d_map.insert(stacking_context_index, current_task);
+                        current_task = alpha_task_stack.pop().unwrap();
+                    }
+
+                    if !preserve_3d_map.is_empty() && parent_isolation != Some(ContextIsolation::Items) {
+                        // Flush the accumulated plane splits onto the task tree.
+                        // Notice how this is done before splitting in order to avoid duplicate tasks.
+                        current_task.children.extend(preserve_3d_map.values().cloned());
+                        debug!("\tplane splitting in {:?}", current_task.id);
+                        // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
+                        for poly in splitter.sort(TypedPoint3D::new(0.0, 0.0, 1.0)) {
+                            let sc_index = StackingContextIndex(poly.anchor);
+                            let task_id = preserve_3d_map[&sc_index].id;
+                            debug!("\t\tproduce {:?} -> {:?} for {:?}", sc_index, poly, task_id);
+                            let pp = &poly.points;
+                            let split_geo = SplitGeometry {
+                                data: [pp[0].x, pp[0].y, pp[0].z,
+                                       pp[1].x, pp[1].y, pp[1].z,
+                                       pp[2].x, pp[2].y, pp[2].z,
+                                       pp[3].x, pp[3].y, pp[3].z],
+                            };
+                            let gpu_index = self.prim_store.gpu_split_geometry.push(split_geo);
+                            let item = AlphaRenderItem::SplitComposite(sc_index, task_id, gpu_index, next_z);
+                            current_task.as_alpha_batch().items.push(item);
+                        }
+                        splitter.reset();
+                        preserve_3d_map.clear();
+                        next_z += 1;
+                    }
                 }
                 PrimitiveRunCmd::PrimitiveRun(first_prim_index, prim_count, clip_and_scroll) => {
                     let stacking_context_index = *sc_stack.last().unwrap();
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
 
                     if !stacking_context.is_visible {
                         continue;
                     }
 
                     let stacking_context_index = *sc_stack.last().unwrap();
                     let group_index = self.stacking_context_store[stacking_context_index.0]
                                           .clip_scroll_group(clip_and_scroll);
                     if self.clip_scroll_group_store[group_index.0].screen_bounding_rect.is_none() {
                         continue
                     }
 
+                    debug!("\trun of {} items into {:?}", prim_count, current_task.id);
+
                     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 {
@@ -1321,17 +1350,18 @@ impl FrameBuilder {
                             next_z += 1;
                         }
                     }
                 }
             }
         }
 
         debug_assert!(alpha_task_stack.is_empty());
-        debug_assert!(preserve_3d_stack.is_empty());
+        debug_assert!(preserve_3d_map.is_empty());
+        debug_assert_eq!(current_task.id, RenderTaskId::Static(RenderTaskIndex(0)));
         (current_task, next_task_index.0)
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  frame_id: FrameId,
                  clip_scroll_tree: &mut ClipScrollTree,
                  display_lists: &DisplayListMap,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -56,16 +56,17 @@ mod ellipse;
 mod frame;
 mod frame_builder;
 mod freelist;
 mod geometry;
 mod gpu_store;
 mod internal_types;
 mod mask_cache;
 mod prim_store;
+mod print_tree;
 mod profiler;
 mod record;
 mod render_backend;
 mod render_task;
 mod resource_cache;
 mod scene;
 mod spring;
 mod texture_cache;
@@ -127,16 +128,16 @@ extern crate fnv;
 extern crate gleam;
 extern crate num_traits;
 //extern crate notify;
 extern crate time;
 extern crate webrender_traits;
 #[cfg(feature = "webgl")]
 extern crate offscreen_gl_context;
 extern crate byteorder;
-extern crate threadpool;
+extern crate rayon;
 extern crate plane_split;
 
 #[cfg(any(target_os="macos", target_os="windows"))]
 extern crate gamma_lut;
 
 pub use renderer::{ExternalImage, ExternalImageSource, ExternalImageHandler};
 pub use renderer::{Renderer, RendererOptions};
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -157,17 +157,17 @@ impl MaskCacheInfo {
                     complex_clip_count += region.complex_clip_count;
                     if region_mode == RegionMode::IncludeRect {
                         complex_clip_count += 1;
                     }
                 }
                 ClipSource::BorderCorner(ref source) => {
                     // One block for the corner header, plus one
                     // block per dash to clip out.
-                    let gpu_address = clip_store.alloc(1 + source.dash_count);
+                    let gpu_address = clip_store.alloc(1 + source.max_clip_count);
                     border_corners.push((source.clone(), gpu_address));
                 }
             }
         }
 
         let complex_clip_range = ClipAddressRange {
             start: if complex_clip_count > 0 {
                 clip_store.alloc(CLIP_DATA_GPU_SIZE * complex_clip_count)
@@ -188,17 +188,17 @@ impl MaskCacheInfo {
     }
 
     pub fn update(&mut self,
                   sources: &[ClipSource],
                   transform: &LayerToWorldTransform,
                   clip_store: &mut VertexDataStore<GpuBlock32>,
                   device_pixel_ratio: f32,
                   display_list: &BuiltDisplayList) {
-        let is_aligned = transform.can_losslessly_transform_and_perspective_project_a_2d_rect();
+        let is_aligned = transform.preserves_2d_axis_alignment();
 
         // 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;
@@ -260,20 +260,20 @@ impl MaskCacheInfo {
                             local_inner = local_inner.and_then(|r| clip.get_inner_rect_safe()
                                                                        .and_then(|ref inner| r.intersection(inner)));
                         }
                     }
                     ClipSource::BorderCorner{..} => {}
                 }
             }
 
-            for &(ref source, gpu_address) in &self.border_corners {
+            for &mut (ref mut source, gpu_address) in &mut self.border_corners {
                 has_border_clip = true;
                 let slice = clip_store.get_slice_mut(gpu_address,
-                                                     1 + source.dash_count);
+                                                     1 + source.max_clip_count);
                 source.populate_gpu_data(slice);
             }
 
             // Work out the type of mask geometry we have, based on the
             // list of clip sources above.
             if has_clip_out || has_border_clip {
                 // For clip-out, the mask rect is not known.
                 self.bounds = Some(MaskBounds::None);
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -239,148 +239,147 @@ impl FontContext {
         }
     }
 
     pub fn rasterize_glyph(&mut self,
                            key: &GlyphKey,
                            render_mode: FontRenderMode,
                            _glyph_options: Option<GlyphOptions>)
                            -> Option<RasterizedGlyph> {
-        match self.get_ct_font(key.font_key, key.size) {
-            Some(ref ct_font) => {
-                let glyph = key.index as CGGlyph;
-                let metrics = get_glyph_metrics(ct_font, glyph, &key.subpixel_point);
-                if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
-                    return Some(RasterizedGlyph::blank())
-                }
+
+        let ct_font = match self.get_ct_font(key.font_key, key.size) {
+            Some(font) => font,
+            None => return Some(RasterizedGlyph::blank())
+        };
+
+        let glyph = key.index as CGGlyph;
+        let metrics = get_glyph_metrics(&ct_font, glyph, &key.subpixel_point);
+        if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 {
+            return Some(RasterizedGlyph::blank())
+        }
 
-                let context_flags = match render_mode {
-                    FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
-                    FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
-                };
+        let context_flags = match render_mode {
+            FontRenderMode::Subpixel => kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
+            FontRenderMode::Alpha | FontRenderMode::Mono => kCGImageAlphaPremultipliedLast,
+        };
 
-                let mut cg_context = CGContext::create_bitmap_context(metrics.rasterized_width as usize,
-                                                                      metrics.rasterized_height as usize,
-                                                                      8,
-                                                                      metrics.rasterized_width as usize * 4,
-                                                                      &CGColorSpace::create_device_rgb(),
-                                                                      context_flags);
+        let mut cg_context = CGContext::create_bitmap_context(metrics.rasterized_width as usize,
+                                                              metrics.rasterized_height as usize,
+                                                              8,
+                                                              metrics.rasterized_width as usize * 4,
+                                                              &CGColorSpace::create_device_rgb(),
+                                                              context_flags);
 
 
-                // Tested on mac OS Sierra, 10.12
-                // For Mono + alpha, the only values that matter are the alpha values.
-                // For subpixel, we need each individual rgb channel.
-                // CG has two individual glyphs for subpixel AA (pre-10.11, this is not true):
-                // 1) black text on white opaque background
-                // 2) white text on black opaque background
-                // Gecko does (1). Note, the BG must be opaque for subpixel AA to work.
-                // See https://bugzilla.mozilla.org/show_bug.cgi?id=1230366#c35
-                //
-                // For grayscale / mono, CG still produces two glyphs, but it doesn't matter
-                // 1) black text on transparent white - only alpha values filled
-                // 2) white text on transparent black - channels == alpha
-                //
-                // If we draw grayscale/mono on an opaque background
-                // the RGB channels are the alpha values from transparent backgrounds
-                // with the alpha set as opaque.
-                // At the end of all this, WR expects individual RGB channels and ignores alpha
-                // for subpixel AA.
-                // For alpha/mono, WR ignores all channels other than alpha.
-                // Also note that WR expects text to be black bg with white text, so invert
-                // when we draw the glyphs.
-                let (antialias, smooth) = match render_mode {
-                    FontRenderMode::Subpixel => (true, true),
-                    FontRenderMode::Alpha => (true, false),
-                    FontRenderMode::Mono => (false, false),
-                };
+        // Tested on mac OS Sierra, 10.12
+        // For Mono + alpha, the only values that matter are the alpha values.
+        // For subpixel, we need each individual rgb channel.
+        // CG has two individual glyphs for subpixel AA (pre-10.11, this is not true):
+        // 1) black text on white opaque background
+        // 2) white text on black opaque background
+        // Gecko does (1). Note, the BG must be opaque for subpixel AA to work.
+        // See https://bugzilla.mozilla.org/show_bug.cgi?id=1230366#c35
+        //
+        // For grayscale / mono, CG still produces two glyphs, but it doesn't matter
+        // 1) black text on transparent white - only alpha values filled
+        // 2) white text on transparent black - channels == alpha
+        //
+        // If we draw grayscale/mono on an opaque background
+        // the RGB channels are the alpha values from transparent backgrounds
+        // with the alpha set as opaque.
+        // At the end of all this, WR expects individual RGB channels and ignores alpha
+        // for subpixel AA.
+        // For alpha/mono, WR ignores all channels other than alpha.
+        // Also note that WR expects text to be black bg with white text, so invert
+        // when we draw the glyphs.
+        let (antialias, smooth) = match render_mode {
+            FontRenderMode::Subpixel => (true, true),
+            FontRenderMode::Alpha => (true, false),
+            FontRenderMode::Mono => (false, false),
+        };
 
-                // These are always true in Gecko, even for non-AA fonts
-                cg_context.set_allows_font_subpixel_positioning(true);
-                cg_context.set_should_subpixel_position_fonts(true);
+        // These are always true in Gecko, even for non-AA fonts
+        cg_context.set_allows_font_subpixel_positioning(true);
+        cg_context.set_should_subpixel_position_fonts(true);
 
-                // Don't quantize because we're doing it already.
-                cg_context.set_allows_font_subpixel_quantization(false);
-                cg_context.set_should_subpixel_quantize_fonts(false);
+        // Don't quantize because we're doing it already.
+        cg_context.set_allows_font_subpixel_quantization(false);
+        cg_context.set_should_subpixel_quantize_fonts(false);
 
-                cg_context.set_allows_font_smoothing(smooth);
-                cg_context.set_should_smooth_fonts(smooth);
-                cg_context.set_allows_antialiasing(antialias);
-                cg_context.set_should_antialias(antialias);
+        cg_context.set_allows_font_smoothing(smooth);
+        cg_context.set_should_smooth_fonts(smooth);
+        cg_context.set_allows_antialiasing(antialias);
+        cg_context.set_should_antialias(antialias);
 
-                let (x_offset, y_offset) = key.subpixel_point.to_f64();
+        let (x_offset, y_offset) = key.subpixel_point.to_f64();
 
-                // CG Origin is bottom left, WR is top left. Need -y offset
-                let rasterization_origin = CGPoint {
-                    x: -metrics.rasterized_left as f64 + x_offset,
-                    y: metrics.rasterized_descent as f64 - y_offset,
-                };
+        // CG Origin is bottom left, WR is top left. Need -y offset
+        let rasterization_origin = CGPoint {
+            x: -metrics.rasterized_left as f64 + x_offset,
+            y: metrics.rasterized_descent as f64 - y_offset,
+        };
 
-                // Always draw black text on a white background
-                // Fill the background
-                cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
-                let rect = CGRect {
-                    origin: CGPoint {
-                        x: 0.0,
-                        y: 0.0,
-                    },
-                    size: CGSize {
-                        width: metrics.rasterized_width as f64,
-                        height: metrics.rasterized_height as f64,
-                    }
-                };
-                cg_context.fill_rect(rect);
+        // Always draw black text on a white background
+        // Fill the background
+        cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
+        let rect = CGRect {
+            origin: CGPoint {
+                x: 0.0,
+                y: 0.0,
+            },
+            size: CGSize {
+                width: metrics.rasterized_width as f64,
+                height: metrics.rasterized_height as f64,
+            }
+        };
+        cg_context.fill_rect(rect);
 
-                // Set the text color
-                cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
-                cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
-                ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());
+        // Set the text color
+        cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
+        cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill);
+        ct_font.draw_glyphs(&[glyph], &[rasterization_origin], cg_context.clone());
 
-                let mut rasterized_pixels = cg_context.data().to_vec();
+        let mut rasterized_pixels = cg_context.data().to_vec();
 
-                // Convert to linear space for subpixel AA.
-                // We explicitly do not do this for grayscale AA
-                if render_mode == FontRenderMode::Subpixel {
-                    self.gamma_lut.coregraphics_convert_to_linear_bgra(&mut rasterized_pixels,
-                                                                       metrics.rasterized_width as usize,
-                                                                       metrics.rasterized_height as usize);
-                }
+        // Convert to linear space for subpixel AA.
+        // We explicitly do not do this for grayscale AA
+        if render_mode == FontRenderMode::Subpixel {
+            self.gamma_lut.coregraphics_convert_to_linear_bgra(&mut rasterized_pixels,
+                                                               metrics.rasterized_width as usize,
+                                                               metrics.rasterized_height as usize);
+        }
 
-                // We need to invert the pixels back since right now
-                // transparent pixels are actually opaque white.
-                for i in 0..metrics.rasterized_height {
-                    let current_height = (i * metrics.rasterized_width * 4) as usize;
-                    let end_row = current_height + (metrics.rasterized_width as usize * 4);
+        // We need to invert the pixels back since right now
+        // transparent pixels are actually opaque white.
+        for i in 0..metrics.rasterized_height {
+            let current_height = (i * metrics.rasterized_width * 4) as usize;
+            let end_row = current_height + (metrics.rasterized_width as usize * 4);
 
-                    for mut pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
-                        pixel[0] = 255 - pixel[0];
-                        pixel[1] = 255 - pixel[1];
-                        pixel[2] = 255 - pixel[2];
+            for mut pixel in rasterized_pixels[current_height .. end_row].chunks_mut(4) {
+                pixel[0] = 255 - pixel[0];
+                pixel[1] = 255 - pixel[1];
+                pixel[2] = 255 - pixel[2];
 
-                        pixel[3] = match render_mode {
-                            FontRenderMode::Subpixel => 255,
-                            _ => {
-                                assert_eq!(pixel[0], pixel[1]);
-                                assert_eq!(pixel[0], pixel[2]);
-                                pixel[0]
-                            }
-                        }; // end match
-                    } // end row
-                } // end height
+                pixel[3] = match render_mode {
+                    FontRenderMode::Subpixel => 255,
+                    _ => {
+                        assert_eq!(pixel[0], pixel[1]);
+                        assert_eq!(pixel[0], pixel[2]);
+                        pixel[0]
+                    }
+                }; // end match
+            } // end row
+        } // end height
 
-                self.gamma_correct_pixels(&mut rasterized_pixels,
-                                          metrics.rasterized_width as usize,
-                                          metrics.rasterized_height as usize,
-                                          render_mode,
-                                          key.color);
+        self.gamma_correct_pixels(&mut rasterized_pixels,
+                                  metrics.rasterized_width as usize,
+                                  metrics.rasterized_height as usize,
+                                  render_mode,
+                                  key.color);
 
-                Some(RasterizedGlyph {
-                    width: metrics.rasterized_width,
-                    height: metrics.rasterized_height,
-                    bytes: rasterized_pixels,
-                })
-            }
-            None => {
-                Some(RasterizedGlyph::blank())
-            }
-        }
+        Some(RasterizedGlyph {
+            width: metrics.rasterized_width,
+            height: metrics.rasterized_height,
+            bytes: rasterized_pixels,
+        })
     }
 }
 
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -8,19 +8,19 @@ use webrender_traits::{NativeFontHandle,
 use webrender_traits::{GlyphKey};
 
 use freetype::freetype::{FT_Render_Mode, FT_Pixel_Mode};
 use freetype::freetype::{FT_Done_FreeType, FT_Library_SetLcdFilter};
 use freetype::freetype::{FT_Library, FT_Set_Char_Size};
 use freetype::freetype::{FT_Face, FT_Long, FT_UInt, FT_F26Dot6};
 use freetype::freetype::{FT_Init_FreeType, FT_Load_Glyph, FT_Render_Glyph};
 use freetype::freetype::{FT_New_Memory_Face, FT_GlyphSlot, FT_LcdFilter};
-use freetype::freetype::{FT_Done_Face};
+use freetype::freetype::{FT_Done_Face, FT_Error};
 
-use std::{mem, ptr, slice};
+use std::{cmp, mem, ptr, slice};
 use std::collections::HashMap;
 
 struct Face {
     face: FT_Face,
 }
 
 pub struct FontContext {
     lib: FT_Library,
@@ -36,16 +36,18 @@ pub struct RasterizedGlyph {
 fn float_to_fixed(before: usize, f: f64) -> i32 {
     ((1i32 << before) as f64 * f) as i32
 }
 
 fn float_to_fixed_ft(f: f64) -> i32 {
     float_to_fixed(6, f)
 }
 
+const SUCCESS: FT_Error = FT_Error(0);
+
 impl FontContext {
     pub fn new() -> FontContext {
         let mut lib: FT_Library = ptr::null_mut();
         unsafe {
             let result = FT_Init_FreeType(&mut lib);
             assert!(result.succeeded(), "Unable to initialize FreeType library {:?}", result);
 
             // TODO(gw): Check result of this to determine if freetype build supports subpixel.
@@ -94,166 +96,157 @@ impl FontContext {
             assert!(result.succeeded());
         }
     }
 
     fn load_glyph(&self,
                   font_key: FontKey,
                   size: Au,
                   character: u32) -> Option<FT_GlyphSlot> {
+
         debug_assert!(self.faces.contains_key(&font_key));
         let face = self.faces.get(&font_key).unwrap();
+        let char_size = float_to_fixed_ft(size.to_f64_px());
 
-        unsafe {
-            let char_size = float_to_fixed_ft(size.to_f64_px());
-            let result = FT_Set_Char_Size(face.face, char_size as FT_F26Dot6, 0, 0, 0);
-            assert!(result.succeeded());
+        assert_eq!(SUCCESS, unsafe {
+            FT_Set_Char_Size(face.face, char_size as FT_F26Dot6, 0, 0, 0)
+        });
+
+        let result = unsafe {
+            FT_Load_Glyph(face.face, character as FT_UInt, 0)
+        };
 
-            let result =  FT_Load_Glyph(face.face, character as FT_UInt, 0);
-            if result.succeeded() {
-                let void_glyph = (*face.face).glyph;
-                let slot_ptr: FT_GlyphSlot = mem::transmute(void_glyph);
-                assert!(!slot_ptr.is_null());
-                return Some(slot_ptr);
-            }
+        if result == SUCCESS {
+            let slot = unsafe { (*face.face).glyph };
+            assert!(slot != ptr::null_mut());
+            Some(slot)
+        } else {
+            error!("Unable to load glyph for {} of size {:?} from font {:?}, {:?}",
+                character, size, font_key, result);
+            None
         }
-
-        None
     }
 
-     pub fn get_glyph_dimensions(&mut self,
-                                 key: &GlyphKey) -> Option<GlyphDimensions> {
-        self.load_glyph(key.font_key, key.size, key.index).and_then(|slot| {
-            let metrics = unsafe { &(*slot).metrics };
-            if metrics.width == 0 || metrics.height == 0 {
-                None
-            } else {
-                Some(GlyphDimensions {
-                    left: (metrics.horiBearingX >> 6) as i32,
-                    top: (metrics.horiBearingY >> 6) as i32,
-                    width: (metrics.width >> 6) as u32,
-                    height: (metrics.height >> 6) as u32,
-                })
-            }
-        })
+    fn get_glyph_dimensions_impl(slot: FT_GlyphSlot) -> Option<GlyphDimensions> {
+        let metrics = unsafe { &(*slot).metrics };
+        if metrics.width == 0 || metrics.height == 0 {
+            None
+        } else {
+            let left = metrics.horiBearingX >> 6;
+            let top = metrics.horiBearingY >> 6;
+            let right = (metrics.horiBearingX + metrics.width + 0x3f) >> 6;
+            let bottom = (metrics.horiBearingY + metrics.height + 0x3f) >> 6;
+            Some(GlyphDimensions {
+                left: left as i32,
+                top: top as i32,
+                width: (right - left) as u32,
+                height: (bottom - top) as u32,
+            })
+        }
+    }
+
+    pub fn get_glyph_dimensions(&mut self,
+                                key: &GlyphKey) -> Option<GlyphDimensions> {
+        self.load_glyph(key.font_key, key.size, key.index)
+            .and_then(Self::get_glyph_dimensions_impl)
     }
 
     pub fn rasterize_glyph(&mut self,
                            key: &GlyphKey,
                            render_mode: FontRenderMode,
                            _glyph_options: Option<GlyphOptions>)
                            -> Option<RasterizedGlyph> {
-        let mut glyph = None;
-
-        if let Some(slot) = self.load_glyph(key.font_key,
-                                            key.size,
-                                            key.index) {
-            let render_mode = match render_mode {
-                FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
-                FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
-                FontRenderMode::Subpixel => FT_Render_Mode::FT_RENDER_MODE_LCD,
-            };
-
-            let result = unsafe { FT_Render_Glyph(slot, render_mode) };
-
-            if result.succeeded() {
-                let bitmap = unsafe { &(*slot).bitmap };
-
-                let metrics = unsafe { &(*slot).metrics };
-                let mut glyph_width = (metrics.width >> 6) as i32;
-                let glyph_height = (metrics.height >> 6) as i32;
-                let mut final_buffer = Vec::with_capacity(glyph_width as usize *
-                                                          glyph_height as usize *
-                                                          4);
-
-                if bitmap.pixel_mode == FT_Pixel_Mode::FT_PIXEL_MODE_MONO as u8 {
-                    // This is not exactly efficient... but it's only used by the
-                    // reftest pass when we have AA disabled on glyphs.
-                    let offset_x = unsafe { (metrics.horiBearingX >> 6) as i32 - (*slot).bitmap_left };
-                    let offset_y = unsafe { (metrics.horiBearingY >> 6) as i32 - (*slot).bitmap_top };
-
-                    // Due to AA being disabled, the bitmap produced for mono
-                    // glyphs is often smaller than the reported glyph dimensions.
-                    // To account for this, place the rendered glyph within the
-                    // box of the glyph dimensions, filling in invalid pixels with
-                    // zero alpha.
-                    for iy in 0..glyph_height {
-                        let y = iy - offset_y;
-                        for ix in 0..glyph_width {
-                            let x = ix + offset_x;
-                            let valid_byte = x >= 0 &&
-                                y >= 0 &&
-                                x < bitmap.width as i32 &&
-                                y < bitmap.rows as i32;
-                            let byte_value = if valid_byte {
-                                let byte_index = (y * bitmap.pitch as i32) + (x >> 3);
 
-                                unsafe {
-                                    let bit_index = x & 7;
-                                    let byte_ptr = bitmap.buffer.offset(byte_index as isize);
-                                    let bit = (*byte_ptr & (0x80 >> bit_index)) != 0;
-                                    if bit {
-                                        0xff
-                                    } else {
-                                        0
-                                    }
-                                }
-                            } else {
-                                0
-                            };
-
-                            final_buffer.extend_from_slice(&[ 0xff, 0xff, 0xff, byte_value ]);
-                        }
-                    }
-                } else if bitmap.pixel_mode == FT_Pixel_Mode::FT_PIXEL_MODE_GRAY as u8 {
-                    // We can assume that the reported glyph dimensions exactly
-                    // match the rasterized bitmap for normal alpha coverage glyphs.
-
-                    let buffer = unsafe {
-                        slice::from_raw_parts(
-                            bitmap.buffer,
-                            (bitmap.width * bitmap.rows) as usize
-                        )
-                    };
+        let slot = match self.load_glyph(key.font_key, key.size, key.index) {
+            Some(slot) => slot,
+            None => return None,
+        };
+        let render_mode = match render_mode {
+            FontRenderMode::Mono => FT_Render_Mode::FT_RENDER_MODE_MONO,
+            FontRenderMode::Alpha => FT_Render_Mode::FT_RENDER_MODE_NORMAL,
+            FontRenderMode::Subpixel => FT_Render_Mode::FT_RENDER_MODE_LCD,
+        };
 
-                    // Convert to RGBA.
-                    for &byte in buffer.iter() {
-                        final_buffer.extend_from_slice(&[ 0xff, 0xff, 0xff, byte ]);
-                    }
-                } else if bitmap.pixel_mode == FT_Pixel_Mode::FT_PIXEL_MODE_LCD as u8 {
-                    // Extra subpixel on each side of the glyph.
-                    glyph_width += 2;
-
-                    for y in 0..bitmap.rows {
-                        for x in 0..(bitmap.width / 3) {
-                            let index = (y as i32 * bitmap.pitch) + (x as i32 * 3);
-
-                            unsafe {
-                                let ptr = bitmap.buffer.offset(index as isize);
-                                let b = *ptr;
-                                let g = *(ptr.offset(1));
-                                let r = *(ptr.offset(2));
-
-                                final_buffer.extend_from_slice(&[r, g, b, 0xff]);
-                            }
-                        }
-                    }
-                } else {
-                    panic!("Unexpected render mode: {}!", bitmap.pixel_mode);
-                }
-
-                glyph = Some(RasterizedGlyph {
-                    width: glyph_width as u32,
-                    height: glyph_height as u32,
-                    bytes: final_buffer,
-                });
-            }
+        let result = unsafe { FT_Render_Glyph(slot, render_mode) };
+        if result != SUCCESS {
+            error!("Unable to rasterize {:?} with {:?}, {:?}", key, render_mode, result);
+            return None;
         }
 
-        glyph
+        let dimensions = Self::get_glyph_dimensions_impl(slot).unwrap();
+        let bitmap = unsafe { &(*slot).bitmap };
+        let pixel_mode = unsafe { mem::transmute(bitmap.pixel_mode as u32) };
+        info!("Rasterizing {:?} as {:?} with dimensions {:?}", key, render_mode, dimensions);
+        // we may be filling only a part of the buffer, so initialize the whole thing with 0
+        let mut final_buffer = Vec::with_capacity(dimensions.width as usize *
+                                                  dimensions.height as usize *
+                                                  4);
+
+        let offset_x = dimensions.left - unsafe { (*slot).bitmap_left };
+
+        for y in 0 .. dimensions.height {
+            let src_y = y as i32 - dimensions.top + unsafe { (*slot).bitmap_top };
+            if src_y < 0 || src_y >= bitmap.rows as i32 {
+                for _x in 0 .. dimensions.width {
+                    final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, 0]);
+                }
+                continue
+            }
+            let base = unsafe {
+                bitmap.buffer.offset((src_y * bitmap.pitch) as isize)
+            };
+            // determine the destination range of texels that `bitmap` provides data for
+            let dst_start = cmp::max(0, -offset_x);
+            let dst_end = cmp::min(dimensions.width as i32, bitmap.width as i32 - offset_x);
+            for _x in 0 .. dst_start {
+                final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, 0]);
+            }
+            match pixel_mode {
+                FT_Pixel_Mode::FT_PIXEL_MODE_MONO => {
+                    for x in dst_start .. dst_end {
+                        let src_x = x + offset_x;
+                        let mask = 0x80 >> (src_x & 0x7);
+                        let byte = unsafe {
+                            *base.offset((src_x >> 3) as isize)
+                        };
+                        let alpha = if byte & mask != 0 { 0xff } else { 0 };
+                        final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, alpha]);
+                    }
+                }
+                FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
+                    for x in dst_start .. dst_end {
+                        let alpha = unsafe {
+                            *base.offset((x + offset_x) as isize)
+                        };
+                        final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, alpha]);
+                    }
+                }
+                FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
+                    for x in dst_start .. dst_end {
+                        let src_x = ((x + offset_x) * 3) as isize;
+                        assert!(src_x+2 < bitmap.pitch as isize);
+                        let t = unsafe {
+                            slice::from_raw_parts(base.offset(src_x), 3)
+                        };
+                        final_buffer.extend_from_slice(&[t[2], t[1], t[0], 0xff]);
+                    }
+                }
+                _ => panic!("Unsupported {:?}", pixel_mode)
+            }
+            for _x in dst_end .. dimensions.width as i32 {
+                final_buffer.extend_from_slice(&[0xff, 0xff, 0xff, 0]);
+            }
+            assert_eq!(final_buffer.len(), ((y+1) * dimensions.width * 4) as usize);
+        }
+
+        Some(RasterizedGlyph {
+            width: dimensions.width as u32,
+            height: dimensions.height as u32,
+            bytes: final_buffer,
+        })
     }
 }
 
 impl Drop for FontContext {
     fn drop(&mut self) {
         unsafe {
             FT_Done_FreeType(self.lib);
         }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,14 +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 app_units::Au;
-use border::{BorderCornerClipData, BorderCornerDashClipData};
+use border::{BorderCornerClipData, BorderCornerDashClipData, BorderCornerDotClipData};
+use border::BorderCornerInstance;
 use euclid::{Size2D};
 use gpu_store::GpuStoreAddress;
 use internal_types::{SourceTexture, PackedTexel};
 use mask_cache::{ClipMode, ClipSource, MaskCacheInfo};
 use renderer::{VertexDataStore, GradientDataStore, SplitGeometryStore};
 use render_task::{RenderTask, RenderTaskLocation};
 use resource_cache::{CacheItem, ImageProperties, ResourceCache};
 use std::mem;
@@ -211,19 +212,17 @@ impl YuvImagePrimitiveGpu {
             size: size,
             padding: [0.0; 2],
         }
     }
 }
 
 #[derive(Debug, Clone)]
 pub struct BorderPrimitiveCpu {
-    // TODO(gw): Remove this when all border kinds are switched
-    //           over to the new border path!
-    pub use_new_border_path: bool,
+    pub corner_instances: [BorderCornerInstance; 4],
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 pub struct BorderPrimitiveGpu {
     pub style: [f32; 4],
     pub widths: [f32; 4],
     pub colors: [ColorF; 4],
@@ -387,42 +386,48 @@ impl GradientData {
 
         // Preconditions (should be ensured by DisplayListBuilder):
         // * we have at least two stops
         // * first stop has offset 0.0
         // * last stop has offset 1.0
 
         let mut src_stops = src_stops.into_iter();
         let first = src_stops.next().unwrap();
-        let mut cur_color = first.color;
+        let mut cur_color = first.color.premultiplied();
         debug_assert_eq!(first.offset, 0.0);
 
         if reverse_stops {
             // If the gradient is reversed, then we invert offsets and draw right-to-left
             let mut cur_idx = MAX_IDX;
             for next in src_stops {
+                let next_color = next.color.premultiplied();
                 let next_idx = Self::get_index(1.0 - next.offset);
+
                 if next_idx < cur_idx {
                     self.fill_colors(next_idx, cur_idx,
-                                     &next.color, &cur_color);
+                                     &next_color, &cur_color);
                     cur_idx = next_idx;
                 }
-                cur_color = next.color;
+
+                cur_color = next_color;
             }
             debug_assert_eq!(cur_idx, MIN_IDX);
         } else {
             let mut cur_idx = MIN_IDX;
             for next in src_stops {
+                let next_color = next.color.premultiplied();
                 let next_idx = Self::get_index(next.offset);
+
                 if next_idx > cur_idx {
                     self.fill_colors(cur_idx, next_idx,
-                                     &cur_color, &next.color);
+                                     &cur_color, &next_color);
                     cur_idx = next_idx;
                 }
-                cur_color = next.color;
+
+                cur_color = next_color;
             }
             debug_assert_eq!(cur_idx, MAX_IDX);
         }
     }
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
@@ -435,17 +440,17 @@ struct InstanceRect {
 pub struct TextRunPrimitiveGpu {
     pub color: ColorF,
 }
 
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font_key: FontKey,
     pub logical_font_size: Au,
-    pub blur_radius: Au,
+    pub blur_radius: f32,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
     pub cache_dirty: bool,
     // TODO(gw): Maybe make this an Arc for sharing with resource cache
     pub glyph_instances: Vec<GlyphInstance>,
     pub color_texture_id: SourceTexture,
     pub color: ColorF,
     pub render_mode: FontRenderMode,
@@ -1041,36 +1046,34 @@ impl PrimitiveStore {
                 }
                 PrimitiveKind::YuvImage => {
                     let image_cpu = &mut self.cpu_yuv_images[metadata.cpu_prim_index.0];
 
                     //yuv channel
                     let channel_count = image_cpu.format.get_plane_num();
                     debug_assert!(channel_count <= 3);
                     for channel in 0..channel_count {
-                        if image_cpu.yuv_texture_id[channel] == SourceTexture::Invalid {
-                            // Check if an external image that needs to be resolved
-                            // by the render thread.
-                            let resource_address = image_cpu.yuv_resource_address + channel as i32;
+                        // Check if an external image that needs to be resolved
+                        // by the render thread.
+                        let resource_address = image_cpu.yuv_resource_address + channel as i32;
 
-                            let (texture_id, cache_item) =
-                                PrimitiveStore::resolve_image(resource_cache,
-                                                              &mut deferred_resolves,
-                                                              image_cpu.yuv_key[channel],
-                                                              resource_address,
-                                                              ImageRendering::Auto,
-                                                              None);
-                            // texture_id
-                            image_cpu.yuv_texture_id[channel] = texture_id;
-                            // uv coordinates
-                            if let Some(cache_item) = cache_item {
-                                let resource_rect = self.gpu_resource_rects.get_mut(image_cpu.yuv_resource_address + channel as i32);
-                                resource_rect.uv0 = cache_item.uv0;
-                                resource_rect.uv1 = cache_item.uv1;
-                            }
+                        let (texture_id, cache_item) =
+                            PrimitiveStore::resolve_image(resource_cache,
+                                                          &mut deferred_resolves,
+                                                          image_cpu.yuv_key[channel],
+                                                          resource_address,
+                                                          ImageRendering::Auto,
+                                                          None);
+                        // texture_id
+                        image_cpu.yuv_texture_id[channel] = texture_id;
+                        // uv coordinates
+                        if let Some(cache_item) = cache_item {
+                            let resource_rect = self.gpu_resource_rects.get_mut(image_cpu.yuv_resource_address + channel as i32);
+                            resource_rect.uv0 = cache_item.uv0;
+                            resource_rect.uv1 = cache_item.uv1;
                         }
                     }
                 }
             }
         }
 
         deferred_resolves
     }
@@ -1230,31 +1233,30 @@ impl PrimitiveStore {
                             index: src.index,
                             point: LayoutPoint::new(src.point.x, src.point.y),
                         });
 
                         actual_glyph_count += 1;
                     }
 
                     // Expand the rectangle of the text run by the blur radius.
-                    let local_rect = local_rect.inflate(text.blur_radius.to_f32_px(),
-                                                        text.blur_radius.to_f32_px());
+                    let local_rect = local_rect.inflate(text.blur_radius, text.blur_radius);
 
-                    let render_task = if text.blur_radius.0 == 0 {
+                    let render_task = if text.blur_radius == 0.0 {
                         None
                     } else {
                         // This is a text-shadow element. Create a render task that will
                         // render the text run to a target, and then apply a gaussian
                         // blur to that text run in order to build the actual primitive
                         // which will be blitted to the framebuffer.
                         let cache_width = (local_rect.size.width * device_pixel_ratio).ceil() as i32;
                         let cache_height = (local_rect.size.height * device_pixel_ratio).ceil() as i32;
                         let cache_size = DeviceIntSize::new(cache_width, cache_height);
                         let cache_key = PrimitiveCacheKey::TextShadow(prim_index);
-                        let blur_radius = device_length(text.blur_radius.to_f32_px(),
+                        let blur_radius = device_length(text.blur_radius,
                                                         device_pixel_ratio);
                         Some(RenderTask::new_blur(cache_key,
                                                   cache_size,
                                                   blur_radius,
                                                   prim_index))
                     };
 
                     metadata.gpu_data_count = actual_glyph_count as i32;
@@ -1309,17 +1311,17 @@ impl PrimitiveStore {
 
                     debug_assert!(metadata.gpu_data_count == src_stops.len() as i32);
                     let dest_stops = self.gpu_data32.get_slice_mut(metadata.gpu_data_address,
                                                                    src_stops.len());
 
                     for (src, dest) in src_stops.zip(dest_stops.iter_mut()) {
                         *dest = GpuBlock32::from(GradientStopGpu {
                             offset: src.offset,
-                            color: src.color,
+                            color: src.color.premultiplied(),
                             padding: [0.0; 3],
                         });
                     }
 
                     gradient.cache_dirty = false;
                 }
             }
             PrimitiveKind::AngleGradient => {
@@ -1380,17 +1382,17 @@ macro_rules! define_gpu_block {
 }
 
 define_gpu_block!(GpuBlock16: [f32; 4] =
     RectanglePrimitive, InstanceRect, GlyphPrimitive,
     TextRunPrimitiveGpu, ImagePrimitiveGpu, YuvImagePrimitiveGpu
 );
 define_gpu_block!(GpuBlock32: [f32; 8] =
     GradientStopGpu, ClipCorner, ClipRect, ImageMaskData,
-    BorderCornerClipData, BorderCornerDashClipData
+    BorderCornerClipData, BorderCornerDashClipData, BorderCornerDotClipData
 );
 define_gpu_block!(GpuBlock64: [f32; 16] =
     GradientPrimitiveGpu, RadialGradientPrimitiveGpu, BoxShadowPrimitiveGpu
 );
 define_gpu_block!(GpuBlock128: [f32; 32] =
     BorderPrimitiveGpu
 );
 
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/print_tree.rs
@@ -0,0 +1,65 @@
+/* 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 struct that makes it easier to print out a pretty tree of data, which
+/// can be visually scanned more easily.
+pub struct PrintTree {
+    /// The current level of recursion.
+    level: u32,
+
+    /// An item which is queued up, so that we can determine if we need
+    /// a mid-tree prefix or a branch ending prefix.
+    queued_item: Option<String>,
+}
+
+impl PrintTree {
+    pub fn new(title: &str) -> PrintTree {
+        println!("\u{250c} {}", title);
+        PrintTree {
+            level: 1,
+            queued_item: None,
+        }
+    }
+
+    /// Descend one level in the tree with the given title.
+    pub fn new_level(&mut self, title: String) {
+        self.flush_queued_item("\u{251C}\u{2500}");
+
+        self.print_level_prefix();
+        println!("\u{251C}\u{2500} {}", title);
+
+        self.level = self.level + 1;
+    }
+
+    /// Ascend one level in the tree.
+    pub fn end_level(&mut self) {
+        self.flush_queued_item("\u{2514}\u{2500}");
+        self.level = self.level - 1;
+    }
+
+    /// Add an item to the current level in the tree.
+    pub fn add_item(&mut self, text: String) {
+        self.flush_queued_item("\u{251C}\u{2500}");
+        self.queued_item = Some(text);
+    }
+
+    fn print_level_prefix(&self) {
+        for _ in 0..self.level {
+            print!("\u{2502}  ");
+        }
+    }
+
+    fn flush_queued_item(&mut self, prefix: &str) {
+        if let Some(queued_item) = self.queued_item.take() {
+            self.print_level_prefix();
+            println!("{} {}", prefix, queued_item);
+        }
+    }
+}
+
+impl Drop for PrintTree {
+    fn drop(&mut self) {
+        self.flush_queued_item("\u{9492}\u{9472}");
+    }
+}
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -70,13 +70,14 @@ pub fn should_record_msg(msg: &ApiMsg) -
         ApiMsg::AddImage(..) |
         ApiMsg::GenerateFrame(..) |
         ApiMsg::UpdateImage(..) |
         ApiMsg::DeleteImage(..) |
         ApiMsg::SetDisplayList(..) |
         ApiMsg::SetRootPipeline(..) |
         ApiMsg::Scroll(..) |
         ApiMsg::TickScrollingBounce |
-        ApiMsg::WebGLCommand(..) =>
+        ApiMsg::WebGLCommand(..) |
+        ApiMsg::SetWindowParameters(..) =>
             true,
         _ => false
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -10,25 +10,28 @@ use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
 use std::collections::HashMap;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use texture_cache::TextureCache;
 use time::precise_time_ns;
 use thread_profiler::register_thread_with_profiler;
-use threadpool::ThreadPool;
+use rayon::ThreadPool;
 use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
-use webrender_traits::{DeviceIntPoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize, LayerPoint};
-use webrender_traits::{ApiMsg, BuiltDisplayList, IdNamespace, ImageData};
-use webrender_traits::{PipelineId, RenderNotifier, RenderDispatcher, WebGLCommand, WebGLContextId};
-use webrender_traits::channel::{PayloadSenderHelperMethods, PayloadReceiverHelperMethods, PayloadReceiver, PayloadSender, MsgReceiver};
-use webrender_traits::{BlobImageRenderer, VRCompositorCommand, VRCompositorHandler};
+use webrender_traits::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
+use webrender_traits::channel::{PayloadSender, PayloadSenderHelperMethods};
+use webrender_traits::{ApiMsg, BlobImageRenderer, BuiltDisplayList, DeviceIntPoint};
+use webrender_traits::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, IdNamespace, ImageData};
+use webrender_traits::{LayerPoint, PipelineId, RenderDispatcher, RenderNotifier};
+use webrender_traits::{VRCompositorCommand, VRCompositorHandler, WebGLCommand, WebGLContextId};
+
 #[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.
 ///
 /// The render backend operates on its own thread.
 pub struct RenderBackend {
@@ -65,28 +68,27 @@ pub struct RenderBackend {
 
 impl RenderBackend {
     pub fn new(api_rx: MsgReceiver<ApiMsg>,
                payload_rx: PayloadReceiver,
                payload_tx: PayloadSender,
                result_tx: Sender<ResultMsg>,
                hidpi_factor: f32,
                texture_cache: TextureCache,
-               enable_aa: bool,
-               workers: Arc<Mutex<ThreadPool>>,
+               workers: Arc<ThreadPool>,
                notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
                webrender_context_handle: Option<GLContextHandleWrapper>,
                config: FrameBuilderConfig,
                recorder: Option<Box<ApiRecordingReceiver>>,
                main_thread_dispatcher: Arc<Mutex<Option<Box<RenderDispatcher>>>>,
                blob_image_renderer: Option<Box<BlobImageRenderer>>,
                vr_compositor_handler: Arc<Mutex<Option<Box<VRCompositorHandler>>>>,
                initial_window_size: DeviceUintSize) -> RenderBackend {
 
-        let resource_cache = ResourceCache::new(texture_cache, workers, blob_image_renderer, enable_aa);
+        let resource_cache = ResourceCache::new(texture_cache, workers, blob_image_renderer);
 
         register_thread_with_profiler("Backend".to_string());
 
         RenderBackend {
             api_rx: api_rx,
             payload_rx: payload_rx,
             payload_tx: payload_tx,
             result_tx: result_tx,
@@ -175,16 +177,17 @@ impl RenderBackend {
                             self.next_namespace_id = IdNamespace(id_namespace + 1);
 
                             sender.send(result).unwrap();
                         }
                         ApiMsg::SetDisplayList(background_color,
                                                epoch,
                                                pipeline_id,
                                                viewport_size,
+                                               content_size,
                                                display_list_descriptor,
                                                preserve_frame_state) => {
                             profile_scope!("SetDisplayList");
                             let mut leftover_data = vec![];
                             let mut data;
                             loop {
                                 data = self.payload_rx.recv_payload().unwrap();
                                 {
@@ -215,17 +218,18 @@ impl RenderBackend {
 
                             let display_list_received_time = precise_time_ns();
 
                             profile_counters.total_time.profile(|| {
                                 self.scene.set_display_list(pipeline_id,
                                                             epoch,
                                                             built_display_list,
                                                             background_color,
-                                                            viewport_size);
+                                                            viewport_size,
+                                                            content_size);
                                 self.build_scene();
                             });
 
                             // Note: this isn't quite right as auxiliary values will be
                             // pulled out somewhere in the prim_store, but aux values are
                             // really simple and cheap to access, so it's not a big deal.
                             let display_list_consumed_time = precise_time_ns();
 
@@ -261,22 +265,22 @@ impl RenderBackend {
                             match frame {
                                 Some(frame) => {
                                     self.publish_frame(frame, &mut profile_counters);
                                     self.notify_compositor_of_new_scroll_frame(true)
                                 }
                                 None => self.notify_compositor_of_new_scroll_frame(false),
                             }
                         }
-                        ApiMsg::ScrollNodeWithId(origin, id) => {
+                        ApiMsg::ScrollNodeWithId(origin, id, clamp) => {
                             profile_scope!("ScrollNodeWithScrollId");
                             let frame = {
                                 let counters = &mut profile_counters.resources.texture_cache;
                                 profile_counters.total_time.profile(|| {
-                                    if self.frame.scroll_nodes(origin, id) {
+                                    if self.frame.scroll_node(origin, id, clamp) {
                                         Some(self.render(counters))
                                     } else {
                                         None
                                     }
                                 })
                             };
 
                             match frame {
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -32,17 +32,17 @@ pub enum RenderTaskKey {
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum MaskCacheKey {
     Primitive(PrimitiveIndex),
     ClipNode(ClipId),
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq)]
 pub enum RenderTaskId {
     Static(RenderTaskIndex),
     Dynamic(RenderTaskKey),
 }
 
 
 #[derive(Debug, Clone)]
 pub enum RenderTaskLocation {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -36,28 +36,29 @@ use std::hash::BuildHasherDefault;
 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 rayon::ThreadPool;
+use rayon::Configuration as ThreadPoolConfig;
 use tiling::{AlphaBatchKind, BlurCommand, Frame, PrimitiveBatch, RenderTarget};
 use tiling::{AlphaRenderTarget, CacheClipInstance, PrimitiveInstance, ColorRenderTarget, RenderTargetKind};
 use time::precise_time_ns;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use util::TransformedRectKind;
 use webgl_types::GLContextHandleWrapper;
 use webrender_traits::{ColorF, Epoch, PipelineId, RenderNotifier, RenderDispatcher};
 use webrender_traits::{ExternalImageId, ExternalImageType, ImageData, ImageFormat, RenderApiSender};
 use webrender_traits::{DeviceIntRect, DevicePoint, DeviceIntPoint, DeviceIntSize, DeviceUintSize};
 use webrender_traits::{ImageDescriptor, BlobImageRenderer};
-use webrender_traits::channel;
+use webrender_traits::{channel, FontRenderMode};
 use webrender_traits::VRCompositorHandler;
 use webrender_traits::{YuvColorSpace, YuvFormat};
 use webrender_traits::{YUV_COLOR_SPACES, YUV_FORMATS};
 
 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 };
@@ -72,17 +73,16 @@ const GPU_TAG_PRIM_BLEND: GpuProfileTag 
 const GPU_TAG_PRIM_HW_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "HwComposite", color: debug_colors::DODGERBLUE };
 const GPU_TAG_PRIM_SPLIT_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "SplitComposite", color: debug_colors::DARKBLUE };
 const GPU_TAG_PRIM_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "Composite", color: debug_colors::MAGENTA };
 const GPU_TAG_PRIM_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "TextRun", color: debug_colors::BLUE };
 const GPU_TAG_PRIM_GRADIENT: GpuProfileTag = GpuProfileTag { label: "Gradient", color: debug_colors::YELLOW };
 const GPU_TAG_PRIM_ANGLE_GRADIENT: GpuProfileTag = GpuProfileTag { label: "AngleGradient", color: debug_colors::POWDERBLUE };
 const GPU_TAG_PRIM_RADIAL_GRADIENT: GpuProfileTag = GpuProfileTag { label: "RadialGradient", color: debug_colors::LIGHTPINK };
 const GPU_TAG_PRIM_BOX_SHADOW: GpuProfileTag = GpuProfileTag { label: "BoxShadow", color: debug_colors::CYAN };
-const GPU_TAG_PRIM_BORDER: GpuProfileTag = GpuProfileTag { label: "Border", color: debug_colors::ORANGE };
 const GPU_TAG_PRIM_BORDER_CORNER: GpuProfileTag = GpuProfileTag { label: "BorderCorner", color: debug_colors::DARKSLATEGREY };
 const GPU_TAG_PRIM_BORDER_EDGE: GpuProfileTag = GpuProfileTag { label: "BorderEdge", color: debug_colors::LAVENDER };
 const GPU_TAG_PRIM_CACHE_IMAGE: GpuProfileTag = GpuProfileTag { label: "CacheImage", color: debug_colors::SILVER };
 const GPU_TAG_BLUR: GpuProfileTag = GpuProfileTag { label: "Blur", color: debug_colors::VIOLET };
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub enum ImageBufferKind {
     Texture2D = 0,
@@ -532,17 +532,16 @@ pub struct Renderer {
     // 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: Vec<Option<PrimitiveShader>>,
     ps_yuv_image: Vec<Option<PrimitiveShader>>,
-    ps_border: PrimitiveShader,
     ps_border_corner: PrimitiveShader,
     ps_border_edge: PrimitiveShader,
     ps_gradient: PrimitiveShader,
     ps_angle_gradient: PrimitiveShader,
     ps_radial_gradient: PrimitiveShader,
     ps_box_shadow: PrimitiveShader,
     ps_cache_image: PrimitiveShader,
 
@@ -809,23 +808,16 @@ impl Renderer {
                                                                    YUV_COLOR_SPACES[color_space_kind]);
                         ps_yuv_image[index] = Some(shader);
                         yuv_features.clear();
                     }
                 }
             }
         }
 
-        let ps_border = try!{
-            PrimitiveShader::new("ps_border",
-                                 &mut device,
-                                 &[],
-                                 options.precache_shaders)
-        };
-
         let ps_border_corner = try!{
             PrimitiveShader::new("ps_border_corner",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
         let ps_border_edge = try!{
@@ -1033,38 +1025,45 @@ impl Renderer {
 
         // We need a reference to the webrender context from the render backend in order to share
         // texture ids
         let context_handle = match options.renderer_kind {
             RendererKind::Native => GLContextHandleWrapper::current_native_handle(),
             RendererKind::OSMesa => GLContextHandleWrapper::current_osmesa_handle(),
         };
 
+        let default_font_render_mode = match (options.enable_aa, options.enable_subpixel_aa) {
+            (true, true) => FontRenderMode::Subpixel,
+            (true, false) => FontRenderMode::Alpha,
+            (false, _) => FontRenderMode::Mono,
+        };
+
         let config = FrameBuilderConfig::new(options.enable_scrollbars,
-                                             options.enable_subpixel_aa,
+                                             default_font_render_mode,
                                              options.debug);
 
-        let (device_pixel_ratio, enable_aa) = (options.device_pixel_ratio, options.enable_aa);
+        let device_pixel_ratio = options.device_pixel_ratio;
         let render_target_debug = options.render_target_debug;
         let payload_tx_for_backend = payload_tx.clone();
         let recorder = options.recorder;
+        // TODO(gw): Use a heuristic to select best # of worker threads.
+        let worker_config = ThreadPoolConfig::new().thread_name(|idx|{ format!("WebRender:Worker#{}", idx) })
+                                                   .num_threads(4);
         let workers = options.workers.take().unwrap_or_else(||{
-            // TODO(gw): Use a heuristic to select best # of worker threads.
-            Arc::new(Mutex::new(ThreadPool::new_with_name("WebRender:Worker".to_string(), 4)))
+            Arc::new(ThreadPool::new(worker_config).unwrap())
         });
 
         let blob_image_renderer = options.blob_image_renderer.take();
         try!{ thread::Builder::new().name("RenderBackend".to_string()).spawn(move || {
             let mut backend = RenderBackend::new(api_rx,
                                                  payload_rx,
                                                  payload_tx_for_backend,
                                                  result_tx,
                                                  device_pixel_ratio,
                                                  texture_cache,
-                                                 enable_aa,
                                                  workers,
                                                  backend_notifier,
                                                  context_handle,
                                                  config,
                                                  recorder,
                                                  backend_main_thread_dispatcher,
                                                  blob_image_renderer,
                                                  backend_vr_compositor,
@@ -1087,17 +1086,16 @@ impl Renderer {
             cs_clip_border: cs_clip_border,
             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_yuv_image: ps_yuv_image,
-            ps_border: ps_border,
             ps_border_corner: ps_border_corner,
             ps_border_edge: ps_border_edge,
             ps_box_shadow: ps_box_shadow,
             ps_gradient: ps_gradient,
             ps_angle_gradient: ps_angle_gradient,
             ps_radial_gradient: ps_radial_gradient,
             ps_cache_image: ps_cache_image,
             ps_blend: ps_blend,
@@ -1550,20 +1548,16 @@ impl Renderer {
             }
             AlphaBatchKind::YuvImage(image_buffer_kind, format, color_space) => {
                 let shader_index = Renderer::get_yuv_shader_index(image_buffer_kind,
                                                                   format,
                                                                   color_space);
                 let shader = self.ps_yuv_image[shader_index].as_mut().unwrap().get(&mut self.device, transform_kind);
                 (GPU_TAG_PRIM_YUV_IMAGE, shader)
             }
-            AlphaBatchKind::Border => {
-                let shader = self.ps_border.get(&mut self.device, transform_kind);
-                (GPU_TAG_PRIM_BORDER, shader)
-            }
             AlphaBatchKind::BorderCorner => {
                 let shader = self.ps_border_corner.get(&mut self.device, transform_kind);
                 (GPU_TAG_PRIM_BORDER_CORNER, shader)
             }
             AlphaBatchKind::BorderEdge => {
                 let shader = self.ps_border_edge.get(&mut self.device, transform_kind);
                 (GPU_TAG_PRIM_BORDER_EDGE, shader)
             }
@@ -1750,17 +1744,18 @@ impl Renderer {
                                       &target.text_run_textures,
                                       &projection);
         }
 
         let _gm2 = GpuMarker::new(self.device.rc_gl(), "alpha batches");
         self.device.set_blend(false);
         let mut prev_blend_mode = BlendMode::None;
 
-        self.device.set_depth_func(DepthFunction::Less);
+        //Note: depth equality is needed for split planes
+        self.device.set_depth_func(DepthFunction::LessEqual);
         self.device.enable_depth();
         self.device.enable_depth_write();
 
         for batch in &target.alpha_batcher.batch_list.opaque_batches {
             self.submit_batch(batch,
                               &projection,
                               render_task_data,
                               color_cache_texture,
@@ -1825,16 +1820,48 @@ impl Renderer {
                                           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;
+
+            // If we have border corner clips, the first step is to clear out the
+            // area in the clip mask. This allows drawing multiple invididual clip
+            // in regions below.
+            if !target.clip_batcher.border_clears.is_empty() {
+                let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders [clear]");
+                self.device.set_blend(false);
+                let shader = self.cs_clip_border.get(&mut self.device).unwrap();
+                self.draw_instanced_batch(&target.clip_batcher.border_clears,
+                                          vao,
+                                          shader,
+                                          &BatchTextures::no_texture(),
+                                          &projection);
+            }
+
+            // Draw any dots or dashes for border corners.
+            if !target.clip_batcher.borders.is_empty() {
+                let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders");
+                // We are masking in parts of the corner (dots or dashes) here.
+                // Blend mode is set to max to allow drawing multiple dots.
+                // The individual dots and dashes in a border never overlap, so using
+                // a max blend mode here is fine.
+                self.device.set_blend(true);
+                self.device.set_blend_mode_max();
+                let shader = self.cs_clip_border.get(&mut self.device).unwrap();
+                self.draw_instanced_batch(&target.clip_batcher.borders,
+                                          vao,
+                                          shader,
+                                          &BatchTextures::no_texture(),
+                                          &projection);
+            }
+
             // switch to multiplicative blending
             self.device.set_blend(true);
             self.device.set_blend_mode_multiply();
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip rectangles");
                 let shader = self.cs_clip_rectangle.get(&mut self.device).unwrap();
@@ -1856,26 +1883,16 @@ impl Renderer {
                 };
                 let shader = self.cs_clip_image.get(&mut self.device).unwrap();
                 self.draw_instanced_batch(items,
                                           vao,
                                           shader,
                                           &textures,
                                           &projection);
             }
-            // draw special border clips
-            if !target.clip_batcher.borders.is_empty() {
-                let _gm2 = GpuMarker::new(self.device.rc_gl(), "clip borders");
-                let shader = self.cs_clip_border.get(&mut self.device).unwrap();
-                self.draw_instanced_batch(&target.clip_batcher.borders,
-                                          vao,
-                                          shader,
-                                          &BatchTextures::no_texture(),
-                                          &projection);
-            }
         }
     }
 
     fn update_deferred_resolves(&mut self, frame: &mut Frame) {
         // The first thing we do is run through any pending deferred
         // resolves, and use a callback to get the UV rect for this
         // custom item. Then we patch the resource_rects structure
         // here before it's uploaded to the GPU.
@@ -2188,17 +2205,17 @@ pub struct RendererOptions {
     pub precache_shaders: bool,
     pub renderer_kind: RendererKind,
     pub enable_subpixel_aa: bool,
     pub clear_framebuffer: bool,
     pub clear_color: ColorF,
     pub enable_batcher: bool,
     pub render_target_debug: bool,
     pub max_texture_size: Option<u32>,
-    pub workers: Option<Arc<Mutex<ThreadPool>>>,
+    pub workers: Option<Arc<ThreadPool>>,
     pub blob_image_renderer: Option<Box<BlobImageRenderer>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
 }
 
 impl Default for RendererOptions {
     fn default() -> RendererOptions {
         RendererOptions {
             device_pixel_ratio: 1.0,
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -11,28 +11,28 @@ use platform::font::{FontContext, Raster
 use profiler::TextureCacheProfileCounters;
 use std::cell::RefCell;
 use std::collections::{HashMap, HashSet};
 use std::collections::hash_map::Entry::{self, Occupied, Vacant};
 use std::fmt::Debug;
 use std::hash::BuildHasherDefault;
 use std::hash::Hash;
 use std::mem;
-use std::sync::{Arc, Barrier, Mutex};
+use std::sync::{Arc, Barrier};
 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::{GlyphOptions, GlyphInstance, TileOffset, TileSize};
 use webrender_traits::{BlobImageRenderer, BlobImageDescriptor, BlobImageError, BlobImageRequest, BlobImageData, ImageStore};
 use webrender_traits::{ExternalImageData, ExternalImageType, LayoutPoint};
-use threadpool::ThreadPool;
+use rayon::ThreadPool;
 
 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.
@@ -169,20 +169,20 @@ impl<K,V> ResourceClassCache<K,V> where 
 
     fn contains_key(&self, key: &K) -> bool {
         self.resources.contains_key(key)
     }
 
     fn get(&self, key: &K, frame: FrameId) -> &V {
         // This assert catches cases in which we accidentally request a resource that we forgot to
         // mark as needed this frame.
-        debug_assert!(frame == *self.last_access_times
-                                    .get(key)
-                                    .expect("Didn't find the access time for a cached resource \
-                                             with that ID!"));
+        debug_assert_eq!(frame, *self.last_access_times
+                                     .get(key)
+                                     .expect("Didn't find the access time for a cached resource \
+                                              with that ID!"));
         self.resources.get(key).expect("Didn't find a cached resource with that ID!")
     }
 
     fn insert(&mut self, key: K, value: V, frame: FrameId) {
         self.last_access_times.insert(key.clone(), frame);
         self.resources.insert(key, value);
     }
 
@@ -246,17 +246,16 @@ pub struct ResourceCache {
     cached_glyphs: Option<GlyphCache>,
     cached_images: ResourceClassCache<ImageRequest, CachedImageInfo>,
 
     // TODO(pcwalton): Figure out the lifecycle of these.
     webgl_textures: HashMap<WebGLContextId, WebGLTexture, BuildHasherDefault<FnvHasher>>,
 
     font_templates: HashMap<FontKey, FontTemplate, BuildHasherDefault<FnvHasher>>,
     image_templates: ImageTemplates,
-    enable_aa: bool,
     state: State,
     current_frame_id: FrameId,
 
     texture_cache: TextureCache,
 
     // TODO(gw): We should expire (parts of) this cache semi-regularly!
     cached_glyph_dimensions: HashMap<GlyphKey, Option<GlyphDimensions>, BuildHasherDefault<FnvHasher>>,
     pending_image_requests: Vec<ImageRequest>,
@@ -264,31 +263,29 @@ pub struct ResourceCache {
     glyph_cache_result_queue: Receiver<GlyphCacheResultMsg>,
 
     blob_image_renderer: Option<Box<BlobImageRenderer>>,
     blob_image_requests: HashSet<ImageRequest>,
 }
 
 impl ResourceCache {
     pub fn new(texture_cache: TextureCache,
-               workers: Arc<Mutex<ThreadPool>>,
-               blob_image_renderer: Option<Box<BlobImageRenderer>>,
-               enable_aa: bool) -> ResourceCache {
+               workers: Arc<ThreadPool>,
+               blob_image_renderer: Option<Box<BlobImageRenderer>>) -> ResourceCache {
         let (glyph_cache_tx, glyph_cache_result_queue) = spawn_glyph_cache_thread(workers);
 
         ResourceCache {
             cached_glyphs: Some(ResourceClassCache::new()),
             cached_images: ResourceClassCache::new(),
             webgl_textures: HashMap::default(),
             font_templates: HashMap::default(),
             image_templates: ImageTemplates::new(),
             cached_glyph_dimensions: HashMap::default(),
             texture_cache: texture_cache,
             state: State::Idle,
-            enable_aa: enable_aa,
             current_frame_id: FrameId(0),
             pending_image_requests: Vec::new(),
             glyph_cache_tx: glyph_cache_tx,
             glyph_cache_result_queue: glyph_cache_result_queue,
 
             blob_image_renderer: blob_image_renderer,
             blob_image_requests: HashSet::new(),
         }
@@ -429,23 +426,24 @@ impl ResourceCache {
         webgl_texture.size = size;
     }
 
     pub fn request_image(&mut self,
                          key: ImageKey,
                          rendering: ImageRendering,
                          tile: Option<TileOffset>) {
 
-        debug_assert!(self.state == State::AddResources);
+        debug_assert_eq!(self.state, State::AddResources);
         let request = ImageRequest {
             key: key,
             rendering: rendering,
             tile: tile,
         };
 
+        self.cached_images.mark_as_needed(&request, self.current_frame_id);
         let template = self.image_templates.get(key).unwrap();
         if template.data.is_blob() {
             if let Some(ref mut renderer) = self.blob_image_renderer {
                 let same_epoch = match self.cached_images.resources.get(&request) {
                     Some(entry) => entry.epoch == template.epoch,
                     None => false,
                 };
 
@@ -486,18 +484,17 @@ impl ResourceCache {
 
     pub fn request_glyphs(&mut self,
                           key: FontKey,
                           size: Au,
                           color: ColorF,
                           glyph_instances: &[GlyphInstance],
                           render_mode: FontRenderMode,
                           glyph_options: Option<GlyphOptions>) {
-        debug_assert!(self.state == State::AddResources);
-        let render_mode = self.get_glyph_render_mode(render_mode);
+        debug_assert_eq!(self.state, State::AddResources);
         // Immediately request that the glyph cache thread start
         // rasterizing glyphs from this request if they aren't
         // already cached.
         let msg = GlyphCacheMsg::RequestGlyphs(key,
                                                size,
                                                color,
                                                glyph_instances.to_vec(),
                                                render_mode,
@@ -512,19 +509,18 @@ impl ResourceCache {
     pub fn get_glyphs<F>(&self,
                          font_key: FontKey,
                          size: Au,
                          color: ColorF,
                          glyph_instances: &[GlyphInstance],
                          render_mode: FontRenderMode,
                          glyph_options: Option<GlyphOptions>,
                          mut f: F) -> SourceTexture where F: FnMut(usize, DevicePoint, DevicePoint) {
-        debug_assert!(self.state == State::QueryResources);
+        debug_assert_eq!(self.state, State::QueryResources);
         let cache = self.cached_glyphs.as_ref().unwrap();
-        let render_mode = self.get_glyph_render_mode(render_mode);
         let mut glyph_key = RenderedGlyphKey::new(font_key,
                                                   size,
                                                   color,
                                                   0,
                                                   LayoutPoint::new(0.0, 0.0),
                                                   render_mode,
                                                   glyph_options);
         let mut texture_id = None;
@@ -576,17 +572,17 @@ impl ResourceCache {
         }
     }
 
     #[inline]
     pub fn get_cached_image(&self,
                             image_key: ImageKey,
                             image_rendering: ImageRendering,
                             tile: Option<TileOffset>) -> CacheItem {
-        debug_assert!(self.state == State::QueryResources);
+        debug_assert_eq!(self.state, State::QueryResources);
         let key = ImageRequest {
             key: image_key,
             rendering: image_rendering,
             tile: tile,
         };
         let image_info = &self.cached_images.get(&key, self.current_frame_id);
         let item = self.texture_cache.get(image_info.texture_cache_id);
         CacheItem {
@@ -641,28 +637,28 @@ impl ResourceCache {
     pub fn expire_old_resources(&mut self, frame_id: FrameId) {
         self.cached_images.expire_old_resources(&mut self.texture_cache, frame_id);
 
         let cached_glyphs = self.cached_glyphs.as_mut().unwrap();
         cached_glyphs.expire_old_resources(&mut self.texture_cache, frame_id);
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
-        debug_assert!(self.state == State::Idle);
+        debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.current_frame_id = frame_id;
         let glyph_cache = self.cached_glyphs.take().unwrap();
         self.glyph_cache_tx.send(GlyphCacheMsg::BeginFrame(frame_id, glyph_cache)).ok();
     }
 
     pub fn block_until_all_resources_added(&mut self,
                                            texture_cache_profile: &mut TextureCacheProfileCounters) {
         profile_scope!("block_until_all_resources_added");
 
-        debug_assert!(self.state == State::AddResources);
+        debug_assert_eq!(self.state, State::AddResources);
         self.state = State::QueryResources;
 
         // Tell the glyph cache thread that all glyphs have been requested
         // and block, waiting for any pending glyphs to be rasterized. In the
         // future, we will expand this to have a timeout. If the glyph rasterizing
         // takes longer than the timeout, then we will select the best glyphs
         // available in the cache, render with those, and then re-render at
         // a later point when the correct resolution glyphs finally become
@@ -847,27 +843,19 @@ impl ResourceCache {
                 self.update_texture_cache(&request,
                                            image_data,
                                            texture_cache_profile);
             }
         }
     }
 
     pub fn end_frame(&mut self) {
-        debug_assert!(self.state == State::QueryResources);
+        debug_assert_eq!(self.state, State::QueryResources);
         self.state = State::Idle;
     }
-
-    fn get_glyph_render_mode(&self, requested_mode: FontRenderMode) -> FontRenderMode {
-        if self.enable_aa {
-            requested_mode
-        } else {
-            FontRenderMode::Mono
-        }
-    }
 }
 
 pub trait Resource {
     fn texture_cache_item_id(&self) -> Option<TextureCacheItemId>;
 }
 
 impl Resource for TextureCacheItemId {
     fn texture_cache_item_id(&self) -> Option<TextureCacheItemId> {
@@ -882,19 +870,19 @@ impl Resource for Option<TextureCacheIte
 }
 
 impl Resource for CachedImageInfo {
     fn texture_cache_item_id(&self) -> Option<TextureCacheItemId> {
         Some(self.texture_cache_id)
     }
 }
 
-fn spawn_glyph_cache_thread(workers: Arc<Mutex<ThreadPool>>) -> (Sender<GlyphCacheMsg>, Receiver<GlyphCacheResultMsg>) {
+fn spawn_glyph_cache_thread(workers: Arc<ThreadPool>) -> (Sender<GlyphCacheMsg>, Receiver<GlyphCacheResultMsg>) {
     let worker_count = {
-        workers.lock().unwrap().max_count()
+        workers.current_num_threads()
     };
     // Used for messages from resource cache -> glyph cache thread.
     let (msg_tx, msg_rx) = channel();
     // Used for returning results from glyph cache thread -> resource cache.
     let (result_tx, result_rx) = channel();
     // Used for rasterizer worker threads to send glyphs -> glyph cache thread.
     let (glyph_tx, glyph_rx) = channel();
 
@@ -902,17 +890,17 @@ fn spawn_glyph_cache_thread(workers: Arc
         let mut glyph_cache = None;
         let mut current_frame_id = FrameId(0);
 
         register_thread_with_profiler("GlyphCache".to_string());
 
         let barrier = Arc::new(Barrier::new(worker_count));
         for i in 0..worker_count {
             let barrier = Arc::clone(&barrier);
-            workers.lock().unwrap().execute(move || {
+            workers.spawn_async(move || {
                 register_thread_with_profiler(format!("Glyph Worker {}", i));
                 barrier.wait();
             });
         }
 
         // Maintain a set of glyphs that have been requested this
         // frame. This ensures the glyph thread won't rasterize
         // the same glyph more than once in a frame. This is required
@@ -939,17 +927,17 @@ fn spawn_glyph_cache_thread(workers: Arc
                     // Add a new font to the font context in each worker thread.
                     // Use a barrier to ensure that each worker in the pool handles
                     // one of these messages, to ensure that the new font gets
                     // added to each worker thread.
                     let barrier = Arc::new(Barrier::new(worker_count));
                     for _ in 0..worker_count {
                         let barrier = Arc::clone(&barrier);
                         let font_template = font_template.clone();
-                        workers.lock().unwrap().execute(move || {
+                        workers.spawn_async(move || {
                             FONT_CONTEXT.with(|font_context| {
                                 let mut font_context = font_context.borrow_mut();
                                 match font_template {
                                     FontTemplate::Raw(ref bytes, index) => {
                                         font_context.add_raw_font(&font_key, &**bytes, index);
                                     }
                                     FontTemplate::Native(ref native_font_handle) => {
                                         font_context.add_native_font(&font_key,
@@ -964,17 +952,17 @@ fn spawn_glyph_cache_thread(workers: Arc
                 }
                 GlyphCacheMsg::DeleteFont(font_key) => {
                     profile_scope!("DeleteFont");
 
                     // Delete a font from the font context in each worker thread.
                     let barrier = Arc::new(Barrier::new(worker_count));
                     for _ in 0..worker_count {
                         let barrier = Arc::clone(&barrier);
-                        workers.lock().unwrap().execute(move || {
+                        workers.spawn_async(move || {
                             FONT_CONTEXT.with(|font_context| {
                                 let mut font_context = font_context.borrow_mut();
                                 font_context.delete_font(&font_key);
                             });
                             barrier.wait();
                         });
                     }
 
@@ -997,23 +985,26 @@ fn spawn_glyph_cache_thread(workers: Arc
                                                               render_mode,
                                                               glyph_options);
 
                         glyph_cache.mark_as_needed(&glyph_key, current_frame_id);
                         if !glyph_cache.contains_key(&glyph_key) &&
                            !pending_glyphs.contains(&glyph_key) {
                             let glyph_tx = glyph_tx.clone();
                             pending_glyphs.insert(glyph_key.clone());
-                            workers.lock().unwrap().execute(move || {
+                            workers.spawn_async(move || {
                                 profile_scope!("glyph");
                                 FONT_CONTEXT.with(move |font_context| {
                                     let mut font_context = font_context.borrow_mut();
                                     let result = font_context.rasterize_glyph(&glyph_key.key,
                                                                               render_mode,
                                                                               glyph_options);
+                                    if let Some(ref glyph) = result {
+                                        assert_eq!(glyph.bytes.len(), 4 * (glyph.width * glyph.height) as usize);
+                                    }
                                     glyph_tx.send((glyph_key, result)).unwrap();
                                 });
                             });
                         }
                     }
                 }
                 GlyphCacheMsg::EndFrame => {
                     profile_scope!("EndFrame");
@@ -1023,16 +1014,19 @@ fn spawn_glyph_cache_thread(workers: Arc
                     // return the list of new glyphs to the resource cache.
                     let cache = glyph_cache.take().unwrap();
                     let mut rasterized_glyphs = Vec::new();
                     while !pending_glyphs.is_empty() {
                         let (key, glyph) = glyph_rx.recv()
                                                    .expect("BUG: Should be glyphs pending!");
                         debug_assert!(pending_glyphs.contains(&key));
                         pending_glyphs.remove(&key);
+                        if let Some(ref v) = glyph {
+                            debug!("received {}x{} data len {}", v.width, v.height, v.bytes.len());
+                        }
                         rasterized_glyphs.push(GlyphRasterJob {
                             key: key,
                             result: glyph,
                         });
                     }
                     // Ensure that the glyphs are always processed in the same
                     // order for a given text run (since iterating a hash set doesn't
                     // guarantee order). This can show up as very small float inaccuacry
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -1,18 +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 fnv::FnvHasher;
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
-use webrender_traits::{BuiltDisplayList, PipelineId, Epoch, ColorF};
-use webrender_traits::{DynamicProperties, LayerSize, LayoutTransform};
-use webrender_traits::{PropertyBinding, PropertyBindingId};
+use webrender_traits::{BuiltDisplayList, ColorF, DynamicProperties, Epoch, LayerSize, LayoutSize};
+use webrender_traits::{LayoutTransform, PipelineId, PropertyBinding, PropertyBindingId};
 
 /// Stores a map of the animated property bindings for the current display list. These
 /// can be used to animate the transform and/or opacity of a display list without
 /// re-submitting the display list itself.
 pub struct SceneProperties {
     transform_properties: HashMap<PropertyBindingId, LayoutTransform>,
     float_properties: HashMap<PropertyBindingId, f32>,
 }
@@ -80,16 +79,17 @@ impl SceneProperties {
 }
 
 /// A representation of the layout within the display port for a given document or iframe.
 #[derive(Debug)]
 pub struct ScenePipeline {
     pub pipeline_id: PipelineId,
     pub epoch: Epoch,
     pub viewport_size: LayerSize,
+    pub content_size: LayoutSize,
     pub background_color: Option<ColorF>,
 }
 
 /// A complete representation of the layout bundling visible pipelines together.
 pub struct Scene {
     pub root_pipeline_id: Option<PipelineId>,
     pub pipeline_map: HashMap<PipelineId, ScenePipeline, BuildHasherDefault<FnvHasher>>,
     pub display_lists: HashMap<PipelineId, BuiltDisplayList, BuildHasherDefault<FnvHasher>>,
@@ -110,22 +110,23 @@ impl Scene {
         self.root_pipeline_id = Some(pipeline_id);
     }
 
     pub fn set_display_list(&mut self,
                             pipeline_id: PipelineId,
                             epoch: Epoch,
                             built_display_list: BuiltDisplayList,
                             background_color: Option<ColorF>,
-                            viewport_size: LayerSize) {
+                            viewport_size: LayerSize,
+                            content_size: LayoutSize) {
+        self.display_lists.insert(pipeline_id, built_display_list);
 
-        self.display_lists.insert(pipeline_id, built_display_list);
-        
         let new_pipeline = ScenePipeline {
             pipeline_id: pipeline_id,
             epoch: epoch,
             viewport_size: viewport_size,
+            content_size: content_size,
             background_color: background_color,
         };
 
         self.pipeline_map.insert(pipeline_id, new_pipeline);
     }
 }
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -773,16 +773,23 @@ impl TextureCache {
             panic!("must rasterize the vector image before adding to the cache");
         }
 
         let width = descriptor.width;
         let height = descriptor.height;
         let format = descriptor.format;
         let stride = descriptor.stride;
 
+        if let ImageData::Raw(ref vec) = data {
+            let finish = descriptor.offset +
+                         width * format.bytes_per_pixel().unwrap_or(0) +
+                         (height-1) * descriptor.compute_stride();
+            assert!(vec.len() >= finish as usize);
+        }
+
         let result = self.allocate(image_id,
                                    width,
                                    height,
                                    format,
                                    filter,
                                    profile);
 
         match result.kind {
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.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 app_units::Au;
+use border::{BorderCornerInstance, BorderCornerSide};
 use device::TextureId;
 use fnv::FnvHasher;
 use gpu_store::GpuStoreAddress;
 use internal_types::{ANGLE_FLOAT_TO_FIXED, BatchTextures, CacheTextureId, LowLevelFilterOp};
 use internal_types::SourceTexture;
 use mask_cache::MaskCacheInfo;
 use prim_store::{CLIP_DATA_GPU_SIZE, DeferredResolve, GpuBlock128, GpuBlock16, GpuBlock32};
 use prim_store::{GpuBlock64, GradientData, SplitGeometry, PrimitiveCacheKey, PrimitiveGeometry};
@@ -71,17 +72,17 @@ impl AlphaBatchHelpers for PrimitiveStor
             }
         }
     }
 
     fn get_blend_mode(&self, needs_blending: bool, metadata: &PrimitiveMetadata) -> BlendMode {
         match metadata.prim_kind {
             PrimitiveKind::TextRun => {
                 let text_run_cpu = &self.cpu_text_runs[metadata.cpu_prim_index.0];
-                if text_run_cpu.blur_radius.0 == 0 {
+                if text_run_cpu.blur_radius == 0.0 {
                     match text_run_cpu.render_mode {
                         FontRenderMode::Subpixel => BlendMode::Subpixel(text_run_cpu.color),
                         FontRenderMode::Alpha | FontRenderMode::Mono => BlendMode::Alpha,
                     }
                 } else {
                     // Text runs drawn to blur never get drawn with subpixel AA.
                     BlendMode::Alpha
                 }
@@ -414,39 +415,46 @@ impl AlphaRenderItem {
                     sub_index: 0,
                     user_data: [0, 0],
                     z_sort_index: z,
                 };
 
                 match prim_metadata.prim_kind {
                     PrimitiveKind::Border => {
                         let border_cpu = &ctx.prim_store.cpu_borders[prim_metadata.cpu_prim_index.0];
-                        if border_cpu.use_new_border_path {
-                            // TODO(gw): Select correct blend mode for edges and corners!!
-                            let corner_key = AlphaBatchKey::new(AlphaBatchKind::BorderCorner, flags, blend_mode, textures);
-                            let edge_key = AlphaBatchKey::new(AlphaBatchKind::BorderEdge, flags, blend_mode, textures);
+                        // TODO(gw): Select correct blend mode for edges and corners!!
+                        let corner_key = AlphaBatchKey::new(AlphaBatchKind::BorderCorner, flags, blend_mode, textures);
+                        let edge_key = AlphaBatchKey::new(AlphaBatchKind::BorderEdge, flags, blend_mode, textures);
 
-                            batch_list.with_suitable_batch(&corner_key, item_bounding_rect, |batch| {
-                                for border_segment in 0..4 {
-                                    batch.add_instance(base_instance.build(border_segment, 0, 0));
+                        batch_list.with_suitable_batch(&corner_key, item_bounding_rect, |batch| {
+                            for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate() {
+                                let sub_index = i as i32;
+                                match *instance_kind {
+                                    BorderCornerInstance::Single => {
+                                        batch.add_instance(base_instance.build(sub_index,
+                                                                               BorderCornerSide::Both as i32,
+                                                                               0));
+                                    }
+                                    BorderCornerInstance::Double => {
+                                        batch.add_instance(base_instance.build(sub_index,
+                                                                               BorderCornerSide::First as i32,
+                                                                               0));
+                                        batch.add_instance(base_instance.build(sub_index,
+                                                                               BorderCornerSide::Second as i32,
+                                                                               0));
+                                    }
                                 }
-                            });
+                            }
+                        });
 
-                            batch_list.with_suitable_batch(&edge_key, item_bounding_rect, |batch| {
-                                for border_segment in 0..4 {
-                                    batch.add_instance(base_instance.build(border_segment, 0, 0));
-                                }
-                            });
-                        } else {
-                            let key = AlphaBatchKey::new(AlphaBatchKind::Border, flags, blend_mode, textures);
-                            let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
-                            for border_segment in 0..8 {
+                        batch_list.with_suitable_batch(&edge_key, item_bounding_rect, |batch| {
+                            for border_segment in 0..4 {
                                 batch.add_instance(base_instance.build(border_segment, 0, 0));
                             }
-                        }
+                        });
                     }
                     PrimitiveKind::Rectangle => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::Rectangle, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance);
                     }
                     PrimitiveKind::Image => {
                         let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
@@ -470,17 +478,17 @@ impl AlphaRenderItem {
                         };
 
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         batch.add_instance(base_instance.build(0, image_cpu.resource_address.0, 0));
                     }
                     PrimitiveKind::TextRun => {
                         let text_cpu = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-                        let batch_kind = if text_cpu.blur_radius.0 == 0 {
+                        let batch_kind = if text_cpu.blur_radius == 0.0 {
                             AlphaBatchKind::TextRun
                         } else {
                             // Select a generic primitive shader that can blit the
                             // results of the cached text blur to the framebuffer,
                             // applying tile clipping etc.
                             AlphaBatchKind::CacheImage
                         };
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
@@ -638,24 +646,26 @@ impl AlphaBatcher {
 
 /// Batcher managing draw calls into the clip mask (in the RT cache).
 #[derive(Debug)]
 pub struct ClipBatcher {
     /// Rectangle draws fill up the rectangles with rounded corners.
     pub rectangles: Vec<CacheClipInstance>,
     /// Image draws apply the image masking.
     pub images: HashMap<SourceTexture, Vec<CacheClipInstance>>,
+    pub border_clears: Vec<CacheClipInstance>,
     pub borders: Vec<CacheClipInstance>,
 }
 
 impl ClipBatcher {
     fn new() -> ClipBatcher {
         ClipBatcher {
             rectangles: Vec::new(),
             images: HashMap::new(),
+            border_clears: Vec::new(),
             borders: Vec::new(),
         }
     }
 
     fn add<'a>(&mut self,
                task_index: RenderTaskIndex,
                clips: &[(PackedLayerIndex, MaskCacheInfo)],
                resource_cache: &ResourceCache,
@@ -712,20 +722,26 @@ impl ClipBatcher {
                            .or_insert(Vec::new())
                            .push(CacheClipInstance {
                     address: address,
                     ..instance
                 })
             }
 
             for &(ref source, gpu_address) in &info.border_corners {
-                for dash_index in 0..source.dash_count {
+                self.border_clears.push(CacheClipInstance {
+                    address: gpu_address,
+                    segment: 0,
+                    ..instance
+                });
+
+                for clip_index in 0..source.actual_clip_count {
                     self.borders.push(CacheClipInstance {
                         address: gpu_address,
-                        segment: dash_index as i32,
+                        segment: 1 + clip_index as i32,
                         ..instance
                     })
                 }
             }
         }
     }
 }
 
@@ -910,20 +926,20 @@ impl RenderTarget for ColorRenderTarget 
     }
 
     fn add_task(&mut self,
                 task: RenderTask,
                 ctx: &RenderTargetContext,
                 render_tasks: &RenderTaskCollection,
                 pass_index: RenderPassIndex) {
         match task.kind {
-            RenderTaskKind::Alpha(info) => {
+            RenderTaskKind::Alpha(mut info) => {
                 self.alpha_batcher.add_task(AlphaBatchTask {
                     task_id: task.id,
-                    items: info.items,
+                    items: mem::replace(&mut info.items, Vec::new()),
                 });
             }
             RenderTaskKind::VerticalBlur(_, prim_index) => {
                 // Find the child render task that we are applying
                 // a vertical blur on.
                 // TODO(gw): Consider a simpler way for render tasks to find
                 //           their child tasks than having to construct the
                 //           correct id here.
@@ -963,17 +979,17 @@ impl RenderTarget for ColorRenderTarget 
                             sub_index: 0,
                             user_data: [0; 2],
                             z_sort_index: 0,        // z is disabled for rendering cache primitives
                         });
                     }
                     PrimitiveKind::TextRun => {
                         let text = &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
                         // We only cache text runs with a text-shadow (for now).
-                        debug_assert!(text.blur_radius.0 != 0);
+                        debug_assert!(text.blur_radius != 0.0);
 
                         // TODO(gw): This should always be fine for now, since the texture
                         // atlas grows to 4k. However, it won't be a problem soon, once
                         // we switch the texture atlas to use texture layers!
                         let textures = BatchTextures {
                             colors: ctx.prim_store.get_color_textures(prim_metadata),
                         };
 
@@ -1175,17 +1191,16 @@ pub enum AlphaBatchKind {
     Composite,
     HardwareComposite,
     SplitComposite,
     Blend,
     Rectangle,
     TextRun,
     Image(ImageBufferKind),
     YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
-    Border,
     AlignedGradient,
     AngleGradient,
     RadialGradient,
     BoxShadow,
     CacheImage,
     BorderCorner,
     BorderEdge,
 }
@@ -1507,16 +1522,25 @@ impl CompositeOps {
                 &LowLevelFilterOp::Opacity(Au(0)) => return true,
                 _ => {}
             }
         }
         false
     }
 }
 
+impl Default for CompositeOps {
+    fn default() -> CompositeOps {
+        CompositeOps {
+            filters: Vec::new(),
+            mix_blend_mode: None,
+        }
+    }
+}
+
 /// A rendering-oriented representation of frame::Frame built by the render backend
 /// and presented to the renderer.
 pub struct Frame {
     pub window_size: DeviceUintSize,
     pub background_color: Option<ColorF>,
     pub device_pixel_ratio: f32,
     pub cache_size: DeviceUintSize,
     pub passes: Vec<RenderPass>,
@@ -1534,9 +1558,8 @@ pub struct Frame {
     pub gpu_resource_rects: Vec<TexelRect>,
 
     // List of textures that we don't know about yet
     // from the backend thread. The render thread
     // will use a callback to resolve these and
     // patch the data structures.
     pub deferred_resolves: Vec<DeferredResolve>,
 }
-
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -1,73 +1,73 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use std::f32::consts::{FRAC_1_SQRT_2};
 use euclid::{Point2D, Rect, Size2D};
-use euclid::{TypedRect, TypedPoint2D, TypedSize2D, TypedPoint4D, TypedMatrix4D};
+use euclid::{TypedRect, TypedPoint2D, TypedSize2D, TypedMatrix4D};
 use webrender_traits::{DeviceIntRect, DeviceIntPoint, DeviceIntSize};
 use webrender_traits::{LayerRect, WorldPoint4D, LayerPoint4D, LayerToWorldTransform};
 use webrender_traits::{BorderRadius, ComplexClipRegion, LayoutRect};
 use num_traits::Zero;
 
+// Matches the definition of SK_ScalarNearlyZero in Skia.
+const NEARLY_ZERO: f32 = 1.0 / 4096.0;
+
 // TODO: Implement these in euclid!
 pub trait MatrixHelpers<Src, Dst> {
-    fn transform_point_and_perspective_project(&self, point: &TypedPoint4D<f32, Src>) -> TypedPoint2D<f32, Dst>;
     fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst>;
-
-    /// Returns true if this matrix transforms an axis-aligned 2D rectangle to another axis-aligned
-    /// 2D rectangle.
-    fn can_losslessly_transform_a_2d_rect(&self) -> bool;
-
-    /// Returns true if this matrix transforms an axis-aligned 2D rectangle to another axis-
-    /// aligned 2D rectangle after perspective divide.
-    fn can_losslessly_transform_and_perspective_project_a_2d_rect(&self) -> bool;
-
-    /// Clears out the portions of the matrix that `transform_rect()` uses. This allows the use of
-    /// `transform_rect()` while keeping the Z/W transform portions of the matrix intact.
-    fn reset_after_transforming_rect(&self) -> TypedMatrix4D<f32, Src, Dst>;
-
     fn is_identity(&self) -> bool;
+    fn preserves_2d_axis_alignment(&self) -> bool;
 }
 
 impl<Src, Dst> MatrixHelpers<Src, Dst> for TypedMatrix4D<f32, Src, Dst> {
-    fn transform_point_and_perspective_project(&self, point: &TypedPoint4D<f32, Src>) -> TypedPoint2D<f32, Dst> {
-        let point = self.transform_point4d(point);
-        TypedPoint2D::new(point.x / point.w, point.y / point.w)
-    }
-
     fn transform_rect(&self, rect: &TypedRect<f32, Src>) -> TypedRect<f32, Dst> {
         let top_left = self.transform_point(&rect.origin);
         let top_right = self.transform_point(&rect.top_right());
         let bottom_left = self.transform_point(&rect.bottom_left());
         let bottom_right = self.transform_point(&rect.bottom_right());
         TypedRect::from_points(&[top_left, top_right, bottom_right, bottom_left])
     }
 
-    fn can_losslessly_transform_a_2d_rect(&self) -> bool {
-        self.m12 == 0.0 && self.m14 == 0.0 && self.m21 == 0.0 && self.m24 == 0.0 && self.m44 == 1.0
-    }
-
-    fn can_losslessly_transform_and_perspective_project_a_2d_rect(&self) -> bool {
-        self.m12 == 0.0 && self.m21 == 0.0
+    fn is_identity(&self) -> bool {
+        *self == TypedMatrix4D::identity()
     }
 
-    fn reset_after_transforming_rect(&self) -> TypedMatrix4D<f32, Src, Dst> {
-        TypedMatrix4D::row_major(
-            1.0,      0.0,      self.m13, 0.0,
-            0.0,      1.0,      self.m23, 0.0,
-            self.m31, self.m32, self.m33, self.m34,
-            0.0,      0.0,      self.m43, 1.0,
-        )
-    }
+    // A port of the preserves2dAxisAlignment function in Skia.
+    // Defined in the SkMatrix44 class.
+    fn preserves_2d_axis_alignment(&self) -> bool {
+        if self.m14 != 0.0 || self.m24 != 0.0 {
+            return false;
+        }
+
+        let mut col0 = 0;
+        let mut col1 = 0;
+        let mut row0 = 0;
+        let mut row1 = 0;
 
-    fn is_identity(&self) -> bool {
-        *self == TypedMatrix4D::identity()
+        if self.m11.abs() > NEARLY_ZERO {
+            col0 += 1;
+            row0 += 1;
+        }
+        if self.m12.abs() > NEARLY_ZERO {
+            col1 += 1;
+            row0 += 1;
+        }
+        if self.m21.abs() > NEARLY_ZERO {
+            col0 += 1;
+            row1 += 1;
+        }
+        if self.m22.abs() > NEARLY_ZERO {
+            col1 += 1;
+            row1 += 1;
+        }
+
+        col0 < 2 && col1 < 2 && row0 < 2 && row1 < 2
     }
 }
 
 pub trait RectHelpers<U> where Self: Sized {
     fn contains_rect(&self, other: &Self) -> bool;
     fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
     fn is_well_formed_and_nonempty(&self) -> bool;
 }
@@ -166,17 +166,17 @@ pub struct TransformedRect {
     pub kind: TransformedRectKind,
 }
 
 impl TransformedRect {
     pub fn new(rect: &LayerRect,
                transform: &LayerToWorldTransform,
                device_pixel_ratio: f32) -> TransformedRect {
 
-        let kind = if transform.can_losslessly_transform_and_perspective_project_a_2d_rect() {
+        let kind = if transform.preserves_2d_axis_alignment() {
             TransformedRectKind::AxisAligned
         } else {
             TransformedRectKind::Complex
         };
 
         // FIXME(gw): This code is meant to be a fast path for simple transforms.
         // However, it fails on transforms that translate Z but result in an
         // axis aligned rect.
--- a/gfx/webrender_traits/Cargo.toml
+++ b/gfx/webrender_traits/Cargo.toml
@@ -1,28 +1,28 @@
 [package]
 name = "webrender_traits"
-version = "0.36.0"
+version = "0.39.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"]
 
 [dependencies]
 app_units = "0.4"
 bincode = "1.0.0-alpha2"
 byteorder = "1.0"
 euclid = "0.11"
-gleam = "0.4"
+gleam = "0.4.5"
 heapsize = "0.3.6"
-ipc-channel = {version = "0.7", optional = true}
+ipc-channel = {version = "0.7.2", optional = true}
 offscreen_gl_context = {version = "0.8", features = ["serde"], optional = true}
 serde = "0.9"
 serde_derive = "0.9"
 time = "0.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.3"
 core-graphics = "0.7"
--- a/gfx/webrender_traits/src/api.rs
+++ b/gfx/webrender_traits/src/api.rs
@@ -3,20 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 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 {BuiltDisplayList, BuiltDisplayListDescriptor};
-use {ClipId, ColorF, DeviceIntPoint, DeviceIntSize, DeviceUintRect, DeviceUintSize, FontKey};
-use {GlyphDimensions, GlyphKey, ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize};
-use {LayoutTransform, NativeFontHandle, WorldPoint};
+use {BuiltDisplayList, BuiltDisplayListDescriptor, ClipId, ColorF, DeviceIntPoint, DeviceIntSize};
+use {DeviceUintRect, DeviceUintSize, FontKey, GlyphDimensions, GlyphKey};
+use {ImageData, ImageDescriptor, ImageKey, LayoutPoint, LayoutSize, LayoutTransform};
+use {NativeFontHandle, WorldPoint};
 #[cfg(feature = "webgl")]
 use {WebGLCommand, WebGLContextId};
 
 pub type TileSize = u16;
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     AddRawFont(FontKey, Vec<u8>, u32),
@@ -29,29 +29,31 @@ pub enum ApiMsg {
     /// Updates the the resource cache with the new image data.
     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 from the payload channel.
+    // TODO: We should consider using named members to avoid confusion.
     SetDisplayList(Option<ColorF>,
                    Epoch,
                    PipelineId,
-                   LayoutSize,
+                   LayoutSize, // viewport_size
+                   LayoutSize, // content size
                    BuiltDisplayListDescriptor,
                    bool),
     SetPageZoom(ZoomFactor),
     SetPinchZoom(ZoomFactor),
     SetPan(DeviceIntPoint),
     SetRootPipeline(PipelineId),
     SetWindowParameters(DeviceUintSize, DeviceUintRect),
     Scroll(ScrollLocation, WorldPoint, ScrollEventPhase),
-    ScrollNodeWithId(LayoutPoint, ClipId),
+    ScrollNodeWithId(LayoutPoint, ClipId, ScrollClamping),
     TickScrollingBounce,
     TranslatePointToLayerSpace(WorldPoint, MsgSender<(LayoutPoint, PipelineId)>),
     GetScrollNodeState(MsgSender<Vec<ScrollLayerState>>),
     RequestWebGLContext(DeviceIntSize, GLContextAttributes, MsgSender<Result<(WebGLContextId, GLLimits), String>>),
     ResizeWebGLContext(WebGLContextId, DeviceIntSize),
     WebGLCommand(WebGLContextId, WebGLCommand),
     GenerateFrame(Option<DynamicProperties>),
     // WebVR commands that must be called in the WebGL render thread.
@@ -140,16 +142,21 @@ pub struct ExternalEvent {
 unsafe impl Send for ExternalEvent {}
 
 impl ExternalEvent {
     pub fn from_raw(raw: usize) -> Self { ExternalEvent { raw: raw } }
     /// Consumes self to make it obvious that the event should be forwarded only once.
     pub fn unwrap(self) -> usize { self.raw }
 }
 
+#[derive(Clone, Deserialize, Serialize)]
+pub enum ScrollClamping {
+    ToContentBounds,
+    NoClamping,
+}
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct RenderApiSender {
     api_sender: MsgSender<ApiMsg>,
     payload_sender: PayloadSender,
 }
 
 impl RenderApiSender {
@@ -286,55 +293,57 @@ impl RenderApi {
     /// Note: Scrolling doesn't require an own Frame.
     ///
     /// Arguments:
     ///
     /// * `background_color`: The background color of this pipeline.
     /// * `epoch`: The unique Frame ID, monotonically increasing.
     /// * `viewport_size`: The size of the viewport for this frame.
     /// * `pipeline_id`: The ID of the pipeline that is supplying this display list.
+    /// * `content_size`: The total screen space size of this display list's display items.
     /// * `display_list`: The root Display list used in this frame.
     /// * `preserve_frame_state`: If a previous frame exists which matches this pipeline
     ///                           id, this setting determines if frame state (such as scrolling
     ///                           position) should be preserved for this new display list.
     ///
     /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
     pub fn set_display_list(&self,
                             background_color: Option<ColorF>,
                             epoch: Epoch,
                             viewport_size: LayoutSize,
-                            (pipeline_id, display_list): (PipelineId, BuiltDisplayList),
+                            (pipeline_id, content_size, display_list): (PipelineId, LayoutSize, BuiltDisplayList),
                             preserve_frame_state: bool) {
-        let (dl_data, dl_desc) = display_list.into_data();
+        let (display_list_data, display_list_descriptor) = display_list.into_data();
         let msg = ApiMsg::SetDisplayList(background_color,
-                                             epoch,
-                                             pipeline_id,
-                                             viewport_size,
-                                             dl_desc,
-                                             preserve_frame_state);
+                                         epoch,
+                                         pipeline_id,
+                                         viewport_size,
+                                         content_size,
+                                         display_list_descriptor,
+                                         preserve_frame_state);
         self.api_sender.send(msg).unwrap();
 
         self.payload_sender.send_payload(Payload {
             epoch: epoch,
             pipeline_id: pipeline_id,
-            display_list_data: dl_data,
+            display_list_data: display_list_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);
         self.api_sender.send(msg).unwrap();
     }
 
-    pub fn scroll_node_with_id(&self, new_scroll_origin: LayoutPoint, id: ClipId) {
-        let msg = ApiMsg::ScrollNodeWithId(new_scroll_origin, id);
+    pub fn scroll_node_with_id(&self, origin: LayoutPoint, id: ClipId, clamp: ScrollClamping) {
+        let msg = ApiMsg::ScrollNodeWithId(origin, id, clamp);
         self.api_sender.send(msg).unwrap();
     }
 
     pub fn set_page_zoom(&self, page_zoom: ZoomFactor) {
         let msg = ApiMsg::SetPageZoom(page_zoom);
         self.api_sender.send(msg).unwrap();
     }
 
--- a/gfx/webrender_traits/src/channel.rs
+++ b/gfx/webrender_traits/src/channel.rs
@@ -45,18 +45,17 @@ impl Payload {
         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();
 
-        // TODO(new-ipc): assert_eq!(payload_reader.position(), data.len() as u64);
-
+        assert_eq!(payload_reader.position(), data.len() as u64);
 
         Payload {
             epoch: epoch,
             pipeline_id: pipeline_id,
             display_list_data: built_display_list_data,
         }
     }
 }
--- a/gfx/webrender_traits/src/color.rs
+++ b/gfx/webrender_traits/src/color.rs
@@ -7,16 +7,27 @@
 pub struct ColorF {
     pub r: f32,
     pub g: f32,
     pub b: f32,
     pub a: f32,
 }
 known_heap_size!(0, ColorF);
 
+impl ColorF {
+    pub fn premultiplied(&self) -> ColorF {
+        ColorF {
+            r: self.r * self.a,
+            g: self.g * self.a,
+            b: self.b * self.a,
+            a: self.a,
+        }
+    }
+}
+
 #[repr(C)]
 #[derive(Clone, Copy, Hash, Eq, Debug, Deserialize, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct ColorU {
     pub r: u8,
     pub g: u8,
     pub b: u8,
     pub a: u8,
 }
--- a/gfx/webrender_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -76,17 +76,17 @@ pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct TextDisplayItem {
     pub font_key: FontKey,
     pub size: Au,
     pub color: ColorF,
-    pub blur_radius: Au,
+    pub blur_radius: f32,
     pub glyph_options: Option<GlyphOptions>,
 } // IMPLICIT: glyphs: Vec<GlyphInstance>
 
 #[derive(Clone, Copy, Debug, Deserialize, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize)]
 pub struct GlyphOptions {
     // These are currently only used on windows for dwrite fonts.
     pub use_embedded_bitmap: bool,
     pub force_gdi_rendering: bool,
@@ -282,18 +282,18 @@ pub enum ScrollPolicy {
     Fixed       = 1,
 }
 
 known_heap_size!(0, ScrollPolicy);
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum TransformStyle {
-    Flat,
-    Preserve3D,
+    Flat        = 0,
+    Preserve3D  = 1,
 }
 
 #[repr(u32)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum MixBlendMode {
     Normal      = 0,
     Multiply    = 1,
     Screen      = 2,
@@ -366,48 +366,53 @@ impl YuvColorSpace {
             YuvColorSpace::Rec601 => "YUV_REC601",
             YuvColorSpace::Rec709 => "YUV_REC709",
         }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum YuvData {
-    NV12(ImageKey, ImageKey),
-    PlanarYCbCr(ImageKey, ImageKey, ImageKey),
+    NV12(ImageKey, ImageKey),   // (Y channel, CbCr interleaved channel)
+    PlanarYCbCr(ImageKey, ImageKey, ImageKey),  // (Y channel, Cb channel, Cr Channel)
+    InterleavedYCbCr(ImageKey), // (YCbCr interleaved channel)
 }
 
 impl YuvData {
     pub fn get_format(&self) -> YuvFormat {
         match *self {
             YuvData::NV12(..) => YuvFormat::NV12,
             YuvData::PlanarYCbCr(..) => YuvFormat::PlanarYCbCr,
+            YuvData::InterleavedYCbCr(..) => YuvFormat::InterleavedYCbCr,
         }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub enum YuvFormat {
     NV12 = 0,
     PlanarYCbCr = 1,
+    InterleavedYCbCr = 2,
 }
-pub const YUV_FORMATS: [YuvFormat; 2] = [YuvFormat::NV12, YuvFormat::PlanarYCbCr];
+pub const YUV_FORMATS: [YuvFormat; 3] = [YuvFormat::NV12, YuvFormat::PlanarYCbCr, YuvFormat::InterleavedYCbCr];
 
 impl YuvFormat {
     pub fn get_plane_num(&self) -> usize {
         match *self {
             YuvFormat::NV12 => 2,
             YuvFormat::PlanarYCbCr => 3,
+            YuvFormat::InterleavedYCbCr => 1,
         }
     }
 
     pub fn get_feature_string(&self) -> &'static str {
         match *self {
             YuvFormat::NV12 => "NV12",
             YuvFormat::PlanarYCbCr => "",
+            YuvFormat::InterleavedYCbCr => "INTERLEAVED_Y_CB_CR"
         }
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageMask {
     pub image: ImageKey,
     pub rect: LayoutRect,
@@ -618,8 +623,10 @@ macro_rules! define_empty_heap_size_of {
             fn heap_size_of_children(&self) -> usize { 0 }
         }
     }
 }
 
 define_empty_heap_size_of!(ClipId);
 define_empty_heap_size_of!(RepeatMode);
 define_empty_heap_size_of!(ImageKey);
+define_empty_heap_size_of!(MixBlendMode);
+define_empty_heap_size_of!(TransformStyle);
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -3,23 +3,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use bincode;
 use serde::{Deserialize, Serialize, Serializer};
 use serde::ser::{SerializeSeq, SerializeMap};
 use time::precise_time_ns;
 use {BorderDetails, BorderDisplayItem, BorderWidths, BoxShadowClipMode, BoxShadowDisplayItem};
-use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ClipRegion, ColorF, ComplexClipRegion, DisplayItem};
-use {ExtendMode, FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient, GradientDisplayItem};
-use {GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask, ImageRendering};
-use {LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, MixBlendMode, PipelineId};
-use {PropertyBinding, PushStackingContextDisplayItem, RadialGradient, RadialGradientDisplayItem};
-use {RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem, StackingContext, TextDisplayItem};
-use {TransformStyle, WebGLContextId, WebGLDisplayItem, YuvColorSpace, YuvData, YuvImageDisplayItem};
+use {ClipAndScrollInfo, ClipDisplayItem, ClipId, ClipRegion, ColorF, ComplexClipRegion};
+use {DisplayItem, ExtendMode, FilterOp, FontKey, GlyphInstance, GlyphOptions, Gradient};
+use {GradientDisplayItem, GradientStop, IframeDisplayItem, ImageDisplayItem, ImageKey, ImageMask};
+use {ImageRendering, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, MixBlendMode};
+use {PipelineId, PropertyBinding, PushStackingContextDisplayItem, RadialGradient};
+use {RadialGradientDisplayItem, RectangleDisplayItem, ScrollPolicy, SpecificDisplayItem};
+use {StackingContext, TextDisplayItem, TransformStyle, WebGLContextId, WebGLDisplayItem};
+use {YuvColorSpace, YuvData, YuvImageDisplayItem};
 use std::marker::PhantomData;
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
 pub struct ItemRange<T> {
     start: usize,
     length: usize,
     _boo: PhantomData<T>,
@@ -421,29 +422,36 @@ impl<'a, 'b> Serialize for DisplayItemRe
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub data: Vec<u8>,
     pub pipeline_id: PipelineId,
     clip_stack: Vec<ClipAndScrollInfo>,
     next_clip_id: u64,
     builder_start_time: u64,
+
+    /// The size of the content of this display list. This is used to allow scrolling
+    /// outside the bounds of the display list items themselves.
+    content_size: LayoutSize,
 }
 
 impl DisplayListBuilder {
-    pub fn new(pipeline_id: PipelineId) -> DisplayListBuilder {
+    pub fn new(pipeline_id: PipelineId, content_size: LayoutSize) -> DisplayListBuilder {
         let start_time = precise_time_ns();
+
+        // We start at 1 here, because the root scroll id is always 0.
+        const FIRST_CLIP_ID : u64 = 1;
+
         DisplayListBuilder {
             data: Vec::with_capacity(1024 * 1024),
             pipeline_id: pipeline_id,
             clip_stack: vec![ClipAndScrollInfo::simple(ClipId::root_scroll_node(pipeline_id))],
-
-            // We start at 1 here, because the root scroll id is always 0.
-            next_clip_id: 1,
+            next_clip_id: FIRST_CLIP_ID,
             builder_start_time: start_time,
+            content_size: content_size,
         }
     }
 
     pub fn print_display_list(&mut self) {
         let mut temp = BuiltDisplayList::default();
         ::std::mem::swap(&mut temp.data, &mut self.data);
 
         {
@@ -547,17 +555,17 @@ impl DisplayListBuilder {
 
     pub fn push_text(&mut self,
                      rect: LayoutRect,
                      _token: ClipRegionToken,
                      glyphs: &[GlyphInstance],
                      font_key: FontKey,
                      color: ColorF,
                      size: Au,
-                     blur_radius: Au,
+                     blur_radius: f32,
                      glyph_options: Option<GlyphOptions>) {
         // Sanity check - anything with glyphs bigger than this
         // is probably going to consume too much memory to render
         // efficiently anyway. This is specifically to work around
         // the font_advance.html reftest, which creates a very large
         // font as a crash test - the rendering is also ignored
         // by the azure renderer.
         if size < Au::from_px(4096) {
@@ -948,25 +956,26 @@ impl DisplayListBuilder {
         self.push_new_empty_item(SpecificDisplayItem::SetClipRegion(
             ClipRegion::new(rect, image_mask),
         ));
         self.push_iter(complex);
 
         ClipRegionToken { _unforgeable: () }
     }
 
-    pub fn finalize(self) -> (PipelineId, BuiltDisplayList) {
+    pub fn finalize(self) -> (PipelineId, LayoutSize, BuiltDisplayList) {
         let end_time = precise_time_ns();
 
         (self.pipeline_id,
+         self.content_size,
          BuiltDisplayList {
             descriptor: BuiltDisplayListDescriptor {
-            display_list_items_size: self.data.len(),
-            builder_start_time: self.builder_start_time,
-            builder_finish_time: end_time,
+                display_list_items_size: self.data.len(),
+                builder_start_time: self.builder_start_time,
+                builder_finish_time: end_time,
             },
             data: self.data,
          })
     }
 }
 
 /// Verification that push_clip_region was called before
 /// pushing an item that requires it.
--- a/gfx/webrender_traits/src/image.rs
+++ b/gfx/webrender_traits/src/image.rs
@@ -18,16 +18,17 @@ impl ImageKey {
 
 /// An arbitrary identifier for an external image provided by the
 /// application. It must be a unique identifier for each external
 /// image.
 #[repr(C)]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct ExternalImageId(pub u64);
 
+#[repr(u32)]
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
 pub enum ExternalImageType {
     Texture2DHandle,        // gl TEXTURE_2D handle
     TextureRectHandle,      // gl TEXTURE_RECT handle
     TextureExternalHandle,  // gl TEXTURE_EXTERNAL handle
     ExternalBuffer,
 }
 
--- a/gfx/webrender_traits/src/webgl.rs
+++ b/gfx/webrender_traits/src/webgl.rs
@@ -58,25 +58,27 @@ pub enum WebGLCommand {
     BindTexture(u32, Option<WebGLTextureId>),
     DisableVertexAttribArray(u32),
     DrawArrays(u32, i32, i32),
     DrawElements(u32, i32, u32, i64),
     EnableVertexAttribArray(u32),
     FramebufferRenderbuffer(u32, u32, u32, Option<WebGLRenderbufferId>),
     FramebufferTexture2D(u32, u32, u32, Option<WebGLTextureId>, i32),
     GetBufferParameter(u32, u32, MsgSender<WebGLResult<WebGLParameter>>),
+    GetExtensions(MsgSender<String>),
     GetParameter(u32, MsgSender<WebGLResult<WebGLParameter>>),
     GetProgramParameter(WebGLProgramId, u32, MsgSender<WebGLResult<WebGLParameter>>),
     GetShaderParameter(WebGLShaderId, u32, MsgSender<WebGLResult<WebGLParameter>>),
     GetShaderPrecisionFormat(u32, u32, MsgSender<WebGLResult<(i32, i32, i32)>>),
     GetActiveAttrib(WebGLProgramId, u32, MsgSender<WebGLResult<(i32, u32, String)>>),
     GetActiveUniform(WebGLProgramId, u32, MsgSender<WebGLResult<(i32, u32, String)>>),
     GetAttribLocation(WebGLProgramId, String, MsgSender<Option<i32>>),
     GetUniformLocation(WebGLProgramId, String, MsgSender<Option<i32>>),
     GetVertexAttrib(u32, u32, MsgSender<WebGLResult<WebGLParameter>>),
+    GetVertexAttribOffset(u32, u32, MsgSender<WebGLResult<isize>>),
     GetShaderInfoLog(WebGLShaderId, MsgSender<String>),
     GetProgramInfoLog(WebGLProgramId, MsgSender<String>),
     PolygonOffset(f32, f32),
     RenderbufferStorage(u32, u32, i32, i32),
     ReadPixels(i32, i32, i32, i32, u32, u32, MsgSender<Vec<u8>>),
     SampleCoverage(f32, bool),
     Scissor(i32, i32, i32, i32),
     StencilFunc(u32, i32, u32),
@@ -119,22 +121,25 @@ pub enum WebGLCommand {
     TexParameteri(u32, u32, i32),
     TexParameterf(u32, u32, f32),
     TexSubImage2D(u32, i32, i32, i32, i32, i32, u32, u32, Vec<u8>),
     DrawingBufferWidth(MsgSender<i32>),
     DrawingBufferHeight(MsgSender<i32>),
     Finish(MsgSender<()>),
     Flush,
     GenerateMipmap(u32),
+    CreateVertexArray(MsgSender<Option<WebGLVertexArrayId>>),
+    DeleteVertexArray(WebGLVertexArrayId),
+    BindVertexArray(Option<WebGLVertexArrayId>),
 }
 
 #[cfg(feature = "nightly")]
 macro_rules! define_resource_id_struct {
     ($name:ident) => {
-        #[derive(Clone, Copy, PartialEq)]
+        #[derive(Clone, Copy, Eq, Hash, PartialEq)]
         pub struct $name(NonZero<u32>);
 
         impl $name {
             #[inline]
             unsafe fn new(id: u32) -> Self {
                 $name(NonZero::new(id))
             }
 
@@ -145,17 +150,17 @@ macro_rules! define_resource_id_struct {
         }
 
     };
 }
 
 #[cfg(not(feature = "nightly"))]
 macro_rules! define_resource_id_struct {
     ($name:ident) => {
-        #[derive(Clone, Copy, PartialEq)]
+        #[derive(Clone, Copy, Eq, Hash, PartialEq)]
         pub struct $name(u32);
 
         impl $name {
             #[inline]
             unsafe fn new(id: u32) -> Self {
                 $name(id)
             }
 
@@ -215,16 +220,17 @@ macro_rules! define_resource_id {
 }
 
 define_resource_id!(WebGLBufferId);
 define_resource_id!(WebGLFramebufferId);
 define_resource_id!(WebGLRenderbufferId);
 define_resource_id!(WebGLTextureId);
 define_resource_id!(WebGLProgramId);
 define_resource_id!(WebGLShaderId);
+define_resource_id!(WebGLVertexArrayId);
 
 #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
 pub struct WebGLContextId(pub usize);
 
 impl ::heapsize::HeapSizeOf for WebGLContextId {
     fn heap_size_of_children(&self) -> usize { 0 }
 }
 
@@ -312,27 +318,29 @@ impl fmt::Debug for WebGLCommand {
             BindTexture(..) => "BindTexture",
             DisableVertexAttribArray(..) => "DisableVertexAttribArray",
             DrawArrays(..) => "DrawArrays",
             DrawElements(..) => "DrawElements",
             EnableVertexAttribArray(..) => "EnableVertexAttribArray",
             FramebufferRenderbuffer(..) => "FramebufferRenderbuffer",
             FramebufferTexture2D(..) => "FramebufferTexture2D",
             GetBufferParameter(..) => "GetBufferParameter",
+            GetExtensions(..) => "GetExtensions",
             GetParameter(..) => "GetParameter",
             GetProgramParameter(..) => "GetProgramParameter",
             GetShaderParameter(..) => "GetShaderParameter",
             GetShaderPrecisionFormat(..) => "GetShaderPrecisionFormat",
             GetActiveAttrib(..) => "GetActiveAttrib",
             GetActiveUniform(..) => "GetActiveUniform",
             GetAttribLocation(..) => "GetAttribLocation",
             GetUniformLocation(..) => "GetUniformLocation",
             GetShaderInfoLog(..) => "GetShaderInfoLog",
             GetProgramInfoLog(..) => "GetProgramInfoLog",
             GetVertexAttrib(..) => "GetVertexAttrib",
+            GetVertexAttribOffset(..) => "GetVertexAttribOffset",
             PolygonOffset(..) => "PolygonOffset",
             ReadPixels(..) => "ReadPixels",
             RenderbufferStorage(..) => "RenderbufferStorage",
             SampleCoverage(..) => "SampleCoverage",
             Scissor(..) => "Scissor",
             StencilFunc(..) => "StencilFunc",
             StencilFuncSeparate(..) => "StencilFuncSeparate",
             StencilMask(..) => "StencilMask",
@@ -373,16 +381,19 @@ impl fmt::Debug for WebGLCommand {
             TexParameteri(..) => "TexParameteri",
             TexParameterf(..) => "TexParameterf",
             TexSubImage2D(..) => "TexSubImage2D",
             DrawingBufferWidth(..) => "DrawingBufferWidth",
             DrawingBufferHeight(..) => "DrawingBufferHeight",
             Finish(..) => "Finish",
             Flush => "Flush",
             GenerateMipmap(..) => "GenerateMipmap",
+            CreateVertexArray(..) => "CreateVertexArray",
+            DeleteVertexArray(..) => "DeleteVertexArray",
+            BindVertexArray(..) => "BindVertexArray"
         };
 
         write!(f, "CanvasWebGLMsg::{}(..)", name)
     }
 }
 
 impl WebGLCommand {
     /// NOTE: This method consumes the command
@@ -485,26 +496,30 @@ impl WebGLCommand {
             WebGLCommand::GetActiveAttrib(program_id, index, chan) =>
                 Self::active_attrib(ctx.gl(), program_id, index, chan),
             WebGLCommand::GetActiveUniform(program_id, index, chan) =>
                 Self::active_uniform(ctx.gl(), program_id, index, chan),
             WebGLCommand::GetAttribLocation(program_id, name, chan) =>
                 Self::attrib_location(ctx.gl(), program_id, name, chan),
             WebGLCommand::GetVertexAttrib(index, pname, chan) =>
                 Self::vertex_attrib(ctx.gl(), index, pname, chan),
+            WebGLCommand::GetVertexAttribOffset(index, pname, chan) =>
+                Self::vertex_attrib_offset(ctx.gl(), index, pname, chan),
             WebGLCommand::GetBufferParameter(target, param_id, chan) =>
                 Self::buffer_parameter(ctx.gl(), target, param_id, chan),
             WebGLCommand::GetParameter(param_id, chan) =>
                 Self::parameter(ctx.gl(), param_id, chan),
             WebGLCommand::GetProgramParameter(program_id, param_id, chan) =>
                 Self::program_parameter(ctx.gl(), program_id, param_id, chan),
             WebGLCommand::GetShaderParameter(shader_id, param_id, chan) =>
                 Self::shader_parameter(ctx.gl(), shader_id, param_id, chan),
             WebGLCommand::GetShaderPrecisionFormat(shader_type, precision_type, chan) =>
                 Self::shader_precision_format(ctx.gl(), shader_type, precision_type, chan),
+            WebGLCommand::GetExtensions(chan) =>
+                Self::get_extensions(ctx.gl(), chan),
             WebGLCommand::GetUniformLocation(program_id, name, chan) =>
                 Self::uniform_location(ctx.gl(), program_id, name, chan),
             WebGLCommand::GetShaderInfoLog(shader_id, chan) =>
                 Self::shader_info_log(ctx.gl(), shader_id, chan),
             WebGLCommand::GetProgramInfoLog(program_id, chan) =>
                 Self::program_info_log(ctx.gl(), program_id, chan),
             WebGLCommand::CompileShader(shader_id, source) =>
                 Self::compile_shader(ctx.gl(), shader_id, source),
@@ -605,16 +620,22 @@ impl WebGLCommand {
             WebGLCommand::DrawingBufferHeight(sender) =>
                 sender.send(ctx.borrow_draw_buffer().unwrap().size().height).unwrap(),
             WebGLCommand::Finish(sender) =>
                 Self::finish(ctx.gl(), sender),
             WebGLCommand::Flush =>
                 ctx.gl().flush(),
             WebGLCommand::GenerateMipmap(target) =>
                 ctx.gl().generate_mipmap(target),
+            WebGLCommand::CreateVertexArray(chan) =>
+                Self::create_vertex_array(ctx.gl(), chan),
+            WebGLCommand::DeleteVertexArray(id) =>
+                ctx.gl().delete_vertex_arrays(&[id.get()]),
+            WebGLCommand::BindVertexArray(id) =>
+                ctx.gl().bind_vertex_array(id.map_or(0, WebGLVertexArrayId::get)),
         }
 
         // FIXME: Use debug_assertions once tests are run with them
         let error = ctx.gl().get_error();
         assert!(error == gl::NO_ERROR, "Unexpected WebGL error: 0x{:x} ({})", error, error);
     }
 
     fn read_pixels(gl: &gl::Gl, x: i32, y: i32, width: i32, height: i32, format: u32, pixel_type: u32,
@@ -810,16 +831,28 @@ impl WebGLCommand {
                 // gl::VERTEX_ATTRIB_ARRAY_BUFFER_BINDING should return WebGLBuffer
                 _ => Err(WebGLError::InvalidEnum),
             }
         };
 
         chan.send(result).unwrap();
     }
 
+    fn vertex_attrib_offset(gl: &gl::Gl,
+                            index: u32,
+                            pname: u32,
+                            chan: MsgSender<WebGLResult<isize>>) {
+        let result = match pname {
+                gl::VERTEX_ATTRIB_ARRAY_POINTER => Ok(gl.get_vertex_attrib_pointer_v(index, pname)),
+                _ => Err(WebGLError::InvalidEnum),
+        };
+
+        chan.send(result).unwrap();
+    }
+
     fn buffer_parameter(gl: &gl::Gl,
                         target: u32,
                         param_id: u32,
                         chan: MsgSender<WebGLResult<WebGLParameter>>) {
         let result = match param_id {
             gl::BUFFER_SIZE |
             gl::BUFFER_USAGE =>
                 Ok(WebGLParameter::Int(gl.get_buffer_parameter_iv(target, param_id))),
@@ -881,16 +914,20 @@ impl WebGLCommand {
             _=> {
                 Err(WebGLError::InvalidEnum)
             }
         };
 
         chan.send(result).unwrap();
     }
 
+    fn get_extensions(gl: &gl::Gl, chan: MsgSender<String>) {
+        chan.send(gl.get_string(gl::EXTENSIONS)).unwrap();
+    }
+
     fn uniform_location(gl: &gl::Gl,
                         program_id: WebGLProgramId,
                         name: String,
                         chan: MsgSender<Option<i32>>) {
         let location = gl.get_uniform_location(program_id.get(), &name);
         let location = if location == -1 {
             None
         } else {
@@ -968,16 +1005,26 @@ impl WebGLCommand {
         let shader = if shader == 0 {
             None
         } else {
             Some(unsafe { WebGLShaderId::new(shader) })
         };
         chan.send(shader).unwrap();
     }
 
+    fn create_vertex_array(gl: &gl::Gl, chan: MsgSender<Option<WebGLVertexArrayId>>) {
+        let vao = gl.gen_vertex_arrays(1)[0];
+        let vao = if vao == 0 {
+            None
+        } else {
+            Some(unsafe { WebGLVertexArrayId::new(vao) })
+        };
+        chan.send(vao).unwrap();
+    }
+
     #[inline]
     fn bind_framebuffer<Native: NativeGLContextMethods>(gl: &gl::Gl,
                                                         target: u32,
                                                         request: WebGLFramebufferBindingRequest,
                                                         ctx: &GLContext<Native>) {
         let id = match request {
             WebGLFramebufferBindingRequest::Explicit(id) => id.get(),
             WebGLFramebufferBindingRequest::Default =>