Bug 1359744 - Update webrender to 8516d6c04235e684d9bf9c783ba4fc99dab3bf02. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 03 May 2017 19:03:18 -0400
changeset 357037 d655c89236300fb49729f64d653e8d8929209ace
parent 357036 438c49f67c639f96b4120500d21f3ef278406b02
child 357038 853b94881813ea04fb96270e136b87a357722a93
push id31780
push userkwierso@gmail.com
push dateMon, 08 May 2017 20:34:47 +0000
treeherdermozilla-central@bab7046ee2d8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1359744
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1359744 - Update webrender to 8516d6c04235e684d9bf9c783ba4fc99dab3bf02. r=jrmuizel This updates the Cargo.lock files and revendors third_party/rust as well.
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/res/clip_shared.glsl
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_image.fs.glsl
gfx/webrender/res/cs_clip_image.glsl
gfx/webrender/res/cs_clip_image.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_angle_gradient.fs.glsl
gfx/webrender/res/ps_angle_gradient.glsl
gfx/webrender/res/ps_angle_gradient.vs.glsl
gfx/webrender/res/ps_blend.fs.glsl
gfx/webrender/res/ps_blend.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.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_gradient.fs.glsl
gfx/webrender/res/ps_gradient.glsl
gfx/webrender/res/ps_gradient.vs.glsl
gfx/webrender/res/ps_image.fs.glsl
gfx/webrender/res/ps_image.glsl
gfx/webrender/res/ps_image.vs.glsl
gfx/webrender/res/ps_radial_gradient.fs.glsl
gfx/webrender/res/ps_radial_gradient.glsl
gfx/webrender/res/ps_radial_gradient.vs.glsl
gfx/webrender/res/ps_rectangle.fs.glsl
gfx/webrender/res/ps_rectangle.glsl
gfx/webrender/res/ps_rectangle.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_text_run.fs.glsl
gfx/webrender/res/ps_text_run.glsl
gfx/webrender/res/ps_text_run.vs.glsl
gfx/webrender/res/ps_yuv_image.fs.glsl
gfx/webrender/res/ps_yuv_image.glsl
gfx/webrender/res/ps_yuv_image.vs.glsl
gfx/webrender/src/border.rs
gfx/webrender/src/clip_scroll_node.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/gpu_store.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/mask_cache.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/tiling.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/units.rs
third_party/rust/binary-space-partition/.cargo-checksum.json
third_party/rust/binary-space-partition/.cargo-ok
third_party/rust/binary-space-partition/.gitignore
third_party/rust/binary-space-partition/.travis.yml
third_party/rust/binary-space-partition/Cargo.toml
third_party/rust/binary-space-partition/LICENSE
third_party/rust/binary-space-partition/README.md
third_party/rust/binary-space-partition/src/lib.rs
third_party/rust/euclid/.cargo-checksum.json
third_party/rust/euclid/Cargo.toml
third_party/rust/euclid/README.md
third_party/rust/euclid/src/length.rs
third_party/rust/euclid/src/lib.rs
third_party/rust/euclid/src/matrix2d.rs
third_party/rust/euclid/src/matrix4d.rs
third_party/rust/euclid/src/point.rs
third_party/rust/euclid/src/rect.rs
third_party/rust/euclid/src/size.rs
third_party/rust/plane-split/.cargo-checksum.json
third_party/rust/plane-split/.cargo-ok
third_party/rust/plane-split/.gitignore
third_party/rust/plane-split/.travis.yml
third_party/rust/plane-split/Cargo.toml
third_party/rust/plane-split/LICENSE
third_party/rust/plane-split/README.md
third_party/rust/plane-split/benches/split.rs
third_party/rust/plane-split/src/bsp.rs
third_party/rust/plane-split/src/lib.rs
third_party/rust/plane-split/src/naive.rs
third_party/rust/plane-split/tests/main.rs
third_party/rust/plane-split/tests/split.rs
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
--- 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: b9f7e2926731f0ed831108456c219cae30a7e4c8
+Latest Commit: 8516d6c04235e684d9bf9c783ba4fc99dab3bf02
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -12,29 +12,30 @@ freetype-lib = ["freetype/servo-freetype
 profiler = ["thread_profiler/thread_profiler"]
 webgl = ["offscreen_gl_context", "webrender_traits/webgl"]
 
 [dependencies]
 app_units = "0.4"
 bincode = "1.0.0-alpha6"
 bit-set = "0.4"
 byteorder = "1.0"
-euclid = "0.11"
+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"
 webrender_traits = {path = "../webrender_traits"}
 bitflags = "0.7"
 gamma-lut = "0.1"
 thread_profiler = "0.1.1"
+plane-split = "0.2.1"
 
 [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/res/clip_shared.glsl
+++ b/gfx/webrender/res/clip_shared.glsl
@@ -83,12 +83,14 @@ ClipVertexInfo write_clip_tile_vertex(Re
 
     vec4 layer_pos = get_layer_pos(actual_pos / uDevicePixelRatio, layer);
 
     // compute the point position in side the layer, in CSS space
     vec2 vertex_pos = actual_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
 
     gl_Position = uTransform * vec4(vertex_pos, 0.0, 1);
 
+    vLocalBounds = vec4(clipped_local_rect.p0, clipped_local_rect.p0 + clipped_local_rect.size);
+
     return ClipVertexInfo(layer_pos.xyw, actual_pos, clipped_local_rect);
 }
 
 #endif //WR_VERTEX_SHADER
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/cs_clip_border.fs.glsl
@@ -0,0 +1,30 @@
+#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 local_pos = vPos.xy / vPos.z;
+
+    // Get local space position relative to the clip center.
+    vec2 clip_relative_pos = local_pos - vClipCenter;
+
+    // Get the signed distances to the two clip lines.
+    float d0 = distance_to_line(vPoint_Tangent0.xy,
+                                vPoint_Tangent0.zw,
+                                clip_relative_pos);
+    float d1 = distance_to_line(vPoint_Tangent1.xy,
+                                vPoint_Tangent1.zw,
+                                clip_relative_pos);
+
+    // Get AA widths based on zoom / scale etc.
+    vec2 fw = fwidth(local_pos);
+    float afwidth = length(fw);
+
+    // Apply AA over half a device pixel for the clip.
+    float d = smoothstep(-0.5 * afwidth,
+                         0.5 * afwidth,
+                         max(d0, -d1));
+
+    oFragColor = vec4(d, 0.0, 0.0, 1.0);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/cs_clip_border.glsl
@@ -0,0 +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 vec2 vClipCenter;
+
+flat varying vec4 vPoint_Tangent0;
+flat varying vec4 vPoint_Tangent1;
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/cs_clip_border.vs.glsl
@@ -0,0 +1,68 @@
+#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/. */
+
+// Header for a border corner clip.
+struct BorderCorner {
+    RectWithSize rect;
+    vec2 clip_center;
+    vec2 sign_modifier;
+};
+
+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);
+}
+
+// Per-dash clip information.
+// TODO: Expand this to handle dots in the future!
+struct BorderClip {
+    vec4 point_tangent_0;
+    vec4 point_tangent_1;
+};
+
+BorderClip fetch_border_clip(int index) {
+    vec4 data[2] = fetch_data_2(index);
+    return BorderClip(data[0], data[1]);
+}
+
+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;
+
+    // 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);
+    world_pos.xyz /= world_pos.w;
+
+    // Scale into device pixels.
+    vec2 device_pos = world_pos.xy * uDevicePixelRatio;
+
+    // Position vertex within the render task area.
+    vec2 final_pos = device_pos -
+                     area.screen_origin_target_index.xy +
+                     area.task_bounds.xy;
+
+    // Calculate the local space position for this vertex.
+    vec4 layer_pos = get_layer_pos(world_pos.xy, layer);
+    vPos = layer_pos.xyw;
+
+    gl_Position = uTransform * vec4(final_pos, 0.0, 1.0);
+}
--- a/gfx/webrender/res/cs_clip_image.fs.glsl
+++ b/gfx/webrender/res/cs_clip_image.fs.glsl
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     float alpha = 1.f;
-    vec2 local_pos = init_transform_fs(vPos, vLocalRect, alpha);
+    vec2 local_pos = init_transform_fs(vPos, alpha);
 
     bool repeat_mask = false; //TODO
     vec2 clamped_mask_uv = repeat_mask ? fract(vClipMaskUv.xy) :
         clamp(vClipMaskUv.xy, vec2(0.0, 0.0), vec2(1.0, 1.0));
     vec2 source_uv = clamp(clamped_mask_uv * vClipMaskUvRect.zw + vClipMaskUvRect.xy,
         vClipMaskUvInnerRect.xy, vClipMaskUvInnerRect.zw);
     float clip_alpha = texture(sColor0, source_uv).r; //careful: texture has type A8
 
--- a/gfx/webrender/res/cs_clip_image.glsl
+++ b/gfx/webrender/res/cs_clip_image.glsl
@@ -1,10 +1,9 @@
 #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 RectWithSize vLocalRect;
 flat varying vec4 vClipMaskUvRect;
 flat varying vec4 vClipMaskUvInnerRect;
--- a/gfx/webrender/res/cs_clip_image.vs.glsl
+++ b/gfx/webrender/res/cs_clip_image.vs.glsl
@@ -21,17 +21,16 @@ void main(void) {
     ImageMaskData mask = fetch_mask_data(cci.data_index);
     RectWithSize local_rect = mask.local_rect;
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
                                                cci.segment_index);
 
-    vLocalRect = vi.clipped_local_rect;
     vPos = vi.local_pos;
 
     vClipMaskUv = vec3((vPos.xy / vPos.z - local_rect.p0) / local_rect.size, 0.0);
     vec2 texture_size = vec2(textureSize(sColor0, 0));
     vClipMaskUvRect = vec4(mask.uv_rect.p0, mask.uv_rect.size) / texture_size.xyxy;
     // applying a half-texel offset to the UV boundaries to prevent linear samples from the outside
     vec4 inner_rect = vec4(mask.uv_rect.p0, mask.uv_rect.p0 + mask.uv_rect.size);
     vClipMaskUvInnerRect = (inner_rect + vec4(0.5, 0.5, -0.5, -0.5)) / texture_size.xyxy;
--- a/gfx/webrender/res/cs_clip_rectangle.fs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.fs.glsl
@@ -31,17 +31,17 @@ float rounded_rect(vec2 pos) {
     //distance_from_border -= 0.5;
 
     return 1.0 - smoothstep(0.0, 1.0, distance_from_border);
 }
 
 
 void main(void) {
     float alpha = 1.f;
-    vec2 local_pos = init_transform_fs(vPos, vLocalRect, alpha);
+    vec2 local_pos = init_transform_fs(vPos, alpha);
 
     float clip_alpha = rounded_rect(local_pos);
 
     float combined_alpha = min(alpha, clip_alpha);
 
     // Select alpha or inverse alpha depending on clip in/out.
     float final_alpha = mix(combined_alpha, 1.0 - combined_alpha, vClipMode);
 
--- a/gfx/webrender/res/cs_clip_rectangle.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.glsl
@@ -1,11 +1,10 @@
 #line 1
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec3 vPos;
-flat varying RectWithSize vLocalRect;
 flat varying vec4 vClipRect;
 flat varying vec4 vClipRadius;
 flat varying float vClipMode;
--- a/gfx/webrender/res/cs_clip_rectangle.vs.glsl
+++ b/gfx/webrender/res/cs_clip_rectangle.vs.glsl
@@ -49,17 +49,16 @@ void main(void) {
     Layer layer = fetch_layer(cci.layer_index);
     ClipData clip = fetch_clip(cci.data_index);
     RectWithSize local_rect = clip.rect.rect;
 
     ClipVertexInfo vi = write_clip_tile_vertex(local_rect,
                                                layer,
                                                area,
                                                cci.segment_index);
-    vLocalRect = vi.clipped_local_rect;
     vPos = vi.local_pos;
 
     vClipMode = clip.rect.mode.x;
     vClipRect = vec4(local_rect.p0, local_rect.p0 + local_rect.size);
     vClipRadius = vec4(clip.top_left.outer_inner_radius.x,
                        clip.top_right.outer_inner_radius.x,
                        clip.bottom_right.outer_inner_radius.x,
                        clip.bottom_left.outer_inner_radius.x);
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -43,16 +43,18 @@
 #define UV_PIXEL         uint(1)
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
+uniform sampler2D sGradients;
+
 struct RectWithSize {
     vec2 p0;
     vec2 size;
 };
 
 struct RectWithEndpoint {
     vec2 p0;
     vec2 p1;
@@ -96,26 +98,34 @@ RectWithSize intersect_rect(RectWithSize
     return RectWithSize(p.xy, max(vec2(0.0), p.zw - p.xy));
 }
 
 RectWithEndpoint intersect_rect(RectWithEndpoint a, RectWithEndpoint b) {
     vec4 p = clamp_rect(vec4(a.p0, a.p1), b);
     return RectWithEndpoint(p.xy, max(p.xy, p.zw));
 }
 
+float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
+    vec2 dir_to_p0 = p0 - p;
+    return dot(normalize(perp_dir), dir_to_p0);
+}
 
 // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
 flat varying vec4 vClipMaskUvBounds;
 varying vec3 vClipMaskUv;
+#ifdef WR_FEATURE_TRANSFORM
+    flat varying vec4 vLocalBounds;
+#endif
 
 #ifdef WR_VERTEX_SHADER
 
 #define VECS_PER_LAYER             13
 #define VECS_PER_RENDER_TASK        3
 #define VECS_PER_PRIM_GEOM          2
+#define VECS_PER_SPLIT_GEOM         3
 
 uniform sampler2D sLayers;
 uniform sampler2D sRenderTasks;
 uniform sampler2D sPrimGeometry;
 
 uniform sampler2D sData16;
 uniform sampler2D sData32;
 uniform sampler2D sData64;
@@ -693,16 +703,18 @@ TransformVertexInfo write_transform_vert
     // Apply offsets for the render task to get correct screen location.
     vec2 final_pos = device_pos -
                      snap_delta -
                      task.screen_space_origin +
                      task.render_target_origin;
 
     gl_Position = uTransform * vec4(final_pos, z, 1.0);
 
+    vLocalBounds = vec4(local_rect.p0, local_rect.p1);
+
     vec4 layer_pos = get_layer_pos(device_pos / uDevicePixelRatio, layer);
 
     return TransformVertexInfo(layer_pos.xyw, device_pos);
 }
 
 #endif //WR_FEATURE_TRANSFORM
 
 struct ResourceRect {
@@ -772,48 +784,49 @@ void write_clip(vec2 global_pos, ClipAre
     vec2 texture_size = vec2(textureSize(sCacheA8, 0).xy);
     vec2 uv = global_pos + area.task_bounds.xy - area.screen_origin_target_index.xy;
     vClipMaskUvBounds = area.task_bounds / texture_size.xyxy;
     vClipMaskUv = vec3(uv / texture_size, area.screen_origin_target_index.z);
 }
 #endif //WR_VERTEX_SHADER
 
 #ifdef WR_FRAGMENT_SHADER
+
+#ifdef WR_FEATURE_TRANSFORM
 float signed_distance_rect(vec2 pos, vec2 p0, vec2 p1) {
     vec2 d = max(p0 - pos, pos - p1);
     return length(max(vec2(0.0), d)) + min(0.0, max(d.x, d.y));
 }
 
-vec2 init_transform_fs(vec3 local_pos, RectWithSize local_rect, out float fragment_alpha) {
+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,
-                                   local_rect.p0 + dxdy,
-                                   local_rect.p0 + local_rect.size - dxdy);
+    float d = signed_distance_rect(pos, vLocalBounds.xy + dxdy, vLocalBounds.zw - dxdy);
 
     // Find the appropriate distance to apply the AA smoothstep over.
     float afwidth = 0.5 / length(fw);
 
     // Only apply AA to fragments outside the signed distance field.
     fragment_alpha = 1.0 - smoothstep(0.0, afwidth, d);
 
     return pos;
 }
+#endif //WR_FEATURE_TRANSFORM
 
 float do_clip() {
     // anything outside of the mask is considered transparent
     bvec4 inside = lessThanEqual(
         vec4(vClipMaskUvBounds.xy, vClipMaskUv.xy),
         vec4(vClipMaskUv.xy, vClipMaskUvBounds.zw));
     // check for the dummy bounds, which are given to the opaque objects
     return vClipMaskUvBounds.xy == vClipMaskUvBounds.zw ? 1.0:
@@ -829,11 +842,33 @@ vec4 dither(vec4 color) {
     float noise = (noise_normalized - 0.5) / 256.0; // scale down to the unit length
 
     return color + vec4(noise, noise, noise, 0);
 }
 #else
 vec4 dither(vec4 color) {
     return color;
 }
-#endif
+#endif //WR_FEATURE_DITHERING
+
+vec4 sample_gradient(float offset, float gradient_repeat, float gradient_index, vec2 gradient_size) {
+    // Either saturate or modulo the offset depending on repeat mode
+    float x = mix(clamp(offset, 0.0, 1.0), fract(offset), gradient_repeat);
+
+    // Scale to the number of gradient color entries (texture width / 2).
+    x = x * 0.5 * gradient_size.x;
+
+    // Calculate the texel to index into the gradient color entries:
+    //     floor(x) is the gradient color entry index
+    //     fract(x) is the linear filtering factor between start and end
+    //     so, 2 * floor(x) + 0.5 is the center of the start color
+    //     finally, add floor(x) to interpolate to end
+    x = 2.0 * floor(x) + 0.5 + fract(x);
+
+    // 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));
+}
 
 #endif //WR_FRAGMENT_SHADER
--- a/gfx/webrender/res/ps_angle_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.fs.glsl
@@ -1,30 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-uniform sampler2D sGradients;
-
 void main(void) {
     vec2 pos = mod(vPos, vTileRepeat);
 
     if (pos.x >= vTileSize.x ||
         pos.y >= vTileSize.y) {
         discard;
     }
 
-    // Normalized offset of this vertex within the gradient, before clamp/repeat.
     float offset = dot(pos - vStartPoint, vScaledDir);
 
-    vec2 texture_size = vec2(textureSize(sGradients, 0));
-
-    // Either saturate or modulo the offset depending on repeat mode, then scale to number of
-    // gradient color entries (texture width / 2).
-    float x = mix(clamp(offset, 0.0, 1.0), fract(offset), vGradientRepeat) * 0.5 * texture_size.x;
-
-    x = 2.0 * floor(x) + 0.5 + fract(x);
-
-    // Use linear filtering to mix in the low bits (vGradientIndex + 1) with the high
-    // bits (vGradientIndex)
-    float y = vGradientIndex * 2.0 + 0.5 + 1.0 / 256.0;
-    oFragColor = dither(texture(sGradients, vec2(x, y) / texture_size));
+    oFragColor = sample_gradient(offset,
+                                 vGradientRepeat,
+                                 vGradientIndex,
+                                 vGradientTextureSize);
 }
--- a/gfx/webrender/res/ps_angle_gradient.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.glsl
@@ -1,11 +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/. */
 
+flat varying float vGradientIndex;
+flat varying vec2 vGradientTextureSize;
+flat varying float vGradientRepeat;
+
 flat varying vec2 vScaledDir;
 flat varying vec2 vStartPoint;
-flat varying float vGradientIndex;
-flat varying float vGradientRepeat;
+
 flat varying vec2 vTileSize;
 flat varying vec2 vTileRepeat;
+
 varying vec2 vPos;
--- a/gfx/webrender/res/ps_angle_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_angle_gradient.vs.glsl
@@ -24,11 +24,14 @@ void main(void) {
     vScaledDir = dir / dot(dir, dir);
 
     vTileSize = gradient.tile_size_repeat.xy;
     vTileRepeat = gradient.tile_size_repeat.zw;
 
     // V coordinate of gradient row in lookup texture.
     vGradientIndex = float(prim.sub_index);
 
+    // The texture size of the lookup texture
+    vGradientTextureSize = vec2(textureSize(sGradients, 0));
+
     // Whether to repeat the gradient instead of clamping.
     vGradientRepeat = float(int(gradient.extend_mode.x) == EXTEND_MODE_REPEAT);
 }
--- a/gfx/webrender/res/ps_blend.fs.glsl
+++ b/gfx/webrender/res/ps_blend.fs.glsl
@@ -94,17 +94,18 @@ vec4 Brightness(vec4 Cs, float amount) {
     return vec4(Cs.rgb * amount, Cs.a);
 }
 
 vec4 Opacity(vec4 Cs, float amount) {
     return vec4(Cs.rgb, Cs.a * amount);
 }
 
 void main(void) {
-    vec4 Cs = texture(sCacheRGBA8, vUv);
+    vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
+    vec4 Cs = textureLod(sCacheRGBA8, vec3(uv, vUv.z), 0.0);
 
     if (Cs.a == 0.0) {
         discard;
     }
 
     switch (vOp) {
         case 0:
             // Gaussian blur is specially handled:
--- a/gfx/webrender/res/ps_blend.glsl
+++ b/gfx/webrender/res/ps_blend.glsl
@@ -1,7 +1,8 @@
 /* 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 float vAmount;
 flat varying int vOp;
--- a/gfx/webrender/res/ps_blend.vs.glsl
+++ b/gfx/webrender/res/ps_blend.vs.glsl
@@ -15,14 +15,15 @@ void main(void) {
     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);
 
     vOp = pi.sub_index;
     vAmount = float(pi.user_data.y) / 65535.0;
 
     gl_Position = uTransform * vec4(local_pos, pi.z, 1.0);
 }
--- a/gfx/webrender/res/ps_border.fs.glsl
+++ b/gfx/webrender/res/ps_border.fs.glsl
@@ -405,30 +405,30 @@ vec4 draw_complete_border(vec2 local_pos
   return vec4(0.0);
 }
 
 // TODO: Investigate performance of this shader and see
 //       if it's worthwhile splitting it / removing branches etc.
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
+    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.p0.x) +
-                                 (local_pos.y - vBorderRect.p0.y) -
-                                 0.5 * (vBorderRect.size.x + vBorderRect.size.y);
+    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());
 }
--- a/gfx/webrender/res/ps_border.glsl
+++ b/gfx/webrender/res/ps_border.glsl
@@ -3,30 +3,29 @@
 /* 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 RectWithSize vBorderRect; // The rect of the border in local space.
+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 RectWithSize vLocalRect;
 flat varying float vPieceRectHypotenuseLength;
 #else
 varying vec2 vLocalPos;     // The clamped position in local space.
 
 // These two are interpolated
 varying float vDistanceFromMixLine;  // This is the distance from the line where two colors
                                      // meet in border corners.
 varying float vDistanceFromMiddle;   // This is the distance from the line between the top
--- a/gfx/webrender/res/ps_border.vs.glsl
+++ b/gfx/webrender/res/ps_border.vs.glsl
@@ -2,34 +2,34 @@
 /* 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 = prim.local_rect;
+    vBorderRect = vec4(prim.local_rect.p0, prim.local_rect.size);
 
-    vec2 tl_outer = vBorderRect.p0;
+    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.p0.x + vBorderRect.size.x,
-                         vBorderRect.p0.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.p0.x + vBorderRect.size.x,
-                         vBorderRect.p0.y + vBorderRect.size.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.p0.x,
-                         vBorderRect.p0.y + vBorderRect.size.y);
+    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;
@@ -102,28 +102,26 @@ void main(void) {
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
-    vLocalPos = vi.local_pos;
-    vLocalRect = segment_rect;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
-    vLocalPos = vi.local_pos.xy;
 #endif
 
+    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;
@@ -180,13 +178,13 @@ void main(void) {
     // to properly mix border colors. For transformed borders, we calculate this distance
     // in the fragment shader itself. For non-transformed borders, we can use the
     // interpolator.
 #ifdef WR_FEATURE_TRANSFORM
     vPieceRectHypotenuseLength = sqrt(pow(width, 2.0) + pow(height, 2.0));
 #else
     vDistanceFromMixLine = (vi.local_pos.x - x0) * height -
                            (vi.local_pos.y - y0) * width;
-    vDistanceFromMiddle = (vi.local_pos.x - vBorderRect.p0.x) +
-                          (vi.local_pos.y - vBorderRect.p0.y) -
-                          0.5 * (vBorderRect.size.x + vBorderRect.size.y);
+    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
@@ -49,37 +49,32 @@ float sdEllipse( vec2 p, in vec2 ab ) {
 
     float si = sqrt( 1.0 - co*co );
 
     vec2 r = vec2( ab.x*co, ab.y*si );
 
     return length(r - p ) * sign(p.y-r.y);
 }
 
-float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
-    vec2 dir_to_p0 = p0 - p;
-    return dot(normalize(perp_dir), dir_to_p0);
-}
-
 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, vLocalRect, alpha);
+    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
 #else
     vec2 local_pos = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // Find the appropriate distance to apply the AA smoothstep over.
     vec2 fw = fwidth(local_pos);
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ b/gfx/webrender/res/ps_border_corner.glsl
@@ -17,13 +17,12 @@ flat varying vec4 vRadii1;
 flat varying vec2 vClipSign;
 flat varying vec4 vEdgeDistance;
 flat varying float vSDFSelect;
 
 // Border style
 flat varying float vAlphaSelect;
 
 #ifdef WR_FEATURE_TRANSFORM
-flat varying RectWithSize vLocalRect;
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_corner.vs.glsl
+++ b/gfx/webrender/res/ps_border_corner.vs.glsl
@@ -205,22 +205,20 @@ void main(void) {
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
-    vLocalPos = vi.local_pos;
-    vLocalRect = segment_rect;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
-    vLocalPos = vi.local_pos.xy;
 #endif
 
+    vLocalPos = vi.local_pos;
     write_clip(vi.screen_pos, prim.clip_area);
 }
--- a/gfx/webrender/res/ps_border_edge.fs.glsl
+++ b/gfx/webrender/res/ps_border_edge.fs.glsl
@@ -3,45 +3,47 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     float alpha = 1.0;
 #ifdef WR_FEATURE_TRANSFORM
     alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
+    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
 #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);
 
     // 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.
-    float pos = mix(local_pos.x, local_pos.y, vAxisSelect);
+    vec2 pos = mix(local_pos.xy, local_pos.yx, vAxisSelect);
 
     // Get signed distance from each of the inner edges.
-    float d0 = pos - vEdgeDistance.x;
-    float d1 = vEdgeDistance.y - pos;
+    float d0 = pos.x - vEdgeDistance.x;
+    float d1 = vEdgeDistance.y - pos.x;
 
     // SDF union to select both outer edges.
     float d = min(d0, d1);
 
     // Select fragment on/off based on signed distance.
     // No AA here, since we know we're on a straight edge
     // and the width is rounded to a whole CSS pixel.
     alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
 
     // Mix color based on first distance.
     // TODO(gw): Support AA for groove/ridge border edge with transforms.
     vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
 
-    //oFragColor = vec4(d0 * vEdgeDistance.y, -d0 * vEdgeDistance.y, 0, 1.0);
+    // Apply dashing parameters.
+    alpha = min(alpha, step(mod(pos.y - vDashParams.x, vDashParams.y), vDashParams.z));
+
     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,15 @@
  * 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;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying RectWithSize vLocalRect;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -51,69 +51,95 @@ 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,
+                       float border_width,
+                       float edge_length,
+                       float edge_offset) {
+    // x = offset
+    // y = dash on + off length
+    // z = dash length
+    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,
+                               2.0 * dash_length,
+                               dash_length);
+            break;
+        }
+        default:
+            vDashParams = vec3(1.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];
 
     RectWithSize segment_rect;
     switch (sub_part) {
         case 0:
             segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
             segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
             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);
             break;
         case 1:
             segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
             segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
             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);
             break;
         case 2:
             segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
             segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
             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);
             break;
         case 3:
             segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
             segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
             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);
             break;
     }
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
-    vLocalPos = vi.local_pos;
-    vLocalRect = segment_rect;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
-    vLocalPos = vi.local_pos;
 #endif
 
+    vLocalPos = vi.local_pos;
     write_clip(vi.screen_pos, prim.clip_area);
 }
--- a/gfx/webrender/res/ps_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_gradient.fs.glsl
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
-    vec2 local_pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
+    vec2 local_pos = init_transform_fs(vLocalPos, alpha);
 #else
     float alpha = 1.0;
     vec2 local_pos = vPos;
 #endif
 
     alpha = min(alpha, do_clip());
     oFragColor = dither(vColor * vec4(1.0, 1.0, 1.0, alpha));
 }
--- a/gfx/webrender/res/ps_gradient.glsl
+++ b/gfx/webrender/res/ps_gradient.glsl
@@ -1,12 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec4 vColor;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying RectWithSize vLocalRect;
 #else
 varying vec2 vPos;
 #endif
--- a/gfx/webrender/res/ps_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_gradient.vs.glsl
@@ -58,17 +58,16 @@ void main(void) {
 
 #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);
-    vLocalRect = segment_rect;
     vLocalPos = vi.local_pos;
     vec2 f = (vi.local_pos.xy - prim.local_rect.p0) / prim.local_rect.size;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
--- a/gfx/webrender/res/ps_image.fs.glsl
+++ b/gfx/webrender/res/ps_image.fs.glsl
@@ -2,21 +2,21 @@
 
 /* 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) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
-    vec2 pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
+    vec2 pos = init_transform_fs(vLocalPos, alpha);
 
     // We clamp the texture coordinate calculation here to the local rectangle boundaries,
     // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect = clamp_rect(pos, vLocalRect) - vLocalRect.p0;
+    vec2 relative_pos_in_rect = clamp(pos, vLocalBounds.xy, vLocalBounds.zw) - vLocalBounds.xy;
 #else
     float alpha = 1.0;
     vec2 relative_pos_in_rect = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We calculate the particular tile this fragment belongs to, taking into
--- a/gfx/webrender/res/ps_image.glsl
+++ b/gfx/webrender/res/ps_image.glsl
@@ -7,14 +7,12 @@
 // check GL_TEXTURE_RECTANGLE.
 flat varying vec2 vTextureOffset; // Offset of this image into the texture atlas.
 flat varying vec2 vTextureSize;   // Size of the image in the texture atlas.
 flat varying vec2 vTileSpacing;   // Amount of space between tiled instances of this image.
 flat varying vec4 vStRect;        // Rectangle of valid texture rect.
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying RectWithSize vLocalRect;
-flat varying vec2 vStretchSize;
 #else
 varying vec2 vLocalPos;
+#endif
 flat varying vec2 vStretchSize;
-#endif
--- a/gfx/webrender/res/ps_image.vs.glsl
+++ b/gfx/webrender/res/ps_image.vs.glsl
@@ -10,17 +10,16 @@ void main(void) {
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
-    vLocalRect = prim.local_rect;
     vLocalPos = vi.local_pos;
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
--- a/gfx/webrender/res/ps_radial_gradient.fs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.fs.glsl
@@ -1,14 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-uniform sampler2D sGradients;
-
 void main(void) {
     vec2 pos = mod(vPos, vTileRepeat);
 
     if (pos.x >= vTileSize.x ||
         pos.y >= vTileSize.y) {
         discard;
     }
 
@@ -17,50 +15,42 @@ void main(void) {
     float rd = vEndRadius - vStartRadius;
 
     // Solve for t in length(t * cd - pd) = vStartRadius + t * rd
     // using a quadratic equation in form of At^2 - 2Bt + C = 0
     float A = dot(cd, cd) - rd * rd;
     float B = dot(pd, cd) + vStartRadius * rd;
     float C = dot(pd, pd) - vStartRadius * vStartRadius;
 
-    float x;
+    float offset;
     if (A == 0.0) {
         // Since A is 0, just solve for -2Bt + C = 0
         if (B == 0.0) {
             discard;
         }
         float t = 0.5 * C / B;
         if (vStartRadius + rd * t >= 0.0) {
-            x = t;
+            offset = t;
         } else {
             discard;
         }
     } else {
         float discr = B * B - A * C;
         if (discr < 0.0) {
             discard;
         }
         discr = sqrt(discr);
         float t0 = (B + discr) / A;
         float t1 = (B - discr) / A;
         if (vStartRadius + rd * t0 >= 0.0) {
-            x = t0;
+            offset = t0;
         } else if (vStartRadius + rd * t1 >= 0.0) {
-            x = t1;
+            offset = t1;
         } else {
             discard;
         }
     }
 
-    vec2 texture_size = vec2(textureSize(sGradients, 0));
-
-    // Either saturate or modulo the offset depending on repeat mode, then scale to number of
-    // gradient color entries (texture width / 2).
-    x = mix(clamp(x, 0.0, 1.0), fract(x), vGradientRepeat) * 0.5 * texture_size.x;
-
-    x = 2.0 * floor(x) + 0.5 + fract(x);
-
-    // Use linear filtering to mix in the low bits (vGradientIndex + 1) with the high
-    // bits (vGradientIndex)
-    float y = vGradientIndex * 2.0 + 0.5 + 1.0 / 256.0;
-    oFragColor = dither(texture(sGradients, vec2(x, y) / texture_size));
+    oFragColor = sample_gradient(offset,
+                                 vGradientRepeat,
+                                 vGradientIndex,
+                                 vGradientTextureSize);
 }
--- a/gfx/webrender/res/ps_radial_gradient.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.glsl
@@ -1,13 +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/. */
 
 flat varying float vGradientIndex;
+flat varying vec2 vGradientTextureSize;
 flat varying float vGradientRepeat;
+
 flat varying vec2 vStartCenter;
 flat varying vec2 vEndCenter;
 flat varying float vStartRadius;
 flat varying float vEndRadius;
+
 flat varying vec2 vTileSize;
 flat varying vec2 vTileRepeat;
+
 varying vec2 vPos;
--- a/gfx/webrender/res/ps_radial_gradient.vs.glsl
+++ b/gfx/webrender/res/ps_radial_gradient.vs.glsl
@@ -32,11 +32,14 @@ void main(void) {
     vStartCenter.y *= ratio_xy;
     vEndCenter.y *= ratio_xy;
     vTileSize.y *= ratio_xy;
     vTileRepeat.y *= ratio_xy;
 
     // V coordinate of gradient row in lookup texture.
     vGradientIndex = float(prim.sub_index);
 
+    // The texture size of the lookup texture
+    vGradientTextureSize = vec2(textureSize(sGradients, 0));
+
     // Whether to repeat the gradient instead of clamping.
     vGradientRepeat = float(int(gradient.start_end_radius_ratio_xy_extend_mode.w) == EXTEND_MODE_REPEAT);
 }
--- a/gfx/webrender/res/ps_rectangle.fs.glsl
+++ b/gfx/webrender/res/ps_rectangle.fs.glsl
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     float alpha = 1.0;
 #ifdef WR_FEATURE_TRANSFORM
     alpha = 0.0;
-    init_transform_fs(vLocalPos, vLocalRect, alpha);
+    init_transform_fs(vLocalPos, alpha);
 #endif
 
 #ifdef WR_FEATURE_CLIP
     alpha = min(alpha, do_clip());
 #endif
     oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
 }
--- a/gfx/webrender/res/ps_rectangle.glsl
+++ b/gfx/webrender/res/ps_rectangle.glsl
@@ -1,10 +1,9 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 varying vec4 vColor;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying RectWithSize vLocalRect;
 #endif
--- a/gfx/webrender/res/ps_rectangle.vs.glsl
+++ b/gfx/webrender/res/ps_rectangle.vs.glsl
@@ -9,17 +9,16 @@ void main(void) {
     vColor = rect.color;
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
-    vLocalRect = prim.local_rect;
     vLocalPos = vi.local_pos;
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_split_composite.fs.glsl
@@ -0,0 +1,9 @@
+#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);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_split_composite.glsl
@@ -0,0 +1,7 @@
+#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;
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/res/ps_split_composite.vs.glsl
@@ -0,0 +1,48 @@
+#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/. */
+
+uniform sampler2D sSplitGeometry;
+
+struct SplitGeometry {
+    vec3 points[4];
+};
+
+SplitGeometry fetch_split_geometry(int index) {
+    ivec2 uv = get_fetch_uv(index, VECS_PER_SPLIT_GEOM);
+
+    vec4 data0 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(0, 0));
+    vec4 data1 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(1, 0));
+    vec4 data2 = texelFetchOffset(sSplitGeometry, uv, 0, ivec2(2, 0));
+
+    return SplitGeometry(vec3[4](
+        data0.xyz, vec3(data0.w, data1.xy),
+        vec3(data1.zw, data2.x), data2.yzw
+    ));
+}
+
+vec3 bilerp(vec3 a, vec3 b, vec3 c, vec3 d, float s, float t) {
+    vec3 x = mix(a, b, t);
+    vec3 y = mix(c, d, t);
+    return mix(x, y, s);
+}
+
+void main(void) {
+    PrimitiveInstance pi = fetch_prim_instance();
+    SplitGeometry geometry = fetch_split_geometry(pi.specific_prim_index);
+    AlphaBatchTask src_task = fetch_alpha_batch_task(pi.user_data.x);
+
+    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 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);
+}
--- a/gfx/webrender/res/ps_text_run.fs.glsl
+++ b/gfx/webrender/res/ps_text_run.fs.glsl
@@ -6,16 +6,16 @@ void main(void) {
     vec2 tc = clamp(vUv, vUvBorder.xy, vUvBorder.zw);
 #ifdef WR_FEATURE_SUBPIXEL_AA
     //note: the blend mode is not compatible with clipping
     oFragColor = texture(sColor0, tc);
 #else
     float alpha = texture(sColor0, tc).a;
 #ifdef WR_FEATURE_TRANSFORM
     float a = 0.0;
-    init_transform_fs(vLocalPos, vLocalRect, a);
+    init_transform_fs(vLocalPos, a);
     alpha *= a;
 #endif
     vec4 color = vColor;
     alpha = min(alpha, do_clip());
     oFragColor = vec4(vColor.rgb, vColor.a * alpha);
 #endif
 }
--- a/gfx/webrender/res/ps_text_run.glsl
+++ b/gfx/webrender/res/ps_text_run.glsl
@@ -3,10 +3,9 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 flat varying vec4 vColor;
 varying vec2 vUv;
 flat varying vec4 vUvBorder;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying RectWithSize vLocalRect;
 #endif
--- a/gfx/webrender/res/ps_text_run.vs.glsl
+++ b/gfx/webrender/res/ps_text_run.vs.glsl
@@ -14,17 +14,16 @@ void main(void) {
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     local_rect.p0);
-    vLocalRect = local_rect;
     vLocalPos = vi.local_pos;
     vec2 f = (vi.local_pos.xy / vi.local_pos.z - local_rect.p0) / local_rect.size;
 #else
     VertexInfo vi = write_vertex(local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
--- a/gfx/webrender/res/ps_yuv_image.fs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.fs.glsl
@@ -32,21 +32,21 @@ const mat3 YuvColorMatrix = mat3(
     1.16438, -0.21325, -0.53291,
     1.16438,  2.11240,  0.0
 );
 #endif
 
 void main(void) {
 #ifdef WR_FEATURE_TRANSFORM
     float alpha = 0.0;
-    vec2 pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
+    vec2 pos = init_transform_fs(vLocalPos, alpha);
 
     // We clamp the texture coordinate calculation here to the local rectangle boundaries,
     // which makes the edge of the texture stretch instead of repeat.
-    vec2 relative_pos_in_rect = clamp_rect(pos, vLocalRect) - vLocalRect.p0;
+    vec2 relative_pos_in_rect = clamp(pos, vLocalBounds.xy, vLocalBounds.zw) - vLocalBounds.xy;
 #else
     float alpha = 1.0;;
     vec2 relative_pos_in_rect = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // We clamp the texture coordinates to the half-pixel offset from the borders
--- a/gfx/webrender/res/ps_yuv_image.glsl
+++ b/gfx/webrender/res/ps_yuv_image.glsl
@@ -11,12 +11,11 @@ flat varying vec2 vTextureOffsetV; // Of
 flat varying vec2 vTextureSizeY;   // Size of the y plane in the texture atlas.
 flat varying vec2 vTextureSizeUv;  // Size of the u and v planes in the texture atlas.
 flat varying vec2 vStretchSize;
 flat varying vec2 vHalfTexelY;     // Normalized length of the half of a Y texel.
 flat varying vec2 vHalfTexelUv;    // Normalized length of the half of u and v texels.
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
-flat varying RectWithSize vLocalRect;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_yuv_image.vs.glsl
+++ b/gfx/webrender/res/ps_yuv_image.vs.glsl
@@ -7,17 +7,16 @@ void main(void) {
     Primitive prim = load_primitive();
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(prim.local_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
-    vLocalRect = prim.local_rect;
     vLocalPos = vi.local_pos;
 #else
     VertexInfo vi = write_vertex(prim.local_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,55 +1,69 @@
 /* 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 ellipse::Ellipse;
 use frame_builder::FrameBuilder;
-use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, PrimitiveContainer};
+use mask_cache::{ClipSource};
+use prim_store::{BorderPrimitiveCpu, BorderPrimitiveGpu, GpuBlock32, PrimitiveContainer};
 use tiling::PrimitiveFlags;
 use util::pack_as_float;
 use webrender_traits::{BorderSide, BorderStyle, BorderWidths, ClipAndScrollInfo, ClipRegion};
 use webrender_traits::{ColorF, LayerPoint, LayerRect, LayerSize, NormalBorder};
 
-#[derive(Copy, Clone, Debug, PartialEq)]
+enum BorderCorner {
+    TopLeft,
+    TopRight,
+    BottomLeft,
+    BottomRight,
+}
+
+#[derive(Clone, Debug, PartialEq)]
 pub enum BorderCornerKind {
     None,
     Solid,
     Clip,
+    Mask(BorderCornerClipData, LayerSize, LayerSize),
     Unhandled,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderEdgeKind {
     None,
     Solid,
     Clip,
     Unhandled,
 }
 
-pub trait NormalBorderHelpers {
+trait NormalBorderHelpers {
     fn get_corner(&self,
                   edge0: &BorderSide,
                   width0: f32,
                   edge1: &BorderSide,
                   width1: f32,
-                  radius: &LayerSize) -> BorderCornerKind;
+                  radius: &LayerSize,
+                  corner: BorderCorner,
+                  border_rect: &LayerRect) -> BorderCornerKind;
 
     fn get_edge(&self,
                 edge: &BorderSide,
                 width: f32) -> (BorderEdgeKind, f32);
 }
 
 impl NormalBorderHelpers for NormalBorder {
     fn get_corner(&self,
                   edge0: &BorderSide,
                   width0: f32,
                   edge1: &BorderSide,
                   width1: f32,
-                  radius: &LayerSize) -> BorderCornerKind {
+                  radius: &LayerSize,
+                  corner: BorderCorner,
+                  border_rect: &LayerRect) -> BorderCornerKind {
         // If either width is zero, a corner isn't formed.
         if width0 == 0.0 || width1 == 0.0 {
             return BorderCornerKind::None;
         }
 
         // If both edges are transparent, no corner is formed.
         if edge0.color.a == 0.0 && edge1.color.a == 0.0 {
             return BorderCornerKind::None;
@@ -73,16 +87,55 @@ impl NormalBorderHelpers for NormalBorde
             // 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))
+            }
+
             // 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,
@@ -103,32 +156,33 @@ impl NormalBorderHelpers for NormalBorde
             BorderStyle::Hidden => (BorderEdgeKind::None, 0.0),
 
             BorderStyle::Solid |
             BorderStyle::Inset |
             BorderStyle::Outset => (BorderEdgeKind::Solid, width),
 
             BorderStyle::Double |
             BorderStyle::Groove |
-            BorderStyle::Ridge => (BorderEdgeKind::Clip, width),
+            BorderStyle::Ridge |
+            BorderStyle::Dashed => (BorderEdgeKind::Clip, width),
 
-            BorderStyle::Dotted |
-            BorderStyle::Dashed => (BorderEdgeKind::Unhandled, width),
+            BorderStyle::Dotted => (BorderEdgeKind::Unhandled, 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) {
+                                   use_new_border_path: bool,
+                                   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);
@@ -158,17 +212,17 @@ impl FrameBuilder {
                 radius.bottom_right,
                 radius.bottom_left,
             ],
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            clip_region,
-                           &[],
+                           extra_clips,
                            PrimitiveContainer::Border(prim_cpu, prim_gpu));
     }
 
     // TODO(gw): This allows us to move border types over to the
     // simplified shader model one at a time. Once all borders
     // are converted, this can be removed, along with the complex
     // border code path.
     pub fn add_normal_border(&mut self,
@@ -188,30 +242,55 @@ impl FrameBuilder {
 
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
         let corners = [
-            border.get_corner(left, widths.left, top, widths.top, &radius.top_left),
-            border.get_corner(top, widths.top, right, widths.right, &radius.top_right),
-            border.get_corner(right, widths.right, bottom, widths.bottom, &radius.bottom_right),
-            border.get_corner(bottom, widths.bottom, left, widths.left, &radius.bottom_left),
+            border.get_corner(left,
+                              widths.left,
+                              top,
+                              widths.top,
+                              &radius.top_left,
+                              BorderCorner::TopLeft,
+                              rect),
+            border.get_corner(right,
+                              widths.right,
+                              top,
+                              widths.top,
+                              &radius.top_right,
+                              BorderCorner::TopRight,
+                              rect),
+            border.get_corner(right,
+                              widths.right,
+                              bottom,
+                              widths.bottom,
+                              &radius.bottom_right,
+                              BorderCorner::BottomRight,
+                              rect),
+            border.get_corner(left,
+                              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);
+                                             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);
 
@@ -224,17 +303,18 @@ impl FrameBuilder {
 
         // 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);
+                                             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
         });
@@ -279,22 +359,35 @@ impl FrameBuilder {
                 self.add_solid_rectangle(clip_and_scroll,
                                          &LayerRect::new(LayerPoint::new(p0.x, p1.y - bottom_len),
                                                          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();
+
+            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));
+                }
+            }
+
             self.add_normal_border_primitive(rect,
                                              border,
                                              widths,
                                              clip_and_scroll,
                                              clip_region,
-                                             true);
+                                             true,
+                                             &extra_clips);
         }
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self,
                     scale_factor_0: f32,
                     scale_factor_1: f32,
@@ -322,8 +415,132 @@ impl BorderSideHelpers for BorderSide {
                 } else {
                     ColorF::new(black_color_1, black_color_1, black_color_1, self.color.a)
                 }
             }
             _ => self.color,
         }
     }
 }
+
+/// 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,
+    ellipse: Ellipse,
+}
+
+impl BorderCornerClipSource {
+    pub fn new(corner_data: BorderCornerClipData,
+               corner_radius: LayerSize,
+               widths: LayerSize) -> BorderCornerClipSource {
+        let ellipse = Ellipse::new(corner_radius);
+
+        // 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();
+
+        // 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 * 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.
+        let actual_count = desired_count.ceil();
+
+        // Get the correct dash arc length.
+        let dash_arc_length = 0.5 * total_arc_length / actual_count;
+
+        // Get the number of dashes we'll need to fit.
+        let dash_count = actual_count as usize;
+
+        BorderCornerClipSource {
+            corner_data: corner_data,
+            dash_count: dash_count,
+            ellipse: ellipse,
+            dash_arc_length: dash_arc_length,
+        }
+    }
+
+    pub fn populate_gpu_data(&self, slice: &mut [GpuBlock32]) {
+        let (header, dashes) = 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;
+
+            let arc_length1 = current_arc_length;
+            current_arc_length += self.dash_arc_length;
+
+            let dash_data = BorderCornerDashClipData::new(arc_length0,
+                                                          arc_length1,
+                                                          &self.ellipse);
+            dashes[dash_index] = dash_data.into();
+        }
+    }
+}
+
+/// 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,
+}
+
+/// 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)]
+#[repr(C)]
+pub struct BorderCornerDashClipData {
+    pub point0: LayerPoint,
+    pub tangent0: LayerPoint,
+    pub point1: LayerPoint,
+    pub tangent1: LayerPoint,
+}
+
+impl BorderCornerDashClipData {
+    pub fn new(arc_length0: f32,
+               arc_length1: f32,
+               ellipse: &Ellipse) -> BorderCornerDashClipData {
+        let alpha = ellipse.find_angle_for_arc_length(arc_length0);
+        let beta = ellipse.find_angle_for_arc_length(arc_length1);
+
+        let (p0, t0) = ellipse.get_point_and_tangent(alpha);
+        let (p1, t1) = ellipse.get_point_and_tangent(beta);
+
+        BorderCornerDashClipData {
+            point0: p0,
+            tangent0: t0,
+            point1: p1,
+            tangent1: t1,
+        }
+    }
+}
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -266,16 +266,21 @@ impl ClipScrollNode {
         self.combined_local_viewport_rect = match self.node_type {
             NodeType::Clip(_) => {
                 parent_combined_viewport_in_local_space.intersection(&self.local_clip_rect)
                                                        .unwrap_or(LayerRect::zero())
             }
             NodeType::ReferenceFrame(_) => parent_combined_viewport_in_local_space,
         };
 
+        // HACK: prevent the code above for non-AA transforms, it's incorrect.
+        if (local_transform.m13, local_transform.m23) != (0.0, 0.0) {
+            self.combined_local_viewport_rect = self.local_clip_rect;
+        }
+
         // The transformation for this viewport in world coordinates is the transformation for
         // our parent reference frame, plus any accumulated scrolling offsets from nodes
         // between our reference frame and this node. For reference frames, we also include
         // whatever local transformation this reference frame provides. This can be combined
         // with the local_viewport_rect to get its position in world space.
         self.world_viewport_transform =
             parent_reference_frame_transform
                 .pre_translated(parent_accumulated_scroll_offset.x,
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1608,16 +1608,21 @@ impl Device {
             self.gl.uniform_1i(u_resource_rects, TextureSampler::ResourceRects as i32);
         }
 
         let u_gradients = self.gl.get_uniform_location(program.id, "sGradients");
         if u_gradients != -1 {
             self.gl.uniform_1i(u_gradients, TextureSampler::Gradients as i32);
         }
 
+        let u_split_geometry = self.gl.get_uniform_location(program.id, "sSplitGeometry");
+        if u_split_geometry != -1 {
+            self.gl.uniform_1i(u_split_geometry, TextureSampler::SplitGeometry as i32);
+        }
+
         Ok(())
     }
 
 /*
     pub fn refresh_shader(&mut self, path: PathBuf) {
         let mut vs_preamble_path = self.resource_path.clone();
         vs_preamble_path.push(VERTEX_SHADER_PREAMBLE);
 
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/ellipse.rs
@@ -0,0 +1,94 @@
+/* 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 webrender_traits::{LayerPoint, LayerSize};
+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,
+}
+
+impl Ellipse {
+    pub fn new(radius: LayerSize) -> Ellipse {
+        Ellipse {
+            radius: radius,
+        }
+    }
+
+    /// 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 {
+        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);
+
+            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)
+    }
+}
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -56,17 +56,16 @@ impl<'a> FlattenContext<'a> {
         }
     }
 }
 
 // TODO: doc
 pub struct Frame {
     pub clip_scroll_tree: ClipScrollTree,
     pub pipeline_epoch_map: HashMap<PipelineId, Epoch, BuildHasherDefault<FnvHasher>>,
-    pub pipeline_auxiliary_lists: AuxiliaryListsMap,
     id: FrameId,
     frame_builder_config: FrameBuilderConfig,
     frame_builder: Option<FrameBuilder>,
 }
 
 trait DisplayListHelpers {
     fn starting_stacking_context<'a>(&'a self) -> Option<(&'a StackingContext, &'a LayoutRect)>;
 }
@@ -221,17 +220,16 @@ fn clip_intersection(original_rect: &Lay
         })
     })
 }
 
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
         Frame {
             pipeline_epoch_map: HashMap::default(),
-            pipeline_auxiliary_lists: HashMap::default(),
             clip_scroll_tree: ClipScrollTree::new(),
             id: FrameId(0),
             frame_builder: None,
             frame_builder_config: config,
         }
     }
 
     pub fn reset(&mut self) -> ScrollStates {
@@ -291,17 +289,16 @@ impl Frame {
             None => return,
         };
 
         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_auxiliary_lists = scene.pipeline_auxiliary_lists.clone();
 
         self.pipeline_epoch_map.insert(root_pipeline_id, root_pipeline.epoch);
 
         let (root_stacking_context, root_bounds) = match display_list.starting_stacking_context() {
             Some(some) => some,
             None => {
                 warn!("Pipeline display list does not start with a stacking context.");
                 return;
@@ -376,19 +373,19 @@ impl Frame {
                                     stacking_context: &StackingContext) {
         // Avoid doing unnecessary work for empty stacking contexts.
         if traversal.current_stacking_context_empty() {
             traversal.skip_current_stacking_context();
             return;
         }
 
         let composition_operations = {
-            let auxiliary_lists = self.pipeline_auxiliary_lists
-                                      .get(&pipeline_id)
-                                      .expect("No auxiliary lists?!");
+            let auxiliary_lists = context.scene.pipeline_auxiliary_lists
+                                               .get(&pipeline_id)
+                                               .expect("No auxiliary lists?!");
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(auxiliary_lists, &context.scene.properties),
                 stacking_context.mix_blend_mode_for_compositing())
         };
 
         if composition_operations.will_make_invisible() {
             traversal.skip_current_stacking_context();
             return;
@@ -432,16 +429,17 @@ impl Frame {
                 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,
@@ -604,19 +602,19 @@ impl Frame {
                                              text_info.font_key,
                                              text_info.size,
                                              text_info.blur_radius,
                                              &text_info.color,
                                              text_info.glyphs,
                                              text_info.glyph_options);
                 }
                 SpecificDisplayItem::Rectangle(ref info) => {
-                    let auxiliary_lists = self.pipeline_auxiliary_lists
-                                              .get(&pipeline_id)
-                                              .expect("No auxiliary lists?!");
+                    let auxiliary_lists = context.scene.pipeline_auxiliary_lists
+                                                       .get(&pipeline_id)
+                                                       .expect("No auxiliary lists?!");
                     // Try to extract the opaque inner rectangle out of the clipped primitive.
                     if let Some(opaque_rect) = clip_intersection(&item.rect, &item.clip, auxiliary_lists) {
                         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,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -2,41 +2,42 @@
  * 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 frame::FrameId;
 use gpu_store::GpuStoreAddress;
 use internal_types::{HardwareCompositeOp, SourceTexture};
 use mask_cache::{ClipMode, ClipSource, MaskCacheInfo, RegionMode};
+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, TextRunPrimitiveCpu, TextRunPrimitiveGpu};
+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 resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
-use euclid::SideOffsets2D;
-use tiling::StackingContextIndex;
+use euclid::{SideOffsets2D, TypedPoint3D};
+use tiling::{ContextIsolation, StackingContextIndex};
 use tiling::{AuxiliaryListsMap, ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
 use util::{self, pack_as_float, subtract_rect, recycle_vec};
 use util::RectHelpers;
 use webrender_traits::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo};
 use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{DeviceUintRect, DeviceUintSize, ExtendMode, FontKey, FontRenderMode};
 use webrender_traits::{GlyphOptions, ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect};
 use webrender_traits::{LayerSize, LayerToScrollTransform, PipelineId, RepeatMode, TileOffset};
-use webrender_traits::{TransformStyle, WebGLContextId, YuvColorSpace, YuvData};
+use webrender_traits::{TransformStyle, WebGLContextId, WorldPixel, YuvColorSpace, YuvData};
 
 #[derive(Debug, Clone)]
 struct ImageBorderSegment {
     geom_rect: LayerRect,
     sub_rect: TexelRect,
     stretch_size: LayerSize,
     tile_spacing: LayerSize,
 }
@@ -76,16 +77,26 @@ impl ImageBorderSegment {
             geom_rect: rect,
             sub_rect: sub_rect,
             stretch_size: LayerSize::new(stretch_size_x, stretch_size_y),
             tile_spacing: tile_spacing,
         }
     }
 }
 
+/// 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;
+    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 debug: bool,
 }
 
 impl FrameBuilderConfig {
@@ -238,32 +249,40 @@ impl FrameBuilder {
         ClipScrollGroupIndex(self.clip_scroll_group_store.len() - 1, info)
     }
 
     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
                 // must be drawn with a transparent background, unless the parent stacking context
                 // is the root of the page
-                self.stacking_context_store[parent_index.0].should_isolate = true;
+                let isolation = &mut self.stacking_context_store[parent_index.0].isolation;
+                if *isolation != ContextIsolation::None {
+                    error!("Isolation conflict detected on {:?}: {:?}", parent_index, *isolation);
+                }
+                *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,
+                                                              reference_frame_id,
+                                                              local_bounds,
                                                               transform_style,
                                                               composite_ops));
         self.cmds.push(PrimitiveRunCmd::PushStackingContext(stacking_context_index));
         self.stacking_context_stack.push(stacking_context_index);
     }
 
     pub fn pop_stacking_context(&mut self) {
         self.cmds.push(PrimitiveRunCmd::PopStackingContext);
@@ -1099,73 +1118,131 @@ impl FrameBuilder {
             } else {
                 None
             };
             self.prim_store.set_clip_source(scrollbar_prim.prim_index, clip_source);
             *self.prim_store.gpu_geometry.get_mut(GpuStoreAddress(scrollbar_prim.prim_index.0 as i32)) = geom;
         }
     }
 
-    fn build_render_task(&self) -> (RenderTask, usize) {
+    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 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();
+        // The plane splitter, using a simple BSP tree.
+        let mut splitter = BspSplitter::new();
+
+        self.prim_store.gpu_split_geometry.clear();
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
                     sc_stack.push(stacking_context_index);
 
                     if !stacking_context.is_visible {
                         continue;
                     }
 
-                    let stacking_context_rect = &stacking_context.bounding_rect;
+                    let stacking_context_rect = &stacking_context.screen_bounds;
                     let composite_count = stacking_context.composite_ops.count();
 
-                    if composite_count == 0 && stacking_context.should_isolate {
-                        let location = RenderTaskLocation::Dynamic(None, stacking_context_rect.size);
-                        let new_task = RenderTask::new_alpha_batch(next_task_index,
-                                                                   stacking_context_rect.origin,
-                                                                   location);
+                    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 let Some(task) = new_task {
                         next_task_index.0 += 1;
-                        let prev_task = mem::replace(&mut current_task, new_task);
+                        let prev_task = mem::replace(&mut current_task, task);
                         alpha_task_stack.push(prev_task);
                     }
 
                     for _ in 0..composite_count {
-                        let location = RenderTaskLocation::Dynamic(None, stacking_context_rect.size);
-                        let new_task = RenderTask::new_alpha_batch(next_task_index,
-                                                                   stacking_context_rect.origin,
-                                                                   location);
+                        let new_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];
 
                     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;
+                        },
+                    }
+
                     let composite_count = stacking_context.composite_ops.count();
 
-                    if composite_count == 0 && stacking_context.should_isolate {
+                    if composite_count == 0 && stacking_context.isolation == ContextIsolation::Full {
                         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);
@@ -1178,20 +1255,21 @@ impl FrameBuilder {
                                                           current_task.id,
                                                           *filter,
                                                           next_z);
                         next_z += 1;
                         prev_task.as_alpha_batch().items.push(item);
                         prev_task.children.push(current_task);
                         current_task = prev_task;
                     }
+
                     if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
                         let readback_task =
                             RenderTask::new_readback(stacking_context_index,
-                                                     stacking_context.bounding_rect);
+                                                     stacking_context.screen_bounds);
 
                         let mut prev_task = alpha_task_stack.pop().unwrap();
                         let item = AlphaRenderItem::Composite(stacking_context_index,
                                                               readback_task.id,
                                                               current_task.id,
                                                               mix_blend_mode,
                                                               next_z);
                         next_z += 1;
@@ -1225,26 +1303,27 @@ impl FrameBuilder {
                             // Add any dynamic render tasks needed to render this primitive
                             if let Some(ref render_task) = prim_metadata.render_task {
                                 current_task.children.push(render_task.clone());
                             }
                             if let Some(ref clip_task) = prim_metadata.clip_task {
                                 current_task.children.push(clip_task.clone());
                             }
 
-                            let item = AlphaRenderItem::Primitive(group_index, prim_index, next_z);
+                            let item = AlphaRenderItem::Primitive(Some(group_index), prim_index, next_z);
                             current_task.as_alpha_batch().items.push(item);
                             next_z += 1;
                         }
                     }
                 }
             }
         }
 
         debug_assert!(alpha_task_stack.is_empty());
+        debug_assert!(preserve_3d_stack.is_empty());
         (current_task, next_task_index.0)
     }
 
     pub fn build(&mut self,
                  resource_cache: &mut ResourceCache,
                  frame_id: FrameId,
                  clip_scroll_tree: &mut ClipScrollTree,
                  auxiliary_lists_map: &AuxiliaryListsMap,
@@ -1274,17 +1353,17 @@ impl FrameBuilder {
 
         self.build_layer_screen_rects_and_cull_layers(&screen_rect,
                                                       clip_scroll_tree,
                                                       auxiliary_lists_map,
                                                       resource_cache,
                                                       &mut profile_counters,
                                                       device_pixel_ratio);
 
-        let (main_render_task, static_render_task_count) = self.build_render_task();
+        let (main_render_task, static_render_task_count) = self.build_render_task(clip_scroll_tree);
         let mut render_tasks = RenderTaskCollection::new(static_render_task_count);
 
         let mut required_pass_count = 0;
         main_render_task.max_depth(0, &mut required_pass_count);
 
         resource_cache.block_until_all_resources_added(texture_cache_profile);
 
         for node in clip_scroll_tree.nodes.values() {
@@ -1337,16 +1416,17 @@ impl FrameBuilder {
             layer_texture_data: self.packed_layers.clone(),
             render_task_data: render_tasks.render_task_data,
             gpu_data16: self.prim_store.gpu_data16.build(),
             gpu_data32: self.prim_store.gpu_data32.build(),
             gpu_data64: self.prim_store.gpu_data64.build(),
             gpu_data128: self.prim_store.gpu_data128.build(),
             gpu_geometry: self.prim_store.gpu_geometry.build(),
             gpu_gradient_data: self.prim_store.gpu_gradient_data.build(),
+            gpu_split_geometry: self.prim_store.gpu_split_geometry.build(),
             gpu_resource_rects: self.prim_store.gpu_resource_rects.build(),
             deferred_resolves: deferred_resolves,
         }
     }
 
 }
 
 struct LayerRectCalculationAndCullingPass<'a> {
@@ -1515,25 +1595,25 @@ impl<'a> LayerRectCalculationAndCullingP
     }
 
     fn handle_pop_stacking_context(&mut self) {
         let stacking_context_index = self.stacking_context_stack.pop().unwrap();
 
         let (bounding_rect, is_visible) = {
             let stacking_context =
                 &mut self.frame_builder.stacking_context_store[stacking_context_index.0];
-            stacking_context.bounding_rect = stacking_context.bounding_rect
+            stacking_context.screen_bounds = stacking_context.screen_bounds
                                                              .intersection(self.screen_rect)
                                                              .unwrap_or(DeviceIntRect::zero());
-            (stacking_context.bounding_rect.clone(), stacking_context.is_visible)
+            (stacking_context.screen_bounds.clone(), stacking_context.is_visible)
         };
 
         if let Some(ref mut parent_index) = self.stacking_context_stack.last_mut() {
             let parent = &mut self.frame_builder.stacking_context_store[parent_index.0];
-            parent.bounding_rect = parent.bounding_rect.union(&bounding_rect);
+            parent.screen_bounds = parent.screen_bounds.union(&bounding_rect);
 
             // The previous compute_stacking_context_visibility pass did not take into
             // account visibility of children, so we do that now.
             parent.is_visible = parent.is_visible || is_visible;
         }
     }
 
     fn handle_push_stacking_context(&mut self, stacking_context_index: StackingContextIndex) {
@@ -1541,17 +1621,17 @@ impl<'a> LayerRectCalculationAndCullingP
 
         // Reset bounding rect to zero. We will calculate it as we collect primitives
         // from various scroll layers. In handle_pop_stacking_context , we use this to
         // calculate the device bounding rect. In the future, we could cache this during
         // the initial adding of items for the common case (where there is only a single
         // scroll layer for items in a stacking context).
         let stacking_context = &mut self.frame_builder
                                         .stacking_context_store[stacking_context_index.0];
-        stacking_context.bounding_rect = DeviceIntRect::zero();
+        stacking_context.screen_bounds = DeviceIntRect::zero();
     }
 
     fn rebuild_clip_info_stack_if_necessary(&mut self, id: ClipId) -> Option<DeviceIntRect> {
         if let Some((current_scroll_id, bounding_rect)) = self.current_clip_info {
             if current_scroll_id == id {
                 return bounding_rect;
             }
         }
@@ -1641,18 +1721,18 @@ impl<'a> LayerRectCalculationAndCullingP
                     Some(rect) => rect,
                     _ => continue,
                 };
 
                 let prim_metadata = &mut self.frame_builder.prim_store.cpu_metadata[prim_index.0];
                 let prim_clip_info = prim_metadata.clip_cache_info.as_ref();
                 let mut visible = true;
 
-                stacking_context.bounding_rect =
-                    stacking_context.bounding_rect.union(&prim_bounding_rect);
+                stacking_context.screen_bounds =
+                    stacking_context.screen_bounds.union(&prim_bounding_rect);
 
                 if let Some(info) = prim_clip_info {
                     self.current_clip_stack.push((packed_layer_index, info.clone()));
                 }
 
                 // Try to create a mask if we may need to.
                 if !self.current_clip_stack.is_empty() {
                     // If the primitive doesn't have a specific clip, key the task ID off the
--- a/gfx/webrender/src/gpu_store.rs
+++ b/gfx/webrender/src/gpu_store.rs
@@ -134,16 +134,20 @@ impl<T: Clone + Default, L: GpuStoreLayo
 
     pub fn get_slice_mut(&mut self,
                          address: GpuStoreAddress,
                          count: usize) -> &mut [T] {
         let offset = address.0 as usize;
         &mut self.data[offset..offset + count]
     }
 
+    pub fn clear(&mut self) {
+        self.data.clear()
+    }
+
     // TODO(gw): Implement incremental updates of
     // GPU backed data, and support freelist for removing
     // dynamic items.
 
     /*
     pub fn free(&mut self, address: GpuStoreAddress) {
 
     }
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -71,16 +71,17 @@ pub enum TextureSampler {
     Data32,
     Data64,
     Data128,
     Layers,
     RenderTasks,
     Geometry,
     ResourceRects,
     Gradients,
+    SplitGeometry,
     Dither,
 }
 
 impl TextureSampler {
     pub fn color(n: usize) -> TextureSampler {
         match n {
             0 => TextureSampler::Color0,
             1 => TextureSampler::Color1,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -47,16 +47,17 @@ extern crate thread_profiler;
 
 mod border;
 mod clip_scroll_node;
 mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 mod device;
+mod ellipse;
 mod frame;
 mod frame_builder;
 mod freelist;
 mod geometry;
 mod gpu_store;
 mod internal_types;
 mod mask_cache;
 mod prim_store;
@@ -127,14 +128,15 @@ 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 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
@@ -1,12 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+use border::BorderCornerClipSource;
 use gpu_store::GpuStoreAddress;
 use prim_store::{ClipData, GpuBlock32, PrimitiveStore};
 use prim_store::{CLIP_DATA_GPU_SIZE, MASK_DATA_GPU_SIZE};
 use renderer::VertexDataStore;
 use util::{ComplexClipRegionHelpers, MatrixHelpers, TransformedRect};
 use webrender_traits::{AuxiliaryLists, BorderRadius, ClipRegion, ComplexClipRegion, ImageMask};
 use webrender_traits::{DeviceIntRect, LayerToWorldTransform};
 use webrender_traits::{LayerRect, LayerPoint, LayerSize};
@@ -42,22 +43,29 @@ pub enum RegionMode {
 #[derive(Clone, Debug)]
 pub enum ClipSource {
     Complex(LayerRect, f32, ClipMode),
     // The RegionMode here specifies whether to consider the rect
     // from the clip region as part of the mask. This is true
     // for clip/scroll nodes, but false for primitives, where
     // the clip rect is handled in local space.
     Region(ClipRegion, RegionMode),
+
+    // TODO(gw): This currently only handles dashed style
+    // clips, where the border style is dashed for both
+    // adjacent border edges. Expand to handle dotted style
+    // and different styles per edge.
+    BorderCorner(BorderCornerClipSource),
 }
 
 impl ClipSource {
     pub fn image_mask(&self) -> Option<ImageMask> {
         match *self {
-            ClipSource::Complex(..) => None,
+            ClipSource::Complex(..) |
+            ClipSource::BorderCorner{..} => None,
             ClipSource::Region(ref region, _) => region.image_mask,
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub struct ClipAddressRange {
     pub start: GpuStoreAddress,
@@ -107,70 +115,79 @@ pub enum MaskBounds {
     /// We can't determine the bounds - draw mask over entire rect.
     /// This is currently used for clip-out operations on
     /// box shadows.
     None,
 }
 
 #[derive(Clone, Debug)]
 pub struct MaskCacheInfo {
-    pub clip_range: ClipAddressRange,
-    pub effective_clip_count: usize,
+    pub complex_clip_range: ClipAddressRange,
+    pub effective_complex_clip_count: usize,
     pub image: Option<(ImageMask, GpuStoreAddress)>,
+    pub border_corners: Vec<(BorderCornerClipSource, GpuStoreAddress)>,
     pub bounds: Option<MaskBounds>,
     pub is_aligned: bool,
 }
 
 impl MaskCacheInfo {
     /// Create a new mask cache info. It allocates the GPU store data but leaves
     /// it unitialized for the following `update()` call to deal with.
     pub fn new(clips: &[ClipSource],
                clip_store: &mut VertexDataStore<GpuBlock32>)
                -> Option<MaskCacheInfo> {
         if clips.is_empty() {
             return None;
         }
 
         let mut image = None;
-        let mut clip_count = 0;
+        let mut border_corners = Vec::new();
+        let mut complex_clip_count = 0;
 
         // Work out how much clip data space we need to allocate
         // and if we have an image mask.
         for clip in clips {
             match *clip {
                 ClipSource::Complex(..) => {
-                    clip_count += 1;
-                },
+                    complex_clip_count += 1;
+                }
                 ClipSource::Region(ref region, region_mode) => {
                     if let Some(info) = region.image_mask {
                         debug_assert!(image.is_none());     // TODO(gw): Support >1 image mask!
                         image = Some((info, clip_store.alloc(MASK_DATA_GPU_SIZE)));
                     }
 
-                    clip_count += region.complex.length;
+                    complex_clip_count += region.complex.length;
                     if region_mode == RegionMode::IncludeRect {
-                        clip_count += 1;
+                        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);
+                    border_corners.push((source.clone(), gpu_address));
+                }
             }
         }
 
-        let clip_range = ClipAddressRange {
-            start: if clip_count > 0 {
-                clip_store.alloc(CLIP_DATA_GPU_SIZE * clip_count)
+        let complex_clip_range = ClipAddressRange {
+            start: if complex_clip_count > 0 {
+                clip_store.alloc(CLIP_DATA_GPU_SIZE * complex_clip_count)
             } else {
                 GpuStoreAddress(0)
             },
-            item_count: clip_count,
+            item_count: complex_clip_count,
         };
 
         Some(MaskCacheInfo {
-            clip_range: clip_range,
-            effective_clip_count: clip_range.item_count,
+            complex_clip_range: complex_clip_range,
+            effective_complex_clip_count: complex_clip_range.item_count,
             image: image,
+            border_corners: border_corners,
             bounds: None,
             is_aligned: true,
         })
     }
 
     pub fn update(&mut self,
                   sources: &[ClipSource],
                   transform: &LayerToWorldTransform,
@@ -181,31 +198,32 @@ impl MaskCacheInfo {
 
         // 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;
+            let mut has_border_clip = false;
 
-            self.effective_clip_count = 0;
+            self.effective_complex_clip_count = 0;
             self.is_aligned = is_aligned;
 
             for source in sources {
                 match *source {
                     ClipSource::Complex(rect, radius, mode) => {
                         // Once we encounter a clip-out, we just assume the worst
                         // case clip mask size, for now.
                         if mode == ClipMode::ClipOut {
                             has_clip_out = true;
                         }
-                        debug_assert!(self.effective_clip_count < self.clip_range.item_count);
-                        let address = self.clip_range.start + self.effective_clip_count * CLIP_DATA_GPU_SIZE;
-                        self.effective_clip_count += 1;
+                        debug_assert!(self.effective_complex_clip_count < self.complex_clip_range.item_count);
+                        let address = self.complex_clip_range.start + self.effective_complex_clip_count * CLIP_DATA_GPU_SIZE;
+                        self.effective_complex_clip_count += 1;
 
                         let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE);
                         let data = ClipData::uniform(rect, radius, mode);
                         PrimitiveStore::populate_clip_data(slice, data);
                         local_rect = local_rect.and_then(|r| r.intersection(&rect));
                         local_inner = ComplexClipRegion::new(rect, BorderRadius::uniform(radius))
                                                         .get_inner_rect_safe();
                     }
@@ -218,43 +236,51 @@ impl MaskCacheInfo {
                             },
                             Some(_) => None,
                             None => local_rect,
                         };
 
                         let clips = aux_lists.complex_clip_regions(&region.complex);
                         if !self.is_aligned && region_mode == RegionMode::IncludeRect {
                             // we have an extra clip rect coming from the transformed layer
-                            debug_assert!(self.effective_clip_count < self.clip_range.item_count);
-                            let address = self.clip_range.start + self.effective_clip_count * CLIP_DATA_GPU_SIZE;
-                            self.effective_clip_count += 1;
+                            debug_assert!(self.effective_complex_clip_count < self.complex_clip_range.item_count);
+                            let address = self.complex_clip_range.start + self.effective_complex_clip_count * CLIP_DATA_GPU_SIZE;
+                            self.effective_complex_clip_count += 1;
 
                             let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE);
                             PrimitiveStore::populate_clip_data(slice, ClipData::uniform(region.main, 0.0, ClipMode::Clip));
                         }
 
-                        debug_assert!(self.effective_clip_count + clips.len() <= self.clip_range.item_count);
-                        let address = self.clip_range.start + self.effective_clip_count * CLIP_DATA_GPU_SIZE;
-                        self.effective_clip_count += clips.len();
+                        debug_assert!(self.effective_complex_clip_count + clips.len() <= self.complex_clip_range.item_count);
+                        let address = self.complex_clip_range.start + self.effective_complex_clip_count * CLIP_DATA_GPU_SIZE;
+                        self.effective_complex_clip_count += clips.len();
 
                         let slice = clip_store.get_slice_mut(address, CLIP_DATA_GPU_SIZE * clips.len());
                         for (clip, chunk) in clips.iter().zip(slice.chunks_mut(CLIP_DATA_GPU_SIZE)) {
                             let data = ClipData::from_clip_region(clip);
                             PrimitiveStore::populate_clip_data(chunk, data);
                             local_rect = local_rect.and_then(|r| r.intersection(&clip.rect));
                             local_inner = local_inner.and_then(|r| clip.get_inner_rect_safe()
                                                                        .and_then(|ref inner| r.intersection(inner)));
                         }
                     }
+                    ClipSource::BorderCorner{..} => {}
                 }
             }
 
+            for &(ref source, gpu_address) in &self.border_corners {
+                has_border_clip = true;
+                let slice = clip_store.get_slice_mut(gpu_address,
+                                                     1 + source.dash_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 {
+            if has_clip_out || has_border_clip {
                 // For clip-out, the mask rect is not known.
                 self.bounds = Some(MaskBounds::None);
             } else {
                 // TODO(gw): local inner is only valid if there's a single clip (for now).
                 // This can be improved in the future, with some proper
                 // rectangle region handling.
                 if sources.len() > 1 {
                     local_inner = None;
@@ -283,15 +309,17 @@ impl MaskCacheInfo {
             }
             &mut MaskBounds::OuterInner(ref mut outer, ref mut inner) => {
                 outer.update(transform, device_pixel_ratio);
                 inner.update(transform, device_pixel_ratio);
             }
         }
     }
 
-    /// Check if this `MaskCacheInfo` actually carries any masks. `effective_clip_count`
+    /// Check if this `MaskCacheInfo` actually carries any masks. `effective_complex_clip_count`
     /// can change during the `update` call depending on the transformation, so the mask may
     /// appear to be empty.
     pub fn is_masking(&self) -> bool {
-        self.image.is_some() || self.effective_clip_count != 0
+        self.image.is_some() ||
+        self.effective_complex_clip_count != 0 ||
+        !self.border_corners.is_empty()
     }
 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,18 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
+use border::{BorderCornerClipData, BorderCornerDashClipData};
 use euclid::{Size2D};
 use gpu_store::GpuStoreAddress;
 use internal_types::{SourceTexture, PackedTexel};
 use mask_cache::{ClipMode, ClipSource, MaskCacheInfo};
-use renderer::{VertexDataStore, GradientDataStore};
+use renderer::{VertexDataStore, GradientDataStore, SplitGeometryStore};
 use render_task::{RenderTask, RenderTaskLocation};
 use resource_cache::{CacheItem, ImageProperties, ResourceCache};
 use std::mem;
 use std::usize;
 use util::{TransformedRect, recycle_vec};
 use webrender_traits::{AuxiliaryLists, ColorF, ImageKey, ImageRendering, YuvColorSpace, YuvFormat};
 use webrender_traits::{ClipRegion, ComplexClipRegion, ItemRange, GlyphKey};
 use webrender_traits::{FontKey, FontRenderMode, WebGLContextId};
@@ -94,16 +95,25 @@ pub enum PrimitiveKind {
 
 /// Geometry description for simple rectangular primitives, uploaded to the GPU.
 #[derive(Debug, Clone)]
 pub struct PrimitiveGeometry {
     pub local_rect: LayerRect,
     pub local_clip_rect: LayerRect,
 }
 
+impl Default for PrimitiveGeometry {
+    fn default() -> PrimitiveGeometry {
+        PrimitiveGeometry {
+            local_rect: unsafe { mem::uninitialized() },
+            local_clip_rect: unsafe { mem::uninitialized() },
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum PrimitiveCacheKey {
     BoxShadow(BoxShadowPrimitiveCacheKey),
     TextShadow(PrimitiveIndex),
 }
 
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
@@ -130,16 +140,30 @@ pub struct PrimitiveMetadata {
 
 impl PrimitiveMetadata {
     pub fn needs_clipping(&self) -> bool {
         self.clip_task.is_some()
     }
 }
 
 #[derive(Debug, Clone)]
+pub struct SplitGeometry {
+    pub data: [f32; 12],
+}
+
+impl Default for SplitGeometry {
+    fn default() -> SplitGeometry {
+        SplitGeometry {
+            data: unsafe { mem::uninitialized() },
+        }
+    }
+}
+
+
+#[derive(Debug, Clone)]
 #[repr(C)]
 pub struct RectanglePrimitive {
     pub color: ColorF,
 }
 
 #[derive(Debug)]
 pub enum ImagePrimitiveKind {
     Image(ImageKey, ImageRendering, Option<TileOffset>, LayerSize),
@@ -313,21 +337,17 @@ impl Clone for GradientData {
             colors_high: self.colors_high,
             colors_low: self.colors_low,
         }
     }
 }
 
 impl GradientData {
     // Generate a color ramp between the start and end indexes from a start color to an end color.
-    fn fill_colors(&mut self, start_idx: usize, end_idx: usize, start_color: &ColorF, end_color: &ColorF) -> usize {
-        if start_idx >= end_idx {
-            return start_idx;
-        }
-
+    fn fill_colors(&mut self, start_idx: usize, end_idx: usize, start_color: &ColorF, end_color: &ColorF) {
         // Calculate the color difference for individual steps in the ramp.
         let inv_steps = 1.0 / (end_idx - start_idx) as f32;
         let step_r = (end_color.r - start_color.r) * inv_steps;
         let step_g = (end_color.g - start_color.g) * inv_steps;
         let step_b = (end_color.b - start_color.b) * inv_steps;
         let step_a = (end_color.a - start_color.a) * inv_steps;
 
         let mut cur_color = *start_color;
@@ -345,53 +365,66 @@ impl GradientData {
             cur_color.g += step_g;
             cur_color.b += step_b;
             cur_color.a += step_a;
             cur_color_high = PackedTexel::high_bytes(&cur_color);
             cur_color_low = PackedTexel::low_bytes(&cur_color);
             high_byte_entry.end_color = cur_color_high;
             low_byte_entry.end_color = cur_color_low;
         }
-
-        end_idx
     }
 
     // Compute an entry index based on a gradient stop offset.
     #[inline]
     fn get_index(offset: f32) -> usize {
         (offset.max(0.0).min(1.0) * GRADIENT_DATA_RESOLUTION as f32).round() as usize
     }
 
     // Build the gradient data from the supplied stops, reversing them if necessary.
     fn build(&mut self, src_stops: &[GradientStop], reverse_stops: bool) {
-        let mut cur_idx = 0usize;
-        let mut cur_color = if let Some(src) = src_stops.first() {
-            src.color
-        } else {
-            ColorF::new(0.0, 0.0, 0.0, 0.0)
-        };
+
+        const MAX_IDX: usize = GRADIENT_DATA_RESOLUTION;
+        const MIN_IDX: usize = 0;
+
+        // 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;
+        debug_assert_eq!(first.offset, 0.0);
 
         if reverse_stops {
-            // If the gradient is reversed, then ensure the stops are processed in reverse order
-            // and that the offsets are inverted.
-            for src in src_stops.iter().rev() {
-                cur_idx = self.fill_colors(cur_idx, Self::get_index(1.0 - src.offset),
-                                           &cur_color, &src.color);
-                cur_color = src.color;
+            // 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_idx = Self::get_index(1.0 - next.offset);
+                if next_idx < cur_idx {
+                    self.fill_colors(next_idx, cur_idx,
+                                     &next.color, &cur_color);
+                    cur_idx = next_idx;
+                }
+                cur_color = next.color;
             }
+            debug_assert_eq!(cur_idx, MIN_IDX);
         } else {
-            for src in src_stops {
-                cur_idx = self.fill_colors(cur_idx, Self::get_index(src.offset),
-                                           &cur_color, &src.color);
-                cur_color = src.color;
+            let mut cur_idx = MIN_IDX;
+            for next in src_stops {
+                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_idx = next_idx;
+                }
+                cur_color = next.color;
             }
+            debug_assert_eq!(cur_idx, MAX_IDX);
         }
-
-        // Fill out any remaining entries in the gradient.
-        self.fill_colors(cur_idx, GRADIENT_DATA_RESOLUTION, &cur_color, &cur_color);
     }
 }
 
 #[derive(Debug, Clone)]
 #[repr(C)]
 struct InstanceRect {
     rect: LayerRect,
 }
@@ -560,38 +593,41 @@ pub enum PrimitiveContainer {
     Border(BorderPrimitiveCpu, BorderPrimitiveGpu),
     AlignedGradient(GradientPrimitiveCpu, GradientPrimitiveGpu),
     AngleGradient(GradientPrimitiveCpu, GradientPrimitiveGpu),
     RadialGradient(RadialGradientPrimitiveCpu, RadialGradientPrimitiveGpu),
     BoxShadow(BoxShadowPrimitiveGpu, Vec<LayerRect>),
 }
 
 pub struct PrimitiveStore {
-    // CPU side information only
+    /// CPU side information only.
     pub cpu_bounding_rects: Vec<Option<DeviceIntRect>>,
     pub cpu_text_runs: Vec<TextRunPrimitiveCpu>,
     pub cpu_images: Vec<ImagePrimitiveCpu>,
     pub cpu_yuv_images: Vec<YuvImagePrimitiveCpu>,
     pub cpu_gradients: Vec<GradientPrimitiveCpu>,
     pub cpu_radial_gradients: Vec<RadialGradientPrimitiveCpu>,
     pub cpu_metadata: Vec<PrimitiveMetadata>,
     pub cpu_borders: Vec<BorderPrimitiveCpu>,
 
-    // Gets uploaded directly to GPU via vertex texture
+    /// Gets uploaded directly to GPU via vertex texture.
     pub gpu_geometry: VertexDataStore<PrimitiveGeometry>,
     pub gpu_data16: VertexDataStore<GpuBlock16>,
     pub gpu_data32: VertexDataStore<GpuBlock32>,
     pub gpu_data64: VertexDataStore<GpuBlock64>,
     pub gpu_data128: VertexDataStore<GpuBlock128>,
     pub gpu_gradient_data: GradientDataStore,
 
-    // Resolved resource rects.
+    /// Geometry generated by plane splitting.
+    pub gpu_split_geometry: SplitGeometryStore,
+
+    /// Resolved resource rects.
     pub gpu_resource_rects: VertexDataStore<TexelRect>,
 
-    // General
+    /// General
     prims_to_resolve: Vec<PrimitiveIndex>,
 }
 
 impl PrimitiveStore {
     pub fn new() -> PrimitiveStore {
         PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_bounding_rects: Vec::new(),
@@ -603,16 +639,17 @@ impl PrimitiveStore {
             cpu_borders: Vec::new(),
             prims_to_resolve: Vec::new(),
             gpu_geometry: VertexDataStore::new(),
             gpu_data16: VertexDataStore::new(),
             gpu_data32: VertexDataStore::new(),
             gpu_data64: VertexDataStore::new(),
             gpu_data128: VertexDataStore::new(),
             gpu_gradient_data: GradientDataStore::new(),
+            gpu_split_geometry: SplitGeometryStore::new(),
             gpu_resource_rects: VertexDataStore::new(),
         }
     }
 
     pub fn recycle(self) -> Self {
         PrimitiveStore {
             cpu_metadata: recycle_vec(self.cpu_metadata),
             cpu_bounding_rects: recycle_vec(self.cpu_bounding_rects),
@@ -624,16 +661,17 @@ impl PrimitiveStore {
             cpu_borders: recycle_vec(self.cpu_borders),
             prims_to_resolve: recycle_vec(self.prims_to_resolve),
             gpu_geometry: self.gpu_geometry.recycle(),
             gpu_data16: self.gpu_data16.recycle(),
             gpu_data32: self.gpu_data32.recycle(),
             gpu_data64: self.gpu_data64.recycle(),
             gpu_data128: self.gpu_data128.recycle(),
             gpu_gradient_data: self.gpu_gradient_data.recycle(),
+            gpu_split_geometry: self.gpu_split_geometry.recycle(),
             gpu_resource_rects: self.gpu_resource_rects.recycle(),
         }
     }
 
     pub fn populate_clip_data(data: &mut [GpuBlock32], clip: ClipData) {
         data[0] = GpuBlock32::from(clip.rect);
         data[1] = GpuBlock32::from(clip.top_left);
         data[2] = GpuBlock32::from(clip.top_right);
@@ -1037,16 +1075,17 @@ impl PrimitiveStore {
 
     pub fn set_clip_source(&mut self, index: PrimitiveIndex, source: Option<ClipSource>) {
         let metadata = &mut self.cpu_metadata[index.0];
         metadata.clips = match source {
             Some(source) => {
                 let (rect, is_complex) = match source {
                     ClipSource::Complex(rect, radius, _) => (rect, radius > 0.0),
                     ClipSource::Region(ref region, _) => (region.main, region.is_complex()),
+                    ClipSource::BorderCorner{..} => panic!("Not supported!"),
                 };
                 self.gpu_geometry.get_mut(GpuStoreAddress(index.0 as i32))
                     .local_clip_rect = rect;
                 if is_complex {
                     metadata.clip_cache_info = None; //CLIP TODO: re-use the existing GPU allocation
                 }
                 vec![source]
             }
@@ -1335,17 +1374,18 @@ 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
+    GradientStopGpu, ClipCorner, ClipRect, ImageMaskData,
+    BorderCornerClipData, BorderCornerDashClipData
 );
 define_gpu_block!(GpuBlock64: [f32; 16] =
     GradientPrimitiveGpu, RadialGradientPrimitiveGpu, BoxShadowPrimitiveGpu
 );
 define_gpu_block!(GpuBlock128: [f32; 32] =
     BorderPrimitiveGpu
 );
 
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -1,20 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+use gpu_store::GpuStoreAddress;
 use internal_types::{HardwareCompositeOp, LowLevelFilterOp};
 use mask_cache::{MaskBounds, MaskCacheInfo};
 use prim_store::{PrimitiveCacheKey, PrimitiveIndex};
 use std::{cmp, f32, i32, mem, usize};
 use tiling::{ClipScrollGroupIndex, PackedLayerIndex, RenderPass, RenderTargetIndex};
 use tiling::{RenderTargetKind, StackingContextIndex};
 use webrender_traits::{ClipId, DeviceIntLength, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
-use webrender_traits::MixBlendMode;
+use webrender_traits::{MixBlendMode};
 
 const FLOATS_PER_RENDER_TASK_INFO: usize = 12;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 pub struct RenderTaskIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 pub enum RenderTaskKey {
@@ -46,19 +47,20 @@ pub enum RenderTaskId {
 #[derive(Debug, Clone)]
 pub enum RenderTaskLocation {
     Fixed,
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
 }
 
 #[derive(Debug, Clone)]
 pub enum AlphaRenderItem {
-    Primitive(ClipScrollGroupIndex, PrimitiveIndex, i32),
+    Primitive(Option<ClipScrollGroupIndex>, PrimitiveIndex, i32),
     Blend(StackingContextIndex, RenderTaskId, LowLevelFilterOp, i32),
     Composite(StackingContextIndex, RenderTaskId, RenderTaskId, MixBlendMode, i32),
+    SplitComposite(StackingContextIndex, RenderTaskId, GpuStoreAddress, i32),
     HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
 }
 
 #[derive(Debug, Clone)]
 pub struct AlphaRenderTask {
     screen_origin: DeviceIntPoint,
     pub items: Vec<AlphaRenderItem>,
 }
@@ -150,16 +152,22 @@ impl RenderTask {
             location: location,
             kind: RenderTaskKind::Alpha(AlphaRenderTask {
                 screen_origin: screen_origin,
                 items: Vec::new(),
             }),
         }
     }
 
+    pub fn new_dynamic_alpha_batch(task_index: RenderTaskIndex,
+                                   rect: &DeviceIntRect) -> RenderTask {
+        let location = RenderTaskLocation::Dynamic(None, rect.size);
+        Self::new_alpha_batch(task_index, rect.origin, location)
+    }
+
     pub fn new_prim_cache(key: PrimitiveCacheKey,
                           size: DeviceIntSize,
                           prim_index: PrimitiveIndex) -> RenderTask {
         RenderTask {
             id: RenderTaskId::Dynamic(RenderTaskKey::CachePrimitive(key)),
             children: Vec::new(),
             location: RenderTaskLocation::Dynamic(None, size),
             kind: RenderTaskKind::CachePrimitive(prim_index),
@@ -231,17 +239,17 @@ impl RenderTask {
         //           For now, only draw optimized geometry if it is
         //           a single aligned rect mask with rounded corners.
         //           In the future, we'll expand this to handle the
         //           more complex types of clip mask geometry.
         let mut geometry_kind = MaskGeometryKind::Default;
         if inner_rect.is_some() && clips.len() == 1 {
             let (_, ref clip_info) = clips[0];
             if clip_info.image.is_none() &&
-               clip_info.effective_clip_count == 1 &&
+               clip_info.effective_complex_clip_count == 1 &&
                clip_info.is_aligned {
                 geometry_kind = MaskGeometryKind::CornersOnly;
             }
         }
 
         let inner_rect = inner_rect.unwrap_or(DeviceIntRect::zero());
 
         MaskResult::Inside(RenderTask {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -17,17 +17,17 @@ use euclid::Matrix4D;
 use fnv::FnvHasher;
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use gpu_store::{GpuStore, GpuStoreLayout};
 use internal_types::{CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
 use internal_types::{TextureUpdateList, PackedVertex, RenderTargetMode};
 use internal_types::{ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE, SourceTexture};
 use internal_types::{BatchTextures, TextureSampler};
-use prim_store::GradientData;
+use prim_store::{GradientData, SplitGeometry};
 use profiler::{Profiler, BackendProfileCounters};
 use profiler::{GpuProfileTag, RendererProfileTimers, RendererProfileCounters};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use render_task::RenderTaskData;
 use std;
 use std::cmp;
 use std::collections::{HashMap, VecDeque};
@@ -65,16 +65,17 @@ const GPU_TAG_CACHE_CLIP: GpuProfileTag 
 const GPU_TAG_CACHE_TEXT_RUN: GpuProfileTag = GpuProfileTag { label: "C_TextRun", color: debug_colors::MISTYROSE };
 const GPU_TAG_INIT: GpuProfileTag = GpuProfileTag { label: "Init", color: debug_colors::WHITE };
 const GPU_TAG_SETUP_TARGET: GpuProfileTag = GpuProfileTag { label: "Target", color: debug_colors::SLATEGREY };
 const GPU_TAG_PRIM_RECT: GpuProfileTag = GpuProfileTag { label: "Rect", color: debug_colors::RED };
 const GPU_TAG_PRIM_IMAGE: GpuProfileTag = GpuProfileTag { label: "Image", color: debug_colors::GREEN };
 const GPU_TAG_PRIM_YUV_IMAGE: GpuProfileTag = GpuProfileTag { label: "YuvImage", color: debug_colors::DARKGREEN };
 const GPU_TAG_PRIM_BLEND: GpuProfileTag = GpuProfileTag { label: "Blend", color: debug_colors::LIGHTBLUE };
 const GPU_TAG_PRIM_HW_COMPOSITE: GpuProfileTag = GpuProfileTag { label: "HwComposite", color: debug_colors::DODGERBLUE };
+const GPU_TAG_PRIM_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 };
@@ -241,17 +242,17 @@ impl GpuStoreLayout for VertexDataTextur
     fn texture_filter() -> TextureFilter {
         TextureFilter::Nearest
     }
 }
 
 type VertexDataTexture = GpuDataTexture<VertexDataTextureLayout>;
 pub type VertexDataStore<T> = GpuStore<T, VertexDataTextureLayout>;
 
-pub struct GradientDataTextureLayout {}
+pub struct GradientDataTextureLayout;
 
 impl GpuStoreLayout for GradientDataTextureLayout {
     fn image_format() -> ImageFormat {
         ImageFormat::RGBA8
     }
 
     fn texture_width<T>() -> usize {
         mem::size_of::<GradientData>() / Self::texel_size() / 2
@@ -260,16 +261,36 @@ impl GpuStoreLayout for GradientDataText
     fn texture_filter() -> TextureFilter {
         TextureFilter::Linear
     }
 }
 
 type GradientDataTexture = GpuDataTexture<GradientDataTextureLayout>;
 pub type GradientDataStore = GpuStore<GradientData, GradientDataTextureLayout>;
 
+pub struct SplitGeometryTextureLayout;
+
+impl GpuStoreLayout for SplitGeometryTextureLayout {
+    fn image_format() -> ImageFormat {
+        //TODO: use normalized integers
+        ImageFormat::RGBAF32
+    }
+
+    fn texture_width<T>() -> usize {
+        MAX_VERTEX_TEXTURE_WIDTH - (MAX_VERTEX_TEXTURE_WIDTH % Self::texels_per_item::<T>())
+    }
+
+    fn texture_filter() -> TextureFilter {
+        TextureFilter::Nearest
+    }
+}
+
+type SplitGeometryTexture = GpuDataTexture<SplitGeometryTextureLayout>;
+pub type SplitGeometryStore = GpuStore<SplitGeometry, SplitGeometryTextureLayout>;
+
 const TRANSFORM_FEATURE: &'static str = "TRANSFORM";
 const SUBPIXEL_AA_FEATURE: &'static str = "SUBPIXEL_AA";
 const CLIP_FEATURE: &'static str = "CLIP";
 
 enum ShaderKind {
     Primitive,
     Cache(VertexFormat),
     ClipCache,
@@ -432,53 +453,57 @@ struct GpuDataTextures {
     render_task_texture: VertexDataTexture,
     prim_geom_texture: VertexDataTexture,
     data16_texture: VertexDataTexture,
     data32_texture: VertexDataTexture,
     data64_texture: VertexDataTexture,
     data128_texture: VertexDataTexture,
     resource_rects_texture: VertexDataTexture,
     gradient_data_texture: GradientDataTexture,
+    split_geometry_texture: SplitGeometryTexture,
 }
 
 impl GpuDataTextures {
     fn new(device: &mut Device) -> GpuDataTextures {
         GpuDataTextures {
             layer_texture: VertexDataTexture::new(device),
             render_task_texture: VertexDataTexture::new(device),
             prim_geom_texture: VertexDataTexture::new(device),
             data16_texture: VertexDataTexture::new(device),
             data32_texture: VertexDataTexture::new(device),
             data64_texture: VertexDataTexture::new(device),
             data128_texture: VertexDataTexture::new(device),
             resource_rects_texture: VertexDataTexture::new(device),
             gradient_data_texture: GradientDataTexture::new(device),
+            split_geometry_texture: SplitGeometryTexture::new(device),
         }
     }
 
     fn init_frame(&mut self, device: &mut Device, frame: &mut Frame) {
         self.data16_texture.init(device, &mut frame.gpu_data16);
         self.data32_texture.init(device, &mut frame.gpu_data32);
         self.data64_texture.init(device, &mut frame.gpu_data64);
         self.data128_texture.init(device, &mut frame.gpu_data128);
         self.prim_geom_texture.init(device, &mut frame.gpu_geometry);
         self.resource_rects_texture.init(device, &mut frame.gpu_resource_rects);
         self.layer_texture.init(device, &mut frame.layer_texture_data);
         self.render_task_texture.init(device, &mut frame.render_task_data);
         self.gradient_data_texture.init(device, &mut frame.gpu_gradient_data);
+        self.split_geometry_texture.init(device, &mut frame.gpu_split_geometry);
 
         device.bind_texture(TextureSampler::Layers, self.layer_texture.id);
         device.bind_texture(TextureSampler::RenderTasks, self.render_task_texture.id);
         device.bind_texture(TextureSampler::Geometry, self.prim_geom_texture.id);
         device.bind_texture(TextureSampler::Data16, self.data16_texture.id);
         device.bind_texture(TextureSampler::Data32, self.data32_texture.id);
         device.bind_texture(TextureSampler::Data64, self.data64_texture.id);
         device.bind_texture(TextureSampler::Data128, self.data128_texture.id);
         device.bind_texture(TextureSampler::ResourceRects, self.resource_rects_texture.id);
         device.bind_texture(TextureSampler::Gradients, self.gradient_data_texture.id);
+        device.bind_texture(TextureSampler::SplitGeometry, self.split_geometry_texture.id);
     }
 }
 
 /// The renderer is responsible for submitting to the GPU the work prepared by the
 /// RenderBackend.
 pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     device: Device,
@@ -492,16 +517,17 @@ pub struct Renderer {
     cs_box_shadow: LazilyCompiledShader,
     cs_text_run: LazilyCompiledShader,
     cs_blur: LazilyCompiledShader,
     /// These are "cache clip shaders". These shaders are used to
     /// draw clip instances into the cached clip mask. The results
     /// of these shaders are also used by the primitive shaders.
     cs_clip_rectangle: LazilyCompiledShader,
     cs_clip_image: LazilyCompiledShader,
+    cs_clip_border: LazilyCompiledShader,
 
     // The are "primitive shaders". These shaders draw and blend
     // final results on screen. They are aware of tile boundaries.
     // Most draw directly to the framebuffer, but some use inputs
     // from the cache shaders to draw. Specifically, the box
     // shadow primitive shader stretches the box shadow cache
     // output, and the cache_image shader blits the results of
     // a cache shader (e.g. blur) to the screen.
@@ -517,26 +543,28 @@ pub struct Renderer {
     ps_gradient: PrimitiveShader,
     ps_angle_gradient: PrimitiveShader,
     ps_radial_gradient: PrimitiveShader,
     ps_box_shadow: PrimitiveShader,
     ps_cache_image: PrimitiveShader,
 
     ps_blend: LazilyCompiledShader,
     ps_hw_composite: LazilyCompiledShader,
+    ps_split_composite: LazilyCompiledShader,
     ps_composite: LazilyCompiledShader,
 
     notifier: Arc<Mutex<Option<Box<RenderNotifier>>>>,
 
     enable_profiler: bool,
     max_recorded_profiles: usize,
     clear_framebuffer: bool,
     clear_color: ColorF,
     debug: DebugRenderer,
     render_target_debug: bool,
+    enable_batcher: bool,
     backend_profile_counters: BackendProfileCounters,
     profile_counters: RendererProfileCounters,
     profiler: Profiler,
     last_time: u64,
 
     color_render_targets: Vec<TextureId>,
     alpha_render_targets: Vec<TextureId>,
 
@@ -678,16 +706,24 @@ impl Renderer {
         let cs_clip_image = try!{
             LazilyCompiledShader::new(ShaderKind::ClipCache,
                                       "cs_clip_image",
                                       &[],
                                       &mut device,
                                       options.precache_shaders)
         };
 
+        let cs_clip_border = try!{
+            LazilyCompiledShader::new(ShaderKind::ClipCache,
+                                      "cs_clip_border",
+                                      &[],
+                                      &mut device,
+                                      options.precache_shaders)
+        };
+
         let ps_rectangle = try!{
             PrimitiveShader::new("ps_rectangle",
                                  &mut device,
                                  &[],
                                  options.precache_shaders)
         };
 
         let ps_rectangle_clip = try!{
@@ -867,16 +903,24 @@ impl Renderer {
         let ps_hw_composite = try!{
             LazilyCompiledShader::new(ShaderKind::Primitive,
                                      "ps_hardware_composite",
                                      &[],
                                      &mut device,
                                      options.precache_shaders)
         };
 
+        let ps_split_composite = try!{
+            LazilyCompiledShader::new(ShaderKind::Primitive,
+                                     "ps_split_composite",
+                                     &[],
+                                     &mut device,
+                                     options.precache_shaders)
+        };
+
         let device_max_size = device.max_texture_size();
         let max_texture_size = cmp::min(device_max_size, options.max_texture_size.unwrap_or(device_max_size));
 
         let mut texture_cache = TextureCache::new(max_texture_size);
         let mut backend_profile_counters = BackendProfileCounters::new();
 
         let white_pixels: Vec<u8> = vec![
             0xff, 0xff, 0xff, 0xff,
@@ -1035,16 +1079,17 @@ impl Renderer {
             device: device,
             current_frame: None,
             pending_texture_updates: Vec::new(),
             pending_shader_updates: Vec::new(),
             cs_box_shadow: cs_box_shadow,
             cs_text_run: cs_text_run,
             cs_blur: cs_blur,
             cs_clip_rectangle: cs_clip_rectangle,
+            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,
@@ -1052,20 +1097,22 @@ impl Renderer {
             ps_border_edge: ps_border_edge,
             ps_box_shadow: ps_box_shadow,
             ps_gradient: ps_gradient,
             ps_angle_gradient: ps_angle_gradient,
             ps_radial_gradient: ps_radial_gradient,
             ps_cache_image: ps_cache_image,
             ps_blend: ps_blend,
             ps_hw_composite: ps_hw_composite,
+            ps_split_composite: ps_split_composite,
             ps_composite: ps_composite,
             notifier: notifier,
             debug: debug_renderer,
             render_target_debug: render_target_debug,
+            enable_batcher: options.enable_batcher,
             backend_profile_counters: BackendProfileCounters::new(),
             profile_counters: RendererProfileCounters::new(),
             profiler: Profiler::new(),
             enable_profiler: options.enable_profiler,
             max_recorded_profiles: options.max_recorded_profiles,
             clear_framebuffer: options.clear_framebuffer,
             clear_color: options.clear_color,
             last_time: 0,
@@ -1432,20 +1479,29 @@ impl Renderer {
             self.device.bind_texture(TextureSampler::color(i), texture_id);
         }
 
         // TODO: this probably isn't the best place for this.
         if let Some(id) = self.dither_matrix_texture_id {
             self.device.bind_texture(TextureSampler::Dither, id);
         }
 
-        self.device.update_vao_instances(vao, data, VertexUsageHint::Stream);
-        self.device.draw_indexed_triangles_instanced_u16(6, data.len() as i32);
+        if self.enable_batcher {
+            self.device.update_vao_instances(vao, data, VertexUsageHint::Stream);
+            self.device.draw_indexed_triangles_instanced_u16(6, data.len() as i32);
+            self.profile_counters.draw_calls.inc();
+        } else {
+            for i in 0 .. data.len() {
+                self.device.update_vao_instances(vao, &data[i..i+1], VertexUsageHint::Stream);
+                self.device.draw_triangles_u16(0, 6);
+                self.profile_counters.draw_calls.inc();
+            }
+        }
+
         self.profile_counters.vertices.add(6 * data.len());
-        self.profile_counters.draw_calls.inc();
     }
 
     fn submit_batch(&mut self,
                     batch: &PrimitiveBatch,
                     projection: &Matrix4D<f32>,
                     render_task_data: &[RenderTaskData],
                     cache_texture: TextureId,
                     render_target: Option<(TextureId, i32)>,
@@ -1460,16 +1516,20 @@ impl Renderer {
             AlphaBatchKind::Composite => {
                 let shader = self.ps_composite.get(&mut self.device);
                 (GPU_TAG_PRIM_COMPOSITE, shader)
             }
             AlphaBatchKind::HardwareComposite => {
                 let shader = self.ps_hw_composite.get(&mut self.device);
                 (GPU_TAG_PRIM_HW_COMPOSITE, shader)
             }
+            AlphaBatchKind::SplitComposite => {
+                let shader = self.ps_split_composite.get(&mut self.device);
+                (GPU_TAG_PRIM_SPLIT_COMPOSITE, shader)
+            }
             AlphaBatchKind::Blend => {
                 let shader = self.ps_blend.get(&mut self.device);
                 (GPU_TAG_PRIM_BLEND, shader)
             }
             AlphaBatchKind::Rectangle => {
                 let shader = if needs_clipping {
                     self.ps_rectangle_clip.get(&mut self.device, transform_kind)
                 } else {
@@ -1796,16 +1856,26 @@ 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.
@@ -2115,16 +2185,17 @@ pub struct RendererOptions {
     pub max_recorded_profiles: usize,
     pub debug: bool,
     pub enable_scrollbars: bool,
     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 blob_image_renderer: Option<Box<BlobImageRenderer>>,
     pub recorder: Option<Box<ApiRecordingReceiver>>,
 }
 
 impl Default for RendererOptions {
@@ -2138,16 +2209,17 @@ impl Default for RendererOptions {
             max_recorded_profiles: 0,
             debug: false,
             enable_scrollbars: false,
             precache_shaders: false,
             renderer_kind: RendererKind::Native,
             enable_subpixel_aa: false,
             clear_framebuffer: true,
             clear_color: ColorF::new(1.0, 1.0, 1.0, 1.0),
+            enable_batcher: true,
             render_target_debug: false,
             max_texture_size: None,
             workers: None,
             blob_image_renderer: None,
             recorder: None,
         }
     }
 }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -5,18 +5,18 @@
 use app_units::Au;
 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, PrimitiveCacheKey, PrimitiveGeometry, PrimitiveIndex};
-use prim_store::{PrimitiveKind, PrimitiveMetadata, PrimitiveStore, TexelRect};
+use prim_store::{GpuBlock64, GradientData, SplitGeometry, PrimitiveCacheKey, PrimitiveGeometry};
+use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore, TexelRect};
 use profiler::FrameProfileCounters;
 use render_task::{AlphaRenderItem, MaskGeometryKind, MaskSegment, RenderTask, RenderTaskData};
 use render_task::{RenderTaskId, RenderTaskIndex, RenderTaskKey, RenderTaskKind};
 use render_task::RenderTaskLocation;
 use renderer::BlendMode;
 use renderer::ImageBufferKind;
 use resource_cache::ResourceCache;
 use std::{f32, i32, mem, usize};
@@ -193,25 +193,16 @@ impl RenderTaskCollection {
             &RenderTaskId::Static(index) => index,
             &RenderTaskId::Dynamic(key) => {
                 self.dynamic_tasks[&(key, pass_index)].index
             }
         }
     }
 }
 
-impl Default for PrimitiveGeometry {
-    fn default() -> PrimitiveGeometry {
-        PrimitiveGeometry {
-            local_rect: unsafe { mem::uninitialized() },
-            local_clip_rect: unsafe { mem::uninitialized() },
-        }
-    }
-}
-
 struct AlphaBatchTask {
     task_id: RenderTaskId,
     items: Vec<AlphaRenderItem>,
 }
 
 pub struct BatchList {
     pub alpha_batches: Vec<PrimitiveBatch>,
     pub opaque_batches: Vec<PrimitiveBatch>,
@@ -315,17 +306,17 @@ impl AlphaRenderItem {
                     LowLevelFilterOp::Invert(amount) => (4, amount.to_f32_px()),
                     LowLevelFilterOp::Saturate(amount) => (5, amount.to_f32_px()),
                     LowLevelFilterOp::Sepia(amount) => (6, amount.to_f32_px()),
                     LowLevelFilterOp::Brightness(amount) => (7, amount.to_f32_px()),
                     LowLevelFilterOp::Opacity(amount) => (8, amount.to_f32_px()),
                 };
 
                 let amount = (amount * 65535.0).round() as i32;
-                let batch = batch_list.get_suitable_batch(&key, &stacking_context.bounding_rect);
+                let batch = batch_list.get_suitable_batch(&key, &stacking_context.screen_bounds);
 
                 batch.add_instance(PrimitiveInstance {
                     global_prim_id: -1,
                     prim_address: GpuStoreAddress(0),
                     task_index: task_index.0 as i32,
                     clip_task_index: -1,
                     layer_index: -1,
                     sub_index: filter_mode,
@@ -335,17 +326,17 @@ impl AlphaRenderItem {
             }
             AlphaRenderItem::HardwareComposite(stacking_context_index, src_id, composite_op, z) => {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let src_task_index = render_tasks.get_static_task_index(&src_id);
                 let key = AlphaBatchKey::new(AlphaBatchKind::HardwareComposite,
                                              AlphaBatchKeyFlags::empty(),
                                              composite_op.to_blend_mode(),
                                              BatchTextures::no_texture());
-                let batch = batch_list.get_suitable_batch(&key, &stacking_context.bounding_rect);
+                let batch = batch_list.get_suitable_batch(&key, &stacking_context.screen_bounds);
                 batch.add_instance(PrimitiveInstance {
                     global_prim_id: -1,
                     prim_address: GpuStoreAddress(0),
                     task_index: task_index.0 as i32,
                     clip_task_index: -1,
                     layer_index: -1,
                     sub_index: -1,
                     user_data: [src_task_index.0 as i32, 0],
@@ -357,35 +348,41 @@ impl AlphaRenderItem {
                                        src_id,
                                        mode,
                                        z) => {
                 let stacking_context = &ctx.stacking_context_store[stacking_context_index.0];
                 let key = AlphaBatchKey::new(AlphaBatchKind::Composite,
                                              AlphaBatchKeyFlags::empty(),
                                              BlendMode::Alpha,
                                              BatchTextures::no_texture());
-                let batch = batch_list.get_suitable_batch(&key, &stacking_context.bounding_rect);
+                let batch = batch_list.get_suitable_batch(&key, &stacking_context.screen_bounds);
                 let backdrop_task = render_tasks.get_task_index(&backdrop_id, child_pass_index);
                 let src_task_index = render_tasks.get_static_task_index(&src_id);
                 batch.add_instance(PrimitiveInstance {
                     global_prim_id: -1,
                     prim_address: GpuStoreAddress(0),
                     task_index: task_index.0 as i32,
                     clip_task_index: -1,
                     layer_index: -1,
                     sub_index: mode as u32 as i32,
                     user_data: [ backdrop_task.0 as i32,
                                  src_task_index.0 as i32 ],
                     z_sort_index: z,
                 });
             }
-            AlphaRenderItem::Primitive(clip_scroll_group_index, prim_index, z) => {
-                let group = &ctx.clip_scroll_group_store[clip_scroll_group_index.0];
+            AlphaRenderItem::Primitive(clip_scroll_group_index_opt, prim_index, z) => {
                 let prim_metadata = ctx.prim_store.get_metadata(prim_index);
-                let transform_kind = group.screen_bounding_rect.as_ref().unwrap().0;
+                let (transform_kind, packed_layer_index) = match clip_scroll_group_index_opt {
+                    Some(group_index) => {
+                        let group = &ctx.clip_scroll_group_store[group_index.0];
+                        let bounding_rect = group.screen_bounding_rect.as_ref().unwrap();
+                        (bounding_rect.0, group.packed_layer_index.0 as i32)
+                    },
+                    None => (TransformedRectKind::AxisAligned, 0),
+                };
                 let needs_clipping = prim_metadata.needs_clipping();
                 let mut flags = AlphaBatchKeyFlags::empty();
                 if needs_clipping {
                     flags |= NEEDS_CLIPPING;
                 }
                 if transform_kind == TransformedRectKind::AxisAligned {
                     flags |= AXIS_ALIGNED;
                 }
@@ -396,18 +393,16 @@ impl AlphaRenderItem {
                 let clip_task_index = match prim_metadata.clip_task {
                     Some(ref clip_task) => {
                         render_tasks.get_task_index(&clip_task.id, child_pass_index)
                     }
                     None => {
                         OPAQUE_TASK_INDEX
                     }
                 }.0 as i32;
-                let packed_layer_index = ctx.clip_scroll_group_store[clip_scroll_group_index.0]
-                                            .packed_layer_index.0 as i32;
                 let global_prim_id = prim_index.0 as i32;
                 let prim_address = prim_metadata.gpu_prim_index;
                 let task_index = task_index.0 as i32;
                 let needs_blending = !prim_metadata.is_opaque ||
                                      needs_clipping ||
                                      transform_kind == TransformedRectKind::Complex;
                 let blend_mode = ctx.prim_store.get_blend_mode(needs_blending, prim_metadata);
                 let base_instance = PrimitiveInstance {
@@ -581,19 +576,37 @@ impl AlphaRenderItem {
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
                         for rect_index in 0..prim_metadata.gpu_data_count {
                             batch.add_instance(base_instance.build(prim_metadata.gpu_data_address.0 + rect_index,
                                                                    cache_task_index.0 as i32,
                                                                    0));
                         }
                     }
-
                 }
             }
+            AlphaRenderItem::SplitComposite(sc_index, task_id, gpu_address, z) => {
+                let key = AlphaBatchKey::new(AlphaBatchKind::SplitComposite,
+                                             AlphaBatchKeyFlags::empty(),
+                                             BlendMode::PremultipliedAlpha,
+                                             BatchTextures::no_texture());
+                let stacking_context = &ctx.stacking_context_store[sc_index.0];
+                let batch = batch_list.get_suitable_batch(&key, &stacking_context.screen_bounds);
+                let source_task = render_tasks.get_task_index(&task_id, child_pass_index);
+                batch.add_instance(PrimitiveInstance {
+                    global_prim_id: -1,
+                    prim_address: gpu_address,
+                    task_index: task_index.0 as i32,
+                    clip_task_index: -1,
+                    layer_index: -1, // not be used
+                    sub_index: 0,
+                    user_data: [ source_task.0 as i32, 0 ],
+                    z_sort_index: z,
+                });
+            }
         }
     }
 }
 
 impl AlphaBatcher {
     fn new() -> AlphaBatcher {
         AlphaBatcher {
             tasks: Vec::new(),
@@ -625,23 +638,25 @@ 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 borders: Vec<CacheClipInstance>,
 }
 
 impl ClipBatcher {
     fn new() -> ClipBatcher {
         ClipBatcher {
             rectangles: Vec::new(),
             images: HashMap::new(),
+            borders: Vec::new(),
         }
     }
 
     fn add<'a>(&mut self,
                task_index: RenderTaskIndex,
                clips: &[(PackedLayerIndex, MaskCacheInfo)],
                resource_cache: &ResourceCache,
                geometry_kind: MaskGeometryKind) {
@@ -649,18 +664,18 @@ impl ClipBatcher {
         for &(packed_layer_index, ref info) in clips.iter() {
             let instance = CacheClipInstance {
                 task_id: task_index.0 as i32,
                 layer_index: packed_layer_index.0 as i32,
                 address: GpuStoreAddress(0),
                 segment: 0,
             };
 
-            for clip_index in 0..info.effective_clip_count as usize {
-                let offset = info.clip_range.start.0 + ((CLIP_DATA_GPU_SIZE * clip_index) as i32);
+            for clip_index in 0..info.effective_complex_clip_count as usize {
+                let offset = info.complex_clip_range.start.0 + ((CLIP_DATA_GPU_SIZE * clip_index) as i32);
                 match geometry_kind {
                     MaskGeometryKind::Default => {
                         self.rectangles.push(CacheClipInstance {
                             address: GpuStoreAddress(offset),
                             segment: MaskSegment::All as i32,
                             ..instance
                         });
                     }
@@ -695,16 +710,26 @@ impl ClipBatcher {
                 let cache_item = resource_cache.get_cached_image(mask.image, ImageRendering::Auto, None);
                 self.images.entry(cache_item.texture_id)
                            .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.borders.push(CacheClipInstance {
+                        address: gpu_address,
+                        segment: dash_index as i32,
+                        ..instance
+                    })
+                }
+            }
         }
     }
 }
 
 pub struct RenderTargetContext<'a> {
     pub stacking_context_store: &'a [StackingContext],
     pub clip_scroll_group_store: &'a [ClipScrollGroup],
     pub prim_store: &'a PrimitiveStore,
@@ -1144,16 +1169,17 @@ impl RenderPass {
         self.alpha_targets.build(ctx, render_tasks, self.pass_index);
     }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 pub enum AlphaBatchKind {
     Composite,
     HardwareComposite,
+    SplitComposite,
     Blend,
     Rectangle,
     TextRun,
     Image(ImageBufferKind),
     YuvImage(ImageBufferKind, YuvFormat, YuvColorSpace),
     Border,
     AlignedGradient,
     AngleGradient,
@@ -1295,59 +1321,82 @@ impl PrimitiveBatch {
 }
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct PackedLayerIndex(pub usize);
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
 pub struct StackingContextIndex(pub usize);
 
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
+pub enum ContextIsolation {
+    /// No isolation - the content is mixed up with everything else.
+    None,
+    /// Items are isolated and drawn into a separate render target.
+    /// Child contexts are exposed.
+    Items,
+    /// All the content inside is isolated and drawn into a separate target.
+    Full,
+}
+
 #[derive(Debug)]
 pub struct StackingContext {
     pub pipeline_id: PipelineId,
 
-    // Offset in the parent reference frame to the origin of this stacking
-    // context's coordinate system.
+    /// Offset in the parent reference frame to the origin of this stacking
+    /// context's coordinate system.
     pub reference_frame_offset: LayerPoint,
 
-    // Bounding rectangle for this stacking context calculated based on the size
-    // and position of all its children.
-    pub bounding_rect: DeviceIntRect,
+    /// The `ClipId` of the owning reference frame.
+    pub reference_frame_id: ClipId,
+
+    /// Local bounding rectangle for this stacking context.
+    pub local_bounds: LayerRect,
+
+    /// Screen space bounding rectangle for this stacking context,
+    /// calculated based on the size and position of all its children.
+    pub screen_bounds: DeviceIntRect,
 
     pub composite_ops: CompositeOps,
     pub clip_scroll_groups: Vec<ClipScrollGroupIndex>,
 
-    // Signifies that this stacking context should be drawn in a separate render pass
-    // with a transparent background and then composited back to its parent. Used to
-    // support mix-blend-mode in certain cases.
-    pub should_isolate: bool,
+    /// Type of the isolation of the content.
+    pub isolation: ContextIsolation,
 
-    // Set for the root stacking context of a display list or an iframe. Used for determining
-    // when to isolate a mix-blend-mode composite.
+    /// Set for the root stacking context of a display list or an iframe. Used for determining
+    /// when to isolate a mix-blend-mode composite.
     pub is_page_root: bool,
 
-    // Wehther or not this stacking context has any visible components, calculated
-    // based on the size and position of all children and how they are clipped.
+    /// Whether or not this stacking context has any visible components, calculated
+    /// based on the size and position of all children and how they are clipped.
     pub is_visible: bool,
 }
 
 impl StackingContext {
     pub fn new(pipeline_id: PipelineId,
                reference_frame_offset: LayerPoint,
                is_page_root: bool,
+               reference_frame_id: ClipId,
+               local_bounds: LayerRect,
                transform_style: TransformStyle,
                composite_ops: CompositeOps)
                -> StackingContext {
+        let isolation = match transform_style {
+            TransformStyle::Flat => ContextIsolation::None,
+            TransformStyle::Preserve3D => ContextIsolation::Items,
+        };
         StackingContext {
             pipeline_id: pipeline_id,
             reference_frame_offset: reference_frame_offset,
-            bounding_rect: DeviceIntRect::zero(),
+            reference_frame_id: reference_frame_id,
+            local_bounds: local_bounds,
+            screen_bounds: DeviceIntRect::zero(),
             composite_ops: composite_ops,
             clip_scroll_groups: Vec::new(),
-            should_isolate: transform_style == TransformStyle::Preserve3D, //TODO
+            isolation: isolation,
             is_page_root: is_page_root,
             is_visible: false,
         }
     }
 
     pub fn clip_scroll_group(&self, clip_and_scroll: ClipAndScrollInfo) -> ClipScrollGroupIndex {
         // Currently there is only one scrolled stacking context per context,
         // but eventually this will be selected from the vector based on the
@@ -1476,16 +1525,17 @@ pub struct Frame {
     pub layer_texture_data: Vec<PackedLayer>,
     pub render_task_data: Vec<RenderTaskData>,
     pub gpu_data16: Vec<GpuBlock16>,
     pub gpu_data32: Vec<GpuBlock32>,
     pub gpu_data64: Vec<GpuBlock64>,
     pub gpu_data128: Vec<GpuBlock128>,
     pub gpu_geometry: Vec<PrimitiveGeometry>,
     pub gpu_gradient_data: Vec<GradientData>,
+    pub gpu_split_geometry: Vec<SplitGeometry>,
     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_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -582,16 +582,22 @@ impl ClipId {
         ClipId::Clip(0, pipeline_id)
     }
 
     pub fn root_reference_frame(pipeline_id: PipelineId) -> ClipId {
         ClipId::ReferenceFrame(0, pipeline_id)
     }
 
     pub fn new(id: u64, pipeline_id: PipelineId) -> ClipId {
+        // We do this because it is very easy to create accidentally create something that
+        // seems like a root scroll node, but isn't one.
+        if id == 0 {
+            return ClipId::root_scroll_node(pipeline_id);
+        }
+
         ClipId::ClipExternalId(id, pipeline_id)
     }
 
     pub fn pipeline_id(&self) -> PipelineId {
         match *self {
             ClipId::Clip(_, pipeline_id) |
             ClipId::ClipExternalId(_, pipeline_id) |
             ClipId::ReferenceFrame(_, pipeline_id) => pipeline_id,
--- a/gfx/webrender_traits/src/units.rs
+++ b/gfx/webrender_traits/src/units.rs
@@ -7,17 +7,18 @@
 //! Physical pixels take into account the device pixel ratio and their dimensions tend
 //! to correspond to the allocated size of resources in memory, while logical pixels
 //! don't have the device pixel ratio applied which means they are agnostic to the usage
 //! of hidpi screens and the like.
 //!
 //! The terms "layer" and "stacking context" can be used interchangeably
 //! in the context of coordinate systems.
 
-use euclid::{TypedMatrix4D, TypedRect, TypedPoint2D, TypedSize2D, TypedPoint4D, Length};
+use euclid::{Length, TypedMatrix4D, TypedRect, TypedSize2D};
+use euclid::{TypedPoint2D, TypedPoint3D, TypedPoint4D};
 
 /// Geometry in the coordinate system of the render target (screen or intermediate
 /// surface) in physical pixels.
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct DevicePixel;
 
 pub type DeviceIntRect = TypedRect<i32, DevicePixel>;
 pub type DeviceIntPoint = TypedPoint2D<i32, DevicePixel>;
@@ -66,16 +67,17 @@ pub type ScrollLayerSize = TypedSize2D<f
 
 /// Geometry in the document's coordinate space (logical pixels).
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct WorldPixel;
 
 pub type WorldRect = TypedRect<f32, WorldPixel>;
 pub type WorldPoint = TypedPoint2D<f32, WorldPixel>;
 pub type WorldSize = TypedSize2D<f32, WorldPixel>;
+pub type WorldPoint3D = TypedPoint3D<f32, WorldPixel>;
 pub type WorldPoint4D = TypedPoint4D<f32, WorldPixel>;
 
 /// Offset in number of tiles.
 #[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 pub struct Tiles;
 pub type TileOffset = TypedPoint2D<u16, Tiles>;
 
 pub type LayoutTransform = TypedMatrix4D<f32, LayoutPixel, LayoutPixel>;
new file mode 100644
--- /dev/null
+++ b/third_party/rust/binary-space-partition/.cargo-checksum.json
@@ -0,0 +1,1 @@
+{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"f9b1ca6ae27d1c18215265024629a8960c31379f206d9ed20f64e0b2dcf79805",".travis.yml":"0310eaafa77ed58afbc5f93b1a26e938e96533b352865bc75ff4a5993aa4a8e0","Cargo.toml":"6b56e1576b18b05d665fc048ac8995efbfc8f15f9952472c382b11957114a906","LICENSE":"b946744aeda89b467929585fe8eeb5461847695220c1b168fb375d8abd4ea3d0","README.md":"ed45cabc231f18f0972348f0e230d45c92495c31e4a06eb105e8259ed9b582b3","src/lib.rs":"566b415ed879434aef230a5b5a993d789f61e98b2e38a8c87fb7f1f6102f5e6d"},"package":"df65281d9b2b5c332f5bfbd9bb5e5f2e62f627c259cf9dc9cd10fecb758be33d"}
\ No newline at end of file
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/third_party/rust/binary-space-partition/.gitignore
@@ -0,0 +1,2 @@
+target
+Cargo.lock
new file mode 100644
--- /dev/null
+++ b/third_party/rust/binary-space-partition/.travis.yml
@@ -0,0 +1,10 @@
+sudo: false
+language: rust
+cache: cargo
+rust:
+  - nightly
+  - stable
+script:
+  - cargo build
+  - cargo doc
+  - cargo test
new file mode 100644
--- /dev/null
+++ b/third_party/rust/binary-space-partition/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "binary-space-partition"
+version = "0.1.1"
+authors = ["Dzmitry Malyshau <kvark@mozilla.com>"]
+description = "Abstract BSP tree"
+license = "MPL-2.0"
+repository = "https://github.com/kvark/binary-space-partition"
+keywords = ["geometry", "math"]
+documentation = "https://docs.rs/binary-space-partition"
+
+[dependencies]
+
+[dev-dependencies]
+rand = "0.3"
new file mode 100644
--- /dev/null
+++ b/third_party/rust/binary-space-partition/LICENSE
@@ -0,0 +1,374 @@
+ Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+means each individual or legal entity that creates, contributes to
+the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+means the combination of the Contributions of others (if any) used
+by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+means Source Code Form to which the initial Contributor has attached
+the notice in Exhibit A, the Executable Form of such Source Code
+Form, and Modifications of such Source Code Form, in each case
+including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+means
+
+(a) that the initial Contributor has attached the notice described
+in Exhibit B to the Covered Software; or
+
+(b) that the Covered Software was made available under the terms of
+version 1.1 or earlier of the License, but not also under the
+terms of a Secondary License.
+
+1.6. "Executable Form"
+means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+means a work that combines Covered Software with other material, in
+a separate file or files, that is not Covered Software.
+
+1.8. "License"
+means this document.
+
+1.9. "Licensable"
+means having the right to grant, to the maximum extent possible,
+whether at the time of the initial grant or subsequently, any and
+all of the rights conveyed by this License.
+
+1.10. "Modifications"
+means any of the following:
+
+(a) any file in Source Code Form that results from an addition to,
+deletion from, or modification of the contents of Covered
+Software; or
+
+(b) any new file in Source Code Form that contains any Covered
+Software.
+
+1.11. "Patent Claims" of a Contributor
+means any patent claim(s), including without limitation, method,
+process, and apparatus claims, in any patent Licensable by such
+Contributor that would be infringed, but for the grant of the
+License, by the making, using, selling, offering for sale, having
+made, import, or transfer of either its Contributions or its
+Contributor Version.
+
+1.12. "Secondary License"
+means either the GNU General Public License, Version 2.0, the GNU
+Lesser General Public License, Version 2.1, the GNU Affero General
+Public License, Version 3.0, or any later versions of those
+licenses.
+
+1.13. "Source Code Form"
+means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+means an individual or a legal entity exercising rights under this
+License. For legal entities, "You" includes any entity that
+controls, is controlled by, or is under common control with You. For
+purposes of this definition, "control" means (a) the power, direct
+or indirect, to cause the direction or management of such entity,
+whether by contract or otherwise, or (b) ownership of more than
+fifty percent (50%) of the outstanding shares or beneficial
+ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+Licensable by such Contributor to use, reproduce, make available,
+modify, display, perform, distribute, and otherwise exploit its
+Contributions, either on an unmodified basis, with Modifications, or
+as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+for sale, have made, import, and otherwise transfer either its
+Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+or
+
+(b) for infringements caused by: (i) Your and any other third party's
+modifications of Covered Software, or (ii) the combination of its
+Contributions with other software (except as part of its Contributor
+Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+Form, as described in Section 3.1, and You must inform recipients of
+the Executable Form how they can obtain a copy of such Source Code
+Form by reasonable means in a timely manner, at a charge no more
+than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+License, or sublicense it under different terms, provided that the
+license for the Executable Form does not attempt to limit or alter
+the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+This Source Code Form is "Incompatible With Secondary Licenses", as
+defined by the Mozilla Public License, v. 2.0.
+
new file mode 100644
--- /dev/null
+++ b/third_party/rust/binary-space-partition/README.md
@@ -0,0 +1,5 @@
+# binary-space-partition
+[![Build Status](https://travis-ci.org/kvark/binary-space-partition.svg)](https://travis-ci.org/kvark/binary-space-partition) [![](http://meritbadge.herokuapp.com/binary-space-partition)](https://crates.io/crates/binary-space-partition) [![Documentation](https://docs.rs/binary-space-partition/badge.svg)](https://docs.rs/binary-space-partition)
+
+Binary Space Partitioning (BSP) tree.
+Designed to be used with [plane-split](https://github.com/kvark/plane-split) crate.
new file mode 100644
--- /dev/null
+++ b/third_party/rust/binary-space-partition/src/lib.rs
@@ -0,0 +1,218 @@
+/*!
+Binary Space Partitioning (BSP)
+
+Provides an abstract `BspNode` structure, which can be seen as a tree.
+Useful for quickly ordering polygons along a particular view vector.
+Is not tied to a particular math library.
+*/
+#![warn(missing_docs)]
+
+use std::cmp;
+
+
+/// The result of one plane being cut by another one.
+/// The "cut" here is an attempt to classify a plane as being
+/// in front or in the back of another one.
+pub enum PlaneCut<T> {
+    /// The planes are one the same geometrical plane.
+    Sibling(T),
+    /// Planes are different, thus we can either determine that
+    /// our plane is completely in front/back of another one,
+    /// or split it into these sub-groups.
+    Cut {
+        /// Sub-planes in front of the base plane.
+        front: Vec<T>,
+        /// Sub-planes in the back of the base plane.
+        back: Vec<T>,
+    },
+}
+
+/// A plane abstracted to the matter of partitioning.
+pub trait Plane: Sized + Clone {
+    /// Try to cut a different plane by this one.
+    fn cut(&self, Self) -> PlaneCut<Self>;
+    /// Check if a different plane is aligned in the same direction
+    /// as this one.
+    fn is_aligned(&self, &Self) -> bool;
+}
+
+/// Add a list of planes to a particular front/end branch of some root node.
+fn add_side<T: Plane>(side: &mut Option<Box<BspNode<T>>>, mut planes: Vec<T>) {
+    if planes.len() != 0 {
+        if side.is_none() {
+            *side = Some(Box::new(BspNode::new()));
+        }
+        let mut node = side.as_mut().unwrap();
+        for p in planes.drain(..) {
+            node.insert(p)
+        }
+    }
+}
+
+
+/// A node in the `BspTree`, which can be considered a tree itself.
+pub struct BspNode<T> {
+    values: Vec<T>,
+    front: Option<Box<BspNode<T>>>,
+    back: Option<Box<BspNode<T>>>,
+}
+
+impl<T> BspNode<T> {
+    /// Create a new node.
+    pub fn new() -> Self {
+        BspNode {
+            values: Vec::new(),
+            front: None,
+            back: None,
+        }
+    }
+
+    /// Check if this node is a leaf of the tree.
+    pub fn is_leaf(&self) -> bool {
+        self.front.is_none() && self.back.is_none()
+    }
+
+    /// Get the tree depth starting with this node.
+    pub fn get_depth(&self) -> usize {
+        if self.values.is_empty() {
+            return 0
+        }
+        let df = match self.front {
+            Some(ref node) => node.get_depth(),
+            None => 0,
+        };
+        let db = match self.back {
+            Some(ref node) => node.get_depth(),
+            None => 0,
+        };
+        1 + cmp::max(df, db)
+    }
+}
+
+impl<T: Plane> BspNode<T> {
+    /// Insert a value into the sub-tree starting with this node.
+    /// This operation may spawn additional leafs/branches of the tree.
+    pub fn insert(&mut self, value: T) {
+        if self.values.is_empty() {
+            self.values.push(value);
+            return
+        }
+        match self.values[0].cut(value) {
+            PlaneCut::Sibling(value) => self.values.push(value),
+            PlaneCut::Cut { front, back } => {
+                add_side(&mut self.front, front);
+                add_side(&mut self.back, back);
+            }
+        }
+    }
+
+    /// Build the draw order of this sub-tree into an `out` vector,
+    /// so that the contained planes are sorted back to front according
+    /// to the view vector given as the `base` plane normal.
+    pub fn order(&self, base: &T, out: &mut Vec<T>) {
+        let (former, latter) = match self.values.first() {
+            None => return,
+            Some(ref first) if base.is_aligned(first) => (&self.back, &self.front),
+            Some(_) => (&self.front, &self.back),
+        };
+
+        if let Some(ref node) = *former {
+            node.order(base, out);
+        }
+
+        out.extend_from_slice(&self.values);
+
+        if let Some(ref node) = *latter {
+            node.order(base, out);
+        }
+    }
+}
+
+
+#[cfg(test)]
+mod tests {
+    extern crate rand;
+    use super::*;
+    use self::rand::Rng;
+
+    #[derive(Clone, Debug, PartialEq)]
+    struct Plane1D(i32, bool);
+
+    impl Plane for Plane1D {
+        fn cut(&self, plane: Self) -> PlaneCut<Self> {
+            if self.0 == plane.0 {
+                PlaneCut::Sibling(plane)
+            } else if (plane.0 > self.0) == self.1 {
+                PlaneCut::Cut {
+                    front: vec![plane],
+                    back: vec![],
+                }
+            } else {
+                PlaneCut::Cut {
+                    front: vec![],
+                    back: vec![plane],
+                }
+            }
+        }
+
+        fn is_aligned(&self, plane: &Self) -> bool {
+            self.1 == plane.1
+        }
+    }
+
+
+    #[test]
+    fn test_add_side() {
+        let mut node_opt = None;
+        let p0: Vec<Plane1D> = Vec::new();
+        add_side(&mut node_opt, p0);
+        assert!(node_opt.is_none());
+
+        let p1 = Plane1D(1, true);
+        add_side(&mut node_opt, vec![p1.clone()]);
+        assert_eq!(node_opt.as_ref().unwrap().values, vec![p1.clone()]);
+        assert!(node_opt.as_ref().unwrap().is_leaf());
+
+        let p23 = vec![Plane1D(0, false), Plane1D(2, false)];
+        add_side(&mut node_opt, p23);
+        let node = node_opt.unwrap();
+        assert_eq!(node.values, vec![p1.clone()]);
+        assert!(node.front.is_some() && node.back.is_some());
+    }
+
+    #[test]
+    fn test_insert_depth() {
+        let mut node = BspNode::new();
+        assert_eq!(node.get_depth(), 0);
+        node.insert(Plane1D(0, true));
+        assert_eq!(node.get_depth(), 1);
+        node.insert(Plane1D(6, true));
+        assert_eq!(node.get_depth(), 2);
+        node.insert(Plane1D(8, true));
+        assert_eq!(node.get_depth(), 3);
+        node.insert(Plane1D(6, true));
+        assert_eq!(node.get_depth(), 3);
+        node.insert(Plane1D(-5, false));
+        assert_eq!(node.get_depth(), 3);
+    }
+
+    #[test]
+    fn test_order() {
+        let mut rng = rand::thread_rng();
+        let mut node = BspNode::new();
+        let mut out = Vec::new();
+
+        node.order(&Plane1D(0, true), &mut out);
+        assert!(out.is_empty());
+
+        for _ in 0 .. 100 {
+            let plane = Plane1D(rng.gen(), rng.gen());
+            node.insert(plane);
+        }
+
+        node.order(&Plane1D(0, true), &mut out);
+        let mut out2 = out.clone();
+        out2.sort_by_key(|p| p.0);
+        assert_eq!(out, out2);
+    }
+}
--- a/third_party/rust/euclid/.cargo-checksum.json
+++ b/third_party/rust/euclid/.cargo-checksum.json
@@ -1,1 +1,1 @@
-{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"118514fd9c4958df0d25584cda4917186c46011569f55ef350530c1ad3fbdb48",".travis.yml":"13d3e5a7bf83b04c8e8cfa14f0297bd8366d68391d977dd547f64707dffc275a","COPYRIGHT":"ec82b96487e9e778ee610c7ab245162464782cfa1f555c2299333f8dbe5c036a","Cargo.toml":"67de38f0cff93c8020ec3b04b222ea319be655700c58ea9e82951766c1d01b27","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3","README.md":"7a5648f52b09d3213348177860171d4f19b0fdda55e8fed7c04dafcb0ed9c215","src/approxeq.rs":"2987e046c90d948b6c7d7ddba52d10c8b7520d71dc0a50dbe7665de128d7410e","src/length.rs":"0976cac2d792383389b58f8ea7eea022c618accc0d38f89858999082b4d8fe30","src/lib.rs":"a404dd1472f579b8e542a60ec0fb319e856c5424421ef6db8de8b0b792ccc2d9","src/macros.rs":"b63dabdb52df84ea170dc1dab5fe8d7a78c054562d1566bab416124708d2d7af","src/matrix2d.rs":"2612824b050823a88c96213fb5478558931e9d403cffb617cba02ac5190b964a","src/matrix4d.rs":"a5a917842105441f093da8a1a5d17f8f77a4ad3a2544cf937f6573b9713acbcc","src/num.rs":"62286aa642ce3afa7ebd950f50bf2197d8722907f2e23a2e2ea6690484d8b250","src/point.rs":"a585ad405a69505792efb624f0c0e6345b92b27a2c77e9a4366d6192ac914ef0","src/rect.rs":"7f79b1bc12a292ea413bd8a99637291bc131ee40374ebfd6a229c61298009246","src/scale_factor.rs":"df6dbd1f0f9f63210b92809f84a383dad982a74f09789cf22c7d8f9b62199d39","src/side_offsets.rs":"f85526a421ffda63ff01a3478d4162c8717eef68e942acfa2fd9a1adee02ebb2","src/size.rs":"ef95a114f389a357ef940f42789e2cdbdbbdf4ae6993a80a74cc2c9d10c891c9","src/trig.rs":"6b207980052d13c625272f2a70a22f7741b59513c2a4882385926f497c763a63"},"package":"34559b159306de36203986eff799f83ef2bfb301a29fad333883f1a74a4cc6b0"}
\ No newline at end of file
+{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"118514fd9c4958df0d25584cda4917186c46011569f55ef350530c1ad3fbdb48",".travis.yml":"13d3e5a7bf83b04c8e8cfa14f0297bd8366d68391d977dd547f64707dffc275a","COPYRIGHT":"ec82b96487e9e778ee610c7ab245162464782cfa1f555c2299333f8dbe5c036a","Cargo.toml":"10cfe5580ee83ae883a60d96f504dda8ae7885ae5fd3a3faf95c2a2b8b38fad0","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"62065228e42caebca7e7d7db1204cbb867033de5982ca4009928915e4095f3a3","README.md":"52f974f01c1e15182413e4321c8817d5e66fe4d92c5ec223c857dd0440f5c229","src/approxeq.rs":"2987e046c90d948b6c7d7ddba52d10c8b7520d71dc0a50dbe7665de128d7410e","src/length.rs":"d7c6369f2fe2a17c845b57749bd48c471159f0571a7314d3bf90737d53f697d3","src/lib.rs":"e2e621f05304278d020429d0349acf7a4e7c7a9a72bd23fc0e55680267472ee9","src/macros.rs":"b63dabdb52df84ea170dc1dab5fe8d7a78c054562d1566bab416124708d2d7af","src/matrix2d.rs":"2361338f59813adf4eebaab76e4dd82be0fbfb9ff2461da8dd9ac9d43583b322","src/matrix4d.rs":"b8547bed6108b037192021c97169c00ad456120b849e9b7ac7bec40363edaec1","src/num.rs":"62286aa642ce3afa7ebd950f50bf2197d8722907f2e23a2e2ea6690484d8b250","src/point.rs":"53f3c9018c822e0a6dc5018005e153775479f41fe55c082d0be10f331fda773f","src/rect.rs":"db62b3af8939529509ae21b3bf6ae498d73a95b4ff3a6eba4db614be08e95f8b","src/scale_factor.rs":"df6dbd1f0f9f63210b92809f84a383dad982a74f09789cf22c7d8f9b62199d39","src/side_offsets.rs":"f85526a421ffda63ff01a3478d4162c8717eef68e942acfa2fd9a1adee02ebb2","src/size.rs":"19d1c08f678d793c6eff49a44f69e5b7179e574aa9b81fb4e73210733af38718","src/trig.rs":"6b207980052d13c625272f2a70a22f7741b59513c2a4882385926f497c763a63"},"package":"f5517462c626a893f3b027615e88d7102cc6dd3f7f1bcb90c7220fb1da4970b5"}
\ No newline at end of file
--- a/third_party/rust/euclid/Cargo.toml
+++ b/third_party/rust/euclid/Cargo.toml
@@ -1,14 +1,14 @@
 [package]
 name = "euclid"
-version = "0.11.0"
+version = "0.11.3"
 authors = ["The Servo Project Developers"]
 description = "Geometry primitives"
-documentation = "http://doc.servo.org/euclid/"
+documentation = "https://docs.rs/euclid/"
 repository = "https://github.com/servo/euclid"
 license = "MIT / Apache-2.0"
 
 [features]
 unstable = []
 
 [dependencies]
 heapsize = "0.3"
--- a/third_party/rust/euclid/README.md
+++ b/third_party/rust/euclid/README.md
@@ -1,5 +1,5 @@
 # euclid
 
 This is a small library for geometric types.
 
-[Documentation](http://doc.servo.org/euclid/)
+[Documentation](https://docs.rs/euclid/)
--- a/third_party/rust/euclid/src/length.rs
+++ b/third_party/rust/euclid/src/length.rs
@@ -17,24 +17,24 @@ use serde::{Deserialize, Deserializer, S
 use std::cmp::Ordering;
 use std::ops::{Add, Sub, Mul, Div, Neg};
 use std::ops::{AddAssign, SubAssign};
 use std::marker::PhantomData;
 use std::fmt;
 
 /// A one-dimensional distance, with value represented by `T` and unit of measurement `Unit`.
 ///
-/// `T` can be any numeric type, for example a primitive type like u64 or f32.
+/// `T` can be any numeric type, for example a primitive type like `u64` or `f32`.
 ///
-/// `Unit` is not used in the representation of a Length value. It is used only at compile time
-/// to ensure that a Length stored with one unit is converted explicitly before being used in an
+/// `Unit` is not used in the representation of a `Length` value. It is used only at compile time
+/// to ensure that a `Length` stored with one unit is converted explicitly before being used in an
 /// expression that requires a different unit.  It may be a type without values, such as an empty
 /// enum.
 ///
-/// You can multiply a Length by a `scale_factor::ScaleFactor` to convert it from one unit to
+/// You can multiply a `Length` by a `scale_factor::ScaleFactor` to convert it from one unit to
 /// another. See the `ScaleFactor` docs for an example.
 // Uncomment the derive, and remove the macro call, once heapsize gets
 // PhantomData<T> support.
 #[repr(C)]
 #[derive(RustcDecodable, RustcEncodable)]
 pub struct Length<T, Unit>(pub T, PhantomData<Unit>);
 
 impl<T: Clone, Unit> Clone for Length<T, Unit> {
--- a/third_party/rust/euclid/src/lib.rs
+++ b/third_party/rust/euclid/src/lib.rs
@@ -7,30 +7,30 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
 #![cfg_attr(feature = "unstable", feature(asm, repr_simd, test))]
 
 //! A collection of strongly typed math tools for computer graphics with an inclination
 //! towards 2d graphics and layout.
 //!
-//! All types are generic over the the scalar type of their component (f32, i32, etc.),
+//! All types are generic over the scalar type of their component (`f32`, `i32`, etc.),
 //! and tagged with a generic Unit parameter which is useful to prevent mixing
 //! values from different spaces. For example it should not be legal to translate
 //! a screen-space position by a world-space vector and this can be expressed using
 //! the generic Unit parameter.
 //!
 //! This unit system is not mandatory and all Typed* structures have an alias
 //! with the default unit: `UnknownUnit`.
 //! for example ```Point2D<T>``` is equivalent to ```TypedPoint2D<T, UnknownUnit>```.
 //! Client code typically creates a set of aliases for each type and doesn't need
 //! to deal with the specifics of typed units further. For example:
 //!
-//! All euclid types are marked #[repr(C)] in order to facilitate exposing them to
-//! foreign function interfaces (provided the underlying scalar type is also repr(C)).
+//! All euclid types are marked `#[repr(C)]` in order to facilitate exposing them to
+//! foreign function interfaces (provided the underlying scalar type is also `repr(C)`).
 //!
 //! ```rust
 //! use euclid::*;
 //! pub struct ScreenSpace;
 //! pub type ScreenPoint = TypedPoint2D<f32, ScreenSpace>;
 //! pub type ScreenSize = TypedSize2D<f32, ScreenSpace>;
 //! pub struct WorldSpace;
 //! pub type WorldPoint = TypedPoint3D<f32, WorldSpace>;
@@ -89,17 +89,17 @@ mod macros;
 pub mod matrix2d;
 pub mod matrix4d;
 pub mod num;
 pub mod point;
 pub mod rect;
 pub mod scale_factor;
 pub mod side_offsets;
 pub mod size;
-mod trig;
+pub mod trig;
 
 /// The default unit.
 #[derive(Clone, Copy, RustcDecodable, RustcEncodable)]
 pub struct UnknownUnit;
 
 /// Unit for angles in radians.
 pub struct Rad;
 
--- a/third_party/rust/euclid/src/matrix2d.rs
+++ b/third_party/rust/euclid/src/matrix2d.rs
@@ -18,23 +18,23 @@ use trig::Trig;
 use std::fmt;
 
 define_matrix! {
     /// A 2d transform stored as a 2 by 3 matrix in row-major order in memory,
     /// useful to represent 2d transformations.
     ///
     /// Matrices can be parametrized over the source and destination units, to describe a
     /// transformation from a space to another.
-    /// For example, TypedMatrix2D<f32, WordSpace, ScreenSpace>::transform_point4d
-    /// takes a TypedPoint2D<f32, WordSpace> and returns a TypedPoint2D<f32, ScreenSpace>.
+    /// For example, `TypedMatrix2D<f32, WordSpace, ScreenSpace>::transform_point4d`
+    /// takes a `TypedPoint2D<f32, WordSpace>` and returns a `TypedPoint2D<f32, ScreenSpace>`.
     ///
     /// Matrices expose a set of convenience methods for pre- and post-transformations.
     /// A pre-transformation corresponds to adding an operation that is applied before
     /// the rest of the transformation, while a post-transformation adds an operation
-    /// that is appled after.
+    /// that is applied after.
     pub struct TypedMatrix2D<T, Src, Dst> {
         pub m11: T, pub m12: T,
         pub m21: T, pub m22: T,
         pub m31: T, pub m32: T,
     }
 }
 
 /// The default 2d matrix type with no units.
--- a/third_party/rust/euclid/src/matrix4d.rs
+++ b/third_party/rust/euclid/src/matrix4d.rs
@@ -20,23 +20,23 @@ use std::marker::PhantomData;
 use std::fmt;
 
 define_matrix! {
     /// A 4 by 4 matrix stored in row-major order in memory, useful to represent
     /// 3d transformations.
     ///
     /// Matrices can be parametrized over the source and destination units, to describe a
     /// transformation from a space to another.
-    /// For example, TypedMatrix4D<f32, WordSpace, ScreenSpace>::transform_point4d
-    /// takes a TypedPoint4D<f32, WordSpace> and returns a TypedPoint4D<f32, ScreenSpace>.
+    /// For example, `TypedMatrix4D<f32, WordSpace, ScreenSpace>::transform_point4d`
+    /// takes a `TypedPoint4D<f32, WordSpace>` and returns a `TypedPoint4D<f32, ScreenSpace>`.
     ///
     /// Matrices expose a set of convenience methods for pre- and post-transformations.
     /// A pre-transformation corresponds to adding an operation that is applied before
     /// the rest of the transformation, while a post-transformation adds an operation
-    /// that is appled after.
+    /// that is applied after.
     pub struct TypedMatrix4D<T, Src, Dst> {
         pub m11: T, pub m12: T, pub m13: T, pub m14: T,
         pub m21: T, pub m22: T, pub m23: T, pub m24: T,
         pub m31: T, pub m32: T, pub m33: T, pub m34: T,
         pub m41: T, pub m42: T, pub m43: T, pub m44: T,
     }
 }
 
--- a/third_party/rust/euclid/src/point.rs
+++ b/third_party/rust/euclid/src/point.rs
@@ -105,16 +105,26 @@ where T: Copy + Mul<T, Output=T> + Add<T
         self.x * other.x + self.y * other.y
     }
 
     /// Returns the norm of the cross product [self.x, self.y, 0] x [other.x, other.y, 0]..
     #[inline]
     pub fn cross(self, other: TypedPoint2D<T, U>) -> T {
         self.x * other.y - self.y * other.x
     }
+
+    #[inline]
+    pub fn normalize(self) -> Self where T: Float + ApproxEq<T> {
+        let dot = self.dot(self);
+        if dot.approx_eq(&T::zero()) {
+            self
+        } else {
+            self / dot.sqrt()
+        }
+    }
 }
 
 impl<T: Copy + Add<T, Output=T>, U> Add for TypedPoint2D<T, U> {
     type Output = TypedPoint2D<T, U>;
     fn add(self, other: TypedPoint2D<T, U>) -> TypedPoint2D<T, U> {
         TypedPoint2D::new(self.x + other.x, self.y + other.y)
     }
 }
@@ -188,85 +198,85 @@ impl<T: Copy + Div<T, Output=T>, U1, U2>
         TypedPoint2D::new(self.x / scale.get(), self.y / scale.get())
     }
 }
 
 impl<T: Round, U> TypedPoint2D<T, U> {
     /// Rounds each component to the nearest integer value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
-    /// For example { -0.1, -0.8 }.round() == { 0.0, -1.0 }
+    /// For example `{ -0.1, -0.8 }.round() == { 0.0, -1.0 }`.
     pub fn round(&self) -> Self {
         TypedPoint2D::new(self.x.round(), self.y.round())
     }
 }
 
 impl<T: Ceil, U> TypedPoint2D<T, U> {
-    /// Rounds each component to the smallest integer equal or greater than the orginal value.
+    /// Rounds each component to the smallest integer equal or greater than the original value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
-    /// For example { -0.1, -0.8 }.ceil() == { 0.0, 0.0 }.
+    /// For example `{ -0.1, -0.8 }.ceil() == { 0.0, 0.0 }`.
     pub fn ceil(&self) -> Self {
         TypedPoint2D::new(self.x.ceil(), self.y.ceil())
     }
 }
 
 impl<T: Floor, U> TypedPoint2D<T, U> {
-    /// Rounds each component to the biggest integer equal or lower than the orginal value.
+    /// Rounds each component to the biggest integer equal or lower than the original value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
-    /// For example { -0.1, -0.8 }.floor() == { -1.0, -1.0 }.
+    /// For example `{ -0.1, -0.8 }.floor() == { -1.0, -1.0 }`.
     pub fn floor(&self) -> Self {
         TypedPoint2D::new(self.x.floor(), self.y.floor())
     }
 }
 
 impl<T: NumCast + Copy, U> TypedPoint2D<T, U> {
     /// Cast from one numeric representation to another, preserving the units.
     ///
     /// When casting from floating point to integer coordinates, the decimals are truncated
     /// as one would expect from a simple cast, but this behavior does not always make sense
-    /// geometrically. Consider using round(), ceil or floor() before casting.
+    /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
     pub fn cast<NewT: NumCast + Copy>(&self) -> Option<TypedPoint2D<NewT, U>> {
         match (NumCast::from(self.x), NumCast::from(self.y)) {
             (Some(x), Some(y)) => Some(TypedPoint2D::new(x, y)),
             _ => None
         }
     }
 
     // Convenience functions for common casts
 
-    /// Cast into an f32 vector.
+    /// Cast into an `f32` point.
     pub fn to_f32(&self) -> TypedPoint2D<f32, U> {
         self.cast().unwrap()
     }
 
-    /// Cast into an usize point, truncating decimals if any.
+    /// Cast into an `usize` point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_uint(&self) -> TypedPoint2D<usize, U> {
         self.cast().unwrap()
     }
 
     /// Cast into an i32 point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_i32(&self) -> TypedPoint2D<i32, U> {
         self.cast().unwrap()
     }
 
     /// Cast into an i64 point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_i64(&self) -> TypedPoint2D<i64, U> {
         self.cast().unwrap()
     }
 }
 
 impl<T: Copy+ApproxEq<T>, U> ApproxEq<TypedPoint2D<T, U>> for TypedPoint2D<T, U> {
     #[inline]
     fn approx_epsilon() -> Self {
@@ -381,16 +391,26 @@ impl<T: Mul<T, Output=T> +
 
     // Cross product.
     #[inline]
     pub fn cross(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
         TypedPoint3D::new(self.y * other.z - self.z * other.y,
                           self.z * other.x - self.x * other.z,
                           self.x * other.y - self.y * other.x)
     }
+
+    #[inline]
+    pub fn normalize(self) -> Self where T: Float + ApproxEq<T> {
+        let dot = self.dot(self);
+        if dot.approx_eq(&T::zero()) {
+            self
+        } else {
+            self / dot.sqrt()
+        }
+    }
 }
 
 impl<T: Copy + Add<T, Output=T>, U> Add for TypedPoint3D<T, U> {
     type Output = TypedPoint3D<T, U>;
     fn add(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
         TypedPoint3D::new(self.x + other.x,
                           self.y + other.y,
                           self.z + other.z)
@@ -409,16 +429,32 @@ impl<T: Copy + Sub<T, Output=T>, U> Sub 
 impl <T: Copy + Neg<Output=T>, U> Neg for TypedPoint3D<T, U> {
     type Output = TypedPoint3D<T, U>;
     #[inline]
     fn neg(self) -> TypedPoint3D<T, U> {
         TypedPoint3D::new(-self.x, -self.y, -self.z)
     }
 }
 
+impl<T: Copy + Mul<T, Output=T>, U> Mul<T> for TypedPoint3D<T, U> {
+    type Output = Self;
+    #[inline]
+    fn mul(self, scale: T) -> Self {
+        Self::new(self.x * scale, self.y * scale, self.z * scale)
+    }
+}
+
+impl<T: Copy + Div<T, Output=T>, U> Div<T> for TypedPoint3D<T, U> {
+    type Output = Self;
+    #[inline]
+    fn div(self, scale: T) -> Self {
+        Self::new(self.x / scale, self.y / scale, self.z / scale)
+    }
+}
+
 impl<T: Float, U> TypedPoint3D<T, U> {
     pub fn min(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
          TypedPoint3D::new(self.x.min(other.x),
                            self.y.min(other.y),
                            self.z.min(other.z))
     }
 
     pub fn max(self, other: TypedPoint3D<T, U>) -> TypedPoint3D<T, U> {
@@ -432,26 +468,26 @@ impl<T: Round, U> TypedPoint3D<T, U> {
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn round(&self) -> Self {
         TypedPoint3D::new(self.x.round(), self.y.round(), self.z.round())
     }
 }
 
 impl<T: Ceil, U> TypedPoint3D<T, U> {
-    /// Rounds each component to the smallest integer equal or greater than the orginal value.
+    /// Rounds each component to the smallest integer equal or greater than the original value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn ceil(&self) -> Self {
         TypedPoint3D::new(self.x.ceil(), self.y.ceil(), self.z.ceil())
     }
 }
 
 impl<T: Floor, U> TypedPoint3D<T, U> {
-    /// Rounds each component to the biggest integer equal or lower than the orginal value.
+    /// Rounds each component to the biggest integer equal or lower than the original value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn floor(&self) -> Self {
         TypedPoint3D::new(self.x.floor(), self.y.floor(), self.z.floor())
     }
 }
 
 impl<T: NumCast + Copy, U> TypedPoint3D<T, U> {
@@ -466,44 +502,44 @@ impl<T: NumCast + Copy, U> TypedPoint3D<
                NumCast::from(self.z)) {
             (Some(x), Some(y), Some(z)) => Some(TypedPoint3D::new(x, y, z)),
             _ => None
         }
     }
 
     // Convenience functions for common casts
 
-    /// Cast into an f32 vector.
+    /// Cast into an `f32` point.
     pub fn to_f32(&self) -> TypedPoint3D<f32, U> {
         self.cast().unwrap()
     }
 
-    /// Cast into an usize point, truncating decimals if any.
+    /// Cast into an `usize` point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_uint(&self) -> TypedPoint3D<usize, U> {
         self.cast().unwrap()
     }
 
-    /// Cast into an i32 point, truncating decimals if any.
+    /// Cast into an `i32` point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_i32(&self) -> TypedPoint3D<i32, U> {
         self.cast().unwrap()
     }
 
-    /// Cast into an i64 point, truncating decimals if any.
+    /// Cast into an `i64` point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_i64(&self) -> TypedPoint3D<i64, U> {
         self.cast().unwrap()
     }
 }
 
 impl<T: Copy+ApproxEq<T>, U> ApproxEq<TypedPoint3D<T, U>> for TypedPoint3D<T, U> {
     #[inline]
     fn approx_epsilon() -> Self {
@@ -670,79 +706,79 @@ impl<T: Round, U> TypedPoint4D<T, U> {
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn round(&self) -> Self {
         TypedPoint4D::new(self.x.round(), self.y.round(), self.z.round(), self.w.round())
     }
 }
 
 impl<T: Ceil, U> TypedPoint4D<T, U> {
-    /// Rounds each component to the smallest integer equal or greater than the orginal value.
+    /// Rounds each component to the smallest integer equal or greater than the original value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn ceil(&self) -> Self {
         TypedPoint4D::new(self.x.ceil(), self.y.ceil(), self.z.ceil(), self.w.ceil())
     }
 }
 
 impl<T: Floor, U> TypedPoint4D<T, U> {
-    /// Rounds each component to the biggest integer equal or lower than the orginal value.
+    /// Rounds each component to the biggest integer equal or lower than the original value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn floor(&self) -> Self {
         TypedPoint4D::new(self.x.floor(), self.y.floor(), self.z.floor(), self.w.floor())
     }
 }
 
 impl<T: NumCast + Copy, U> TypedPoint4D<T, U> {
     /// Cast from one numeric representation to another, preserving the units.
     ///
     /// When casting from floating point to integer coordinates, the decimals are truncated
     /// as one would expect from a simple cast, but this behavior does not always make sense
-    /// geometrically. Consider using round(), ceil or floor() before casting.
+    /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
     pub fn cast<NewT: NumCast + Copy>(&self) -> Option<TypedPoint4D<NewT, U>> {
         match (NumCast::from(self.x),
                NumCast::from(self.y),
                NumCast::from(self.z),
                NumCast::from(self.w)) {
             (Some(x), Some(y), Some(z), Some(w)) => Some(TypedPoint4D::new(x, y, z, w)),
             _ => None
         }
     }
 
     // Convenience functions for common casts
 
-    /// Cast into an f32 vector.
+    /// Cast into an `f32` point.
     pub fn to_f32(&self) -> TypedPoint4D<f32, U> {
         self.cast().unwrap()
     }
 
-    /// Cast into an usize point, truncating decimals if any.
+    /// Cast into an `usize` point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_uint(&self) -> TypedPoint4D<usize, U> {
         self.cast().unwrap()
     }
 
-    /// Cast into an i32 point, truncating decimals if any.
+    /// Cast into an `i32` point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_i32(&self) -> TypedPoint4D<i32, U> {
         self.cast().unwrap()
     }
 
-    /// Cast into an i64 point, truncating decimals if any.
+    /// Cast into an `i64` point, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point points, it is worth considering whether
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_i64(&self) -> TypedPoint4D<i64, U> {
         self.cast().unwrap()
     }
 }
 
 impl<T: ApproxEq<T>, U> ApproxEq<T> for TypedPoint4D<T, U> {
     fn approx_epsilon() -> T {
         T::approx_epsilon()
@@ -796,16 +832,26 @@ mod point2d {
     pub fn test_cross() {
         let p1: Point2D<f32> = Point2D::new(4.0, 7.0);
         let p2: Point2D<f32> = Point2D::new(13.0, 8.0);
         let r = p1.cross(p2);
         assert_eq!(r, -59.0);
     }
 
     #[test]
+    pub fn test_normalize() {
+        let p0: Point2D<f32> = Point2D::zero();
+        let p1: Point2D<f32> = Point2D::new(4.0, 0.0);
+        let p2: Point2D<f32> = Point2D::new(3.0, -4.0);
+        assert_eq!(p0.normalize(), p0);
+        assert_eq!(p1.normalize(), Point2D::new(1.0, 0.0));
+        assert_eq!(p2.normalize(), Point2D::new(0.6, -0.8));
+    }
+
+    #[test]
     pub fn test_min() {
         let p1 = Point2D::new(1.0, 3.0);
         let p2 = Point2D::new(2.0, 2.0);
 
         let result = p1.min(p2);
 
         assert_eq!(result, Point2D::new(1.0, 2.0));
     }
@@ -868,16 +914,26 @@ mod point3d {
     pub fn test_cross() {
         let p1 = Point3D::new(4.0, 7.0, 9.0);
         let p2 = Point3D::new(13.0, 8.0, 3.0);
         let p3 = p1.cross(p2);
         assert_eq!(p3, Point3D::new(-51.0, 105.0, -59.0));
     }
 
     #[test]
+    pub fn test_normalize() {
+        let p0: Point3D<f32> = Point3D::zero();
+        let p1: Point3D<f32> = Point3D::new(0.0, -6.0, 0.0);
+        let p2: Point3D<f32> = Point3D::new(1.0, 2.0, -2.0);
+        assert_eq!(p0.normalize(), p0);
+        assert_eq!(p1.normalize(), Point3D::new(0.0, -1.0, 0.0));
+        assert_eq!(p2.normalize(), Point3D::new(1.0/3.0, 2.0/3.0, -2.0/3.0));
+    }
+
+    #[test]
     pub fn test_min() {
         let p1 = Point3D::new(1.0, 3.0, 5.0);
         let p2 = Point3D::new(2.0, 2.0, -1.0);
 
         let result = p1.min(p2);
 
         assert_eq!(result, Point3D::new(1.0, 2.0, -1.0));
     }
--- a/third_party/rust/euclid/src/rect.rs
+++ b/third_party/rust/euclid/src/rect.rs
@@ -389,50 +389,50 @@ impl<T: Floor + Ceil + Round + Add<T, Ou
         let origin = self.origin.floor();
         let size = self.origin.add_size(&self.size).ceil() - origin;
         TypedRect::new(origin, TypedSize2D::new(size.x, size.y))
     }
 }
 
 // Convenience functions for common casts
 impl<T: NumCast + Copy, Unit> TypedRect<T, Unit> {
-    /// Cast into an f32 vector.
+    /// Cast into an `f32` rectangle.
     pub fn to_f32(&self) -> TypedRect<f32, Unit> {
         self.cast().unwrap()
     }
 
-    /// Cast into an usize vector, truncating decimals if any.
+    /// Cast into an `usize` rectangle, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), round_in() or round_out() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point rectangles, it is worth considering whether
+    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+    /// obtain the desired conversion behavior.
     pub fn to_uint(&self) -> TypedRect<usize, Unit> {
         self.cast().unwrap()
     }
 
-    /// Cast into an i32 vector, truncating decimals if any.
+    /// Cast into an `i32` rectangle, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), round_in() or round_out() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point rectangles, it is worth considering whether
+    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+    /// obtain the desired conversion behavior.
     pub fn to_i32(&self) -> TypedRect<i32, Unit> {
         self.cast().unwrap()
     }
 
-    /// Cast into an i64 vector, truncating decimals if any.
+    /// Cast into an `i64` rectangle, truncating decimals if any.
     ///
-    /// When casting from floating point vectors, it is worth considering whether
-    /// to round(), round_in() or round_out() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// When casting from floating point rectangles, it is worth considering whether
+    /// to `round()`, `round_in()` or `round_out()` before the cast in order to
+    /// obtain the desired conversion behavior.
     pub fn to_i64(&self) -> TypedRect<i64, Unit> {
         self.cast().unwrap()
     }
 }
 
-/// Shorthand for TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h)).
+/// Shorthand for `TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))`.
 pub fn rect<T: Copy, U>(x: T, y: T, w: T, h: T) -> TypedRect<T, U> {
     TypedRect::new(TypedPoint2D::new(x, y), TypedSize2D::new(w, h))
 }
 
 #[cfg(test)]
 mod tests {
     use point::Point2D;
     use size::Size2D;
--- a/third_party/rust/euclid/src/size.rs
+++ b/third_party/rust/euclid/src/size.rs
@@ -50,42 +50,42 @@ impl<T, U> TypedSize2D<T, U> {
             width: width,
             height: height,
             _unit: PhantomData,
         }
     }
 }
 
 impl<T: Clone, U> TypedSize2D<T, U> {
-    /// Constructor taking scalar stronlgy typed lengths.
+    /// Constructor taking scalar strongly typed lengths.
     pub fn from_lengths(width: Length<T, U>, height: Length<T, U>) -> TypedSize2D<T, U> {
         TypedSize2D::new(width.get(), height.get())
     }
 }
 
 impl<T: Round, U> TypedSize2D<T, U> {
     /// Rounds each component to the nearest integer value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn round(&self) -> Self {
         TypedSize2D::new(self.width.round(), self.height.round())
     }
 }
 
 impl<T: Ceil, U> TypedSize2D<T, U> {
-    /// Rounds each component to the smallest integer equal or greater than the orginal value.
+    /// Rounds each component to the smallest integer equal or greater than the original value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn ceil(&self) -> Self {
         TypedSize2D::new(self.width.ceil(), self.height.ceil())
     }
 }
 
 impl<T: Floor, U> TypedSize2D<T, U> {
-    /// Rounds each component to the biggest integer equal or lower than the orginal value.
+    /// Rounds each component to the biggest integer equal or lower than the original value.
     ///
     /// This behavior is preserved for negative values (unlike the basic cast).
     pub fn floor(&self) -> Self {
         TypedSize2D::new(self.width.floor(), self.height.floor())
     }
 }
 
 impl<T: Copy + Add<T, Output=T>, U> Add for TypedSize2D<T, U> {
@@ -178,61 +178,61 @@ impl<T: Copy, U> TypedSize2D<T, U> {
         TypedSize2D::new(p.width, p.height)
     }
 }
 
 impl<T: NumCast + Copy, Unit> TypedSize2D<T, Unit> {
     /// Cast from one numeric representation to another, preserving the units.
     ///
     /// When casting from floating point to integer coordinates, the decimals are truncated
-    /// as one would expect from a simple cast, but this behavior does not always marke sense
-    /// geometrically. Consider using round(), ceil or floor() before casting.
+    /// as one would expect from a simple cast, but this behavior does not always make sense
+    /// geometrically. Consider using `round()`, `ceil()` or `floor()` before casting.
     pub fn cast<NewT: NumCast + Copy>(&self) -> Option<TypedSize2D<NewT, Unit>> {
         match (NumCast::from(self.width), NumCast::from(self.height)) {
             (Some(w), Some(h)) => Some(TypedSize2D::new(w, h)),
             _ => None
         }
     }
 
     // Convenience functions for common casts
 
-    /// Cast into an f32 size.
+    /// Cast into an `f32` size.
     pub fn to_f32(&self) -> TypedSize2D<f32, Unit> {
         self.cast().unwrap()
     }
 
-    /// Cast into an usize size, truncating decimals if any.
+    /// Cast into an `uint` size, truncating decimals if any.
     ///
     /// When casting from floating point sizes, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_uint(&self) -> TypedSize2D<usize, Unit> {
         self.cast().unwrap()
     }
 
-    /// Cast into an i32 size, truncating decimals if any.
+    /// Cast into an `i32` size, truncating decimals if any.
     ///
     /// When casting from floating point sizes, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_i32(&self) -> TypedSize2D<i32, Unit> {
         self.cast().unwrap()
     }
 
-    /// Cast into an i64 size, truncating decimals if any.
+    /// Cast into an `i64` size, truncating decimals if any.
     ///
     /// When casting from floating point sizes, it is worth considering whether
-    /// to round(), ceil() or floor() before the cast in order to obtain the desired
-    /// conversion behavior.
+    /// to `round()`, `ceil()` or `floor()` before the cast in order to obtain
+    /// the desired conversion behavior.
     pub fn to_i64(&self) -> TypedSize2D<i64, Unit> {
         self.cast().unwrap()
     }
 }
 
-/// Shorthand for TypedSize2D::new(w, h).
+/// Shorthand for `TypedSize2D::new(w, h)`.
 pub fn size2<T, U>(w: T, h: T) -> TypedSize2D<T, U> {
     TypedSize2D::new(w, h)
 }
 
 #[cfg(test)]
 mod size2d {
     use super::Size2D;
 
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/.cargo-checksum.json
@@ -0,0 +1,1 @@
+{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".gitignore":"f9b1ca6ae27d1c18215265024629a8960c31379f206d9ed20f64e0b2dcf79805",".travis.yml":"b76d49f66f842c652d40825c67791352364a6b6bbb7d8d1009f2ac79eb413e66","Cargo.toml":"b7991659a5ad212104ef390baee63066645a20af5b7d0891bd9166b38cab1f7d","LICENSE":"b946744aeda89b467929585fe8eeb5461847695220c1b168fb375d8abd4ea3d0","README.md":"62f99334c17b451342fcea70eb1cc27b26612616b7c1a58fab50dd493f766f32","benches/split.rs":"49befe22321f34280106fdea53d93644b7757873407376247f86f9d55d09b4ab","src/bsp.rs":"6ec056292b3499f16ad520af7b0480471e34c136bb29429c3c4e1d68efd42a57","src/lib.rs":"368c320c9d4f4bb788b3fb5c0b0e0a6b9756d0da02035539e845639ab9bff80e","src/naive.rs":"8c0e93fcdc30e90fa9da4a032a55fe58619e8e8928000583575eb2f3e001e331","tests/main.rs":"7177353c68b31a78cda67da0485b45d8551961b36e23121d549aa535e68dd287","tests/split.rs":"a4681a788f9a9a515d4084d97ba33406a54bc0725711ade9fc955348d1703368"},"package":"a05e1e40e37630095627acfd2c7c6cf259e8b4f4ef4f01b2adf2a35331e45975"}
\ No newline at end of file
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/.gitignore
@@ -0,0 +1,2 @@
+target
+Cargo.lock
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/.travis.yml
@@ -0,0 +1,11 @@
+sudo: false
+language: rust
+cache: cargo
+rust:
+  - nightly
+  - stable
+script:
+  - cargo build
+  - cargo doc
+  - cargo test
+  - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then (cargo bench); fi
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "plane-split"
+version = "0.2.1"
+description = "Plane splitting"
+authors = ["Dzmitry Malyshau <kvark@mozilla.com>"]
+license = "MPL-2.0"
+repository = "https://github.com/kvark/plane-split"
+keywords = ["geometry", "math"]
+documentation = "https://docs.rs/plane-split"
+
+[dependencies]
+binary-space-partition = "0.1.1"
+euclid = "0.11.2"
+log = "0.3"
+num-traits = {version = "0.1.37", default-features = false}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/LICENSE
@@ -0,0 +1,374 @@
+ Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+means each individual or legal entity that creates, contributes to
+the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+means the combination of the Contributions of others (if any) used
+by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+means Source Code Form to which the initial Contributor has attached
+the notice in Exhibit A, the Executable Form of such Source Code
+Form, and Modifications of such Source Code Form, in each case
+including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+means
+
+(a) that the initial Contributor has attached the notice described
+in Exhibit B to the Covered Software; or
+
+(b) that the Covered Software was made available under the terms of
+version 1.1 or earlier of the License, but not also under the
+terms of a Secondary License.
+
+1.6. "Executable Form"
+means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+means a work that combines Covered Software with other material, in
+a separate file or files, that is not Covered Software.
+
+1.8. "License"
+means this document.
+
+1.9. "Licensable"
+means having the right to grant, to the maximum extent possible,
+whether at the time of the initial grant or subsequently, any and
+all of the rights conveyed by this License.
+
+1.10. "Modifications"
+means any of the following:
+
+(a) any file in Source Code Form that results from an addition to,
+deletion from, or modification of the contents of Covered
+Software; or
+
+(b) any new file in Source Code Form that contains any Covered
+Software.
+
+1.11. "Patent Claims" of a Contributor
+means any patent claim(s), including without limitation, method,
+process, and apparatus claims, in any patent Licensable by such
+Contributor that would be infringed, but for the grant of the
+License, by the making, using, selling, offering for sale, having
+made, import, or transfer of either its Contributions or its
+Contributor Version.
+
+1.12. "Secondary License"
+means either the GNU General Public License, Version 2.0, the GNU
+Lesser General Public License, Version 2.1, the GNU Affero General
+Public License, Version 3.0, or any later versions of those
+licenses.
+
+1.13. "Source Code Form"
+means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+means an individual or a legal entity exercising rights under this
+License. For legal entities, "You" includes any entity that
+controls, is controlled by, or is under common control with You. For
+purposes of this definition, "control" means (a) the power, direct
+or indirect, to cause the direction or management of such entity,
+whether by contract or otherwise, or (b) ownership of more than
+fifty percent (50%) of the outstanding shares or beneficial
+ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+Licensable by such Contributor to use, reproduce, make available,
+modify, display, perform, distribute, and otherwise exploit its
+Contributions, either on an unmodified basis, with Modifications, or
+as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+for sale, have made, import, and otherwise transfer either its
+Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+or
+
+(b) for infringements caused by: (i) Your and any other third party's
+modifications of Covered Software, or (ii) the combination of its
+Contributions with other software (except as part of its Contributor
+Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+Form, as described in Section 3.1, and You must inform recipients of
+the Executable Form how they can obtain a copy of such Source Code
+Form by reasonable means in a timely manner, at a charge no more
+than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+License, or sublicense it under different terms, provided that the
+license for the Executable Form does not attempt to limit or alter
+the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+This Source Code Form is "Incompatible With Secondary Licenses", as
+defined by the Mozilla Public License, v. 2.0.
+
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/README.md
@@ -0,0 +1,4 @@
+# plane-split
+[![Build Status](https://travis-ci.org/kvark/plane-split.svg)](https://travis-ci.org/kvark/plane-split) [![](http://meritbadge.herokuapp.com/plane-split)](https://crates.io/crates/plane-split) [![Documentation](https://docs.rs/plane-split/badge.svg)](https://docs.rs/plane-split)
+
+Plane splitting with [euclid](https://crates.io/crates/euclid).
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/benches/split.rs
@@ -0,0 +1,31 @@
+#![feature(test)]
+
+extern crate euclid;
+extern crate plane_split;
+extern crate test;
+
+use std::sync::Arc;
+use euclid::TypedPoint3D;
+use plane_split::{BspSplitter, NaiveSplitter, Splitter, _make_grid};
+
+#[bench]
+fn bench_naive(b: &mut test::Bencher) {
+    let polys = Arc::new(_make_grid(5));
+    let mut splitter = NaiveSplitter::new();
+    let view = TypedPoint3D::new(0.0, 0.0, 1.0);
+    b.iter(|| {
+        let p = polys.clone();
+        splitter.solve(&p, view);
+    });
+}
+
+#[bench]
+fn bench_bsp(b: &mut test::Bencher) {
+    let polys = Arc::new(_make_grid(5));
+    let mut splitter = BspSplitter::new();
+    let view = TypedPoint3D::new(0.0, 0.0, 1.0);
+    b.iter(|| {
+        let p = polys.clone();
+        splitter.solve(&p, view);
+    });
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/src/bsp.rs
@@ -0,0 +1,101 @@
+use binary_space_partition::{BspNode, Plane, PlaneCut};
+use euclid::TypedPoint3D;
+use euclid::approxeq::ApproxEq;
+use num_traits::{Float, One, Zero};
+use std::{fmt, ops};
+use {Intersection, Polygon, Splitter};
+
+
+impl<T: Copy + fmt::Debug + PartialOrd + ApproxEq<T> +
+        ops::Sub<T, Output=T> + ops::Add<T, Output=T> +
+        ops::Mul<T, Output=T> + ops::Div<T, Output=T> +
+        Zero + One + Float,
+     U> Plane for Polygon<T, U> {
+
+    fn cut(&self, mut plane: Self) -> PlaneCut<Self> {
+        let dist = self.signed_distance_sum_to(&plane);
+        match self.intersect(&plane) {
+            Intersection::Coplanar if dist.approx_eq(&T::zero()) => {
+                PlaneCut::Sibling(plane)
+            }
+            Intersection::Coplanar | Intersection::Outside => {
+                if dist > T::zero() {
+                    PlaneCut::Cut {
+                        front: vec![plane],
+                        back: vec![],
+                    }
+                } else {
+                    PlaneCut::Cut {
+                        front: vec![],
+                        back: vec![plane],
+                    }
+                }
+            }
+            Intersection::Inside(line) => {
+                let (res_add1, res_add2) = plane.split(&line);
+                let mut front = Vec::new();
+                let mut back = Vec::new();
+
+                for sub in Some(plane).into_iter().chain(res_add1).chain(res_add2) {
+                    if self.signed_distance_sum_to(&sub) > T::zero() {
+                        front.push(sub)
+                    } else {
+                        back.push(sub)
+                    }
+                }
+
+                PlaneCut::Cut {
+                    front: front,
+                    back: back,
+                }
+            },
+        }
+    }
+
+    fn is_aligned(&self, plane: &Self) -> bool {
+        self.normal.dot(plane.normal) > T::zero()
+    }
+}
+
+
+/// Binary Space Partitioning splitter, uses a BSP tree.
+pub struct BspSplitter<T, U> {
+    tree: BspNode<Polygon<T, U>>,
+    result: Vec<Polygon<T, U>>,
+}
+
+impl<T, U> BspSplitter<T, U> {
+    /// Create a new BSP splitter.
+    pub fn new() -> Self {
+        BspSplitter {
+            tree: BspNode::new(),
+            result: Vec::new(),
+        }
+    }
+}
+
+impl<T: Copy + fmt::Debug + PartialOrd + ApproxEq<T> +
+        ops::Sub<T, Output=T> + ops::Add<T, Output=T> +
+        ops::Mul<T, Output=T> + ops::Div<T, Output=T> +
+        Zero + One + Float,
+     U> Splitter<T, U> for BspSplitter<T, U> {
+
+    fn reset(&mut self) {
+        self.tree = BspNode::new();
+    }
+
+    fn add(&mut self, poly: Polygon<T, U>) {
+        self.tree.insert(poly);
+    }
+
+    fn sort(&mut self, view: TypedPoint3D<T, U>) -> &[Polygon<T, U>] {
+        let poly = Polygon {
+            points: [TypedPoint3D::zero(); 4],
+            normal: view,
+            offset: T::zero(),
+            anchor: 0,
+        };
+        self.tree.order(&poly, &mut self.result);
+        &self.result
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/src/lib.rs
@@ -0,0 +1,483 @@
+/*!
+Plane splitting.
+
+Uses [euclid](https://crates.io/crates/euclid) for the math basis.
+Introduces new geometrical primitives and associated logic.
+
+Automatically splits a given set of 4-point polygons into sub-polygons
+that don't intersect each other. This is useful for WebRender, to sort
+the resulting sub-polygons by depth and avoid transparency blending issues.
+*/
+#![warn(missing_docs)]
+
+extern crate binary_space_partition;
+extern crate euclid;
+#[macro_use]
+extern crate log;
+extern crate num_traits;
+
+mod bsp;
+mod naive;
+
+use std::{fmt, mem, ops};
+use euclid::{Point2D, TypedMatrix4D, TypedPoint3D, TypedRect};
+use euclid::approxeq::ApproxEq;
+use euclid::trig::Trig;
+use num_traits::{Float, One, Zero};
+
+pub use self::bsp::BspSplitter;
+pub use self::naive::NaiveSplitter;
+
+/// A generic line.
+#[derive(Debug)]
+pub struct Line<T, U> {
+    /// Arbitrary point on the line.
+    pub origin: TypedPoint3D<T, U>,
+    /// Normalized direction of the line.
+    pub dir: TypedPoint3D<T, U>,
+}
+
+impl<
+    T: Copy + One + Zero + PartialEq + ApproxEq<T> +
+       ops::Add<T, Output=T> + ops::Sub<T, Output=T> + ops::Mul<T, Output=T>,
+    U,
+> Line<T, U> {
+    /// Check if the line has consistent parameters.
+    pub fn is_valid(&self) -> bool {
+        self.dir.dot(self.dir).approx_eq(&T::one())
+    }
+    /// Check if two lines match each other.
+    pub fn matches(&self, other: &Self) -> bool {
+        let diff = self.origin - other.origin;
+        let zero = TypedPoint3D::zero();
+        self.dir.cross(other.dir).approx_eq(&zero) &&
+        self.dir.cross(diff).approx_eq(&zero)
+    }
+}
+
+/// A convex flat polygon with 4 points, defined by equation:
+/// dot(v, normal) + offset = 0
+#[derive(Debug, PartialEq)]
+pub struct Polygon<T, U> {
+    /// Points making the polygon.
+    pub points: [TypedPoint3D<T, U>; 4],
+    /// Normalized vector perpendicular to the polygon plane.
+    pub normal: TypedPoint3D<T, U>,
+    /// Constant offset from the normal plane, specified in the
+    /// direction opposite to the normal.
+    pub offset: T,
+    /// A simple anchoring index to allow association of the
+    /// produced split polygons with the original one.
+    pub anchor: usize,
+}
+
+impl<T: Clone, U> Clone for Polygon<T, U> {
+    fn clone(&self) -> Self {
+        Polygon {
+            points: [self.points[0].clone(),
+                     self.points[1].clone(),
+                     self.points[2].clone(),
+                     self.points[3].clone()],
+            normal: self.normal.clone(),
+            offset: self.offset.clone(),
+            anchor: self.anchor,
+        }
+    }
+}
+
+/// The projection of a `Polygon` on a line.
+pub struct LineProjection<T> {
+    /// Projected value of each point in the polygon.
+    pub markers: [T; 4],
+}
+
+impl<T: Copy + PartialOrd + ops::Sub<T, Output=T> + ops::Add<T, Output=T>> LineProjection<T> {
+    /// Get the min/max of the line projection markers.
+    pub fn get_bounds(&self) -> (T, T) {
+        let (mut a, mut b, mut c, mut d) = (self.markers[0], self.markers[1], self.markers[2], self.markers[3]);
+        // bitonic sort of 4 elements
+        // we could not just use `min/max` since they require `Ord` bound
+        if a > c {
+            mem::swap(&mut a, &mut c);
+        }
+        if b > d {
+            mem::swap(&mut b, &mut d);
+        }
+        if a > b {
+            mem::swap(&mut a, &mut b);
+        }
+        if c > d {
+            mem::swap(&mut c, &mut d);
+        }
+        if b > c {
+            mem::swap(&mut b, &mut c);
+        }
+        debug_assert!(a <= b && b <= c && c <= d);
+        (a, d)
+    }
+
+    /// Check intersection with another line projection.
+    pub fn intersect(&self, other: &Self) -> bool {
+        // compute the bounds of both line projections
+        let span = self.get_bounds();
+        let other_span = other.get_bounds();
+        // compute the total footprint
+        let left = if span.0 < other_span.0 { span.0 } else { other_span.0 };
+        let right = if span.1 > other_span.1 { span.1 } else { other_span.1 };
+        // they intersect if the footprint is smaller than the sum
+        right - left < span.1 - span.0 + other_span.1 - other_span.0
+    }
+}
+
+/// Polygon intersection results.
+pub enum Intersection<T> {
+    /// Polygons are coplanar, including the case of being on the same plane.
+    Coplanar,
+    /// Polygon planes are intersecting, but polygons are not.
+    Outside,
+    /// Polygons are actually intersecting.
+    Inside(T),
+}
+
+impl<T> Intersection<T> {
+    /// Return true if the intersection is completely outside.
+    pub fn is_outside(&self) -> bool {
+        match *self {
+            Intersection::Outside => true,
+            _ => false,
+        }
+    }
+    /// Return true if the intersection cuts the source polygon.
+    pub fn is_inside(&self) -> bool {
+        match *self {
+            Intersection::Inside(_) => true,
+            _ => false,
+        }
+    }
+}
+
+impl<T: Copy + fmt::Debug + PartialOrd + ApproxEq<T> +
+        ops::Sub<T, Output=T> + ops::Add<T, Output=T> +
+        ops::Mul<T, Output=T> + ops::Div<T, Output=T> +
+        Zero + One + Float,
+     U> Polygon<T, U> {
+
+    /// Construct a polygon from a transformed rectangle.
+    pub fn from_transformed_rect<V>(rect: TypedRect<T, V>,
+                                    transform: TypedMatrix4D<T, V, U>,
+                                    anchor: usize)
+                                    -> Polygon<T, U>
+    where T: Trig + ops::Neg<Output=T> {
+        let points = [
+            transform.transform_point3d(&rect.origin.to_3d()),
+            transform.transform_point3d(&rect.top_right().to_3d()),
+            transform.transform_point3d(&rect.bottom_right().to_3d()),
+            transform.transform_point3d(&rect.bottom_left().to_3d()),
+        ];
+
+        //Note: this code path could be more efficient if we had inverse-transpose
+        //let n4 = transform.transform_point4d(&TypedPoint4D::new(T::zero(), T::zero(), T::one(), T::zero()));
+        //let normal = TypedPoint3D::new(n4.x, n4.y, n4.z);
+
+        let normal = (points[1] - points[0]).cross(points[2] - points[0])
+                                            .normalize();
+        let offset = -TypedPoint3D::new(transform.m41, transform.m42, transform.m43).dot(normal);
+
+        Polygon {
+            points: points,
+            normal: normal,
+            offset: offset,
+            anchor: anchor,
+        }
+    }
+
+    /// Bring a point into the local coordinate space, returning
+    /// the 2D normalized coordinates.
+    pub fn untransform_point(&self, point: TypedPoint3D<T, U>) -> Point2D<T> {
+        //debug_assert!(self.contains(point));
+        // get axises and target vector
+        let a = self.points[1] - self.points[0];
+        let b = self.points[3] - self.points[0];
+        let c = point - self.points[0];
+        // get pair-wise dot products
+        let a2 = a.dot(a);
+        let ab = a.dot(b);
+        let b2 = b.dot(b);
+        let ca = c.dot(a);
+        let cb = c.dot(b);
+        // compute the final coordinates
+        let denom = ab * ab - a2 * b2;
+        let x = ab * cb - b2 * ca;
+        let y = ab * ca - a2 * cb;
+        Point2D::new(x, y) / denom
+    }
+
+    /// Return the signed distance from this polygon to a point.
+    /// The distance is negative if the point is on the other side of the polygon
+    /// from the direction of the normal.
+    pub fn signed_distance_to(&self, point: &TypedPoint3D<T, U>) -> T {
+        point.dot(self.normal) + self.offset
+    }
+
+    /// Compute the distance across the line to the polygon plane,
+    /// starting from the line origin.
+    pub fn distance_to_line(&self, line: &Line<T, U>) -> T
+    where T: ops::Neg<Output=T> {
+        self.signed_distance_to(&line.origin) / -self.normal.dot(line.dir)
+    }
+
+    /// Compute the sum of signed distances to each of the points
+    /// of another polygon. Useful to know the relation of a polygon that
+    /// is a product of a split, and we know it doesn't intersect `self`.
+    pub fn signed_distance_sum_to(&self, other: &Self) -> T {
+        other.points.iter().fold(T::zero(), |sum, p| {
+            sum + self.signed_distance_to(p)
+        })
+    }
+
+    /// Check if all the points are indeed placed on the plane defined by
+    /// the normal and offset, and the winding order is consistent.
+    /// The epsion is specified for the plane distance calculations.
+    pub fn is_valid_eps(&self, eps: T) -> bool {
+        let is_planar = self.points.iter()
+                                   .all(|p| self.signed_distance_to(p).approx_eq_eps(&T::zero(), &eps));
+        let edges = [self.points[1] - self.points[0],
+                     self.points[2] - self.points[1],
+                     self.points[3] - self.points[2],
+                     self.points[0] - self.points[3]];
+        let anchor = edges[3].cross(edges[0]);
+        let is_winding = edges.iter()
+                              .zip(edges[1..].iter())
+                              .all(|(a, &b)| a.cross(b).dot(anchor) >= T::zero());
+        is_planar && is_winding
+    }
+
+    /// Check validity. Similar to `is_valid_eps` but with default epsilon.
+    pub fn is_valid(&self) -> bool {
+        self.is_valid_eps(T::approx_epsilon())
+    }
+
+    /// Check if a convex shape defined by a set of points is completely
+    /// outside of this polygon. Merely touching the surface is not
+    /// considered an intersection.
+    pub fn are_outside(&self, points: &[TypedPoint3D<T, U>]) -> bool {
+        let d0 = self.signed_distance_to(&points[0]);
+        points[1..].iter()
+                   .all(|p| self.signed_distance_to(p) * d0 > T::zero())
+    }
+
+    /// Check if this polygon contains another one.
+    pub fn contains(&self, other: &Self) -> bool {
+        //TODO: actually check for inside/outside
+        self.normal == other.normal && self.offset == other.offset
+    }
+
+    /// Project this polygon onto a 3D vector, returning a line projection.
+    /// Note: we can think of it as a projection to a ray placed at the origin.
+    pub fn project_on(&self, vector: &TypedPoint3D<T, U>) -> LineProjection<T> {
+        LineProjection {
+            markers: [
+                vector.dot(self.points[0]),
+                vector.dot(self.points[1]),
+                vector.dot(self.points[2]),
+                vector.dot(self.points[3]),
+            ],
+        }
+    }
+
+    /// Compute the line of intersection with another polygon.
+    pub fn intersect(&self, other: &Self) -> Intersection<Line<T, U>> {
+        if self.are_outside(&other.points) || other.are_outside(&self.points) {
+            // one is completely outside the other
+            return Intersection::Outside
+        }
+        let cross_dir = self.normal.cross(other.normal);
+        if cross_dir.dot(cross_dir) < T::approx_epsilon() {
+            // polygons are co-planar
+            return Intersection::Coplanar
+        }
+        let self_proj = self.project_on(&cross_dir);
+        let other_proj = other.project_on(&cross_dir);
+        if !self_proj.intersect(&other_proj) {
+            // projections on the line don't intersect
+            return Intersection::Outside
+        }
+        // compute any point on the intersection between planes
+        // (n1, v) + d1 = 0
+        // (n2, v) + d2 = 0
+        // v = a*n1/w + b*n2/w; w = (n1, n2)
+        // v = (d2*w - d1) / (1 - w*w) * n1 - (d2 - d1*w) / (1 - w*w) * n2
+        let w = self.normal.dot(other.normal);
+        let factor = T::one() / (T::one() - w * w);
+        let center = self.normal * ((other.offset * w - self.offset) * factor) -
+                     other.normal* ((other.offset - self.offset * w) * factor);
+        Intersection::Inside(Line {
+            origin: center,
+            dir: cross_dir.normalize(),
+        })
+    }
+
+    /// Split the polygon along the specified `Line`. Will do nothing if the line
+    /// doesn't belong to the polygon plane.
+    pub fn split(&mut self, line: &Line<T, U>)
+                 -> (Option<Polygon<T, U>>, Option<Polygon<T, U>>) {
+        // check if the cut is within the polygon plane first
+        if !self.normal.dot(line.dir).approx_eq(&T::zero()) ||
+           !self.signed_distance_to(&line.origin).approx_eq(&T::zero()) {
+            return (None, None)
+        }
+        // compute the intersection points for each edge
+        let mut cuts = [None; 4];
+        for ((&b, &a), cut) in self.points.iter()
+                                          .cycle()
+                                          .skip(1)
+                                          .zip(self.points.iter())
+                                          .zip(cuts.iter_mut()) {
+            // intersecting line segment [a, b] with `line`
+            //a + (b-a) * t = r + k * d
+            //(a, d) + t * (b-a, d) - (r, d) = k
+            // a + t * (b-a) = r + t * (b-a, d) * d + (a-r, d) * d
+            // t * ((b-a) - (b-a, d)*d) = (r-a) - (r-a, d) * d
+            let pr = line.origin - a - line.dir * line.dir.dot(line.origin - a);
+            let pb = b - a - line.dir * line.dir.dot(b - a);
+            let denom = pb.dot(pb);
+            if !denom.approx_eq(&T::zero()) {
+                let t = pr.dot(pb) / denom;
+                if t > T::zero() && t < T::one() {
+                    *cut = Some(a + (b - a) * t);
+                }
+            }
+        }
+
+        let first = match cuts.iter().position(|c| c.is_some()) {
+            Some(pos) => pos,
+            None => return (None, None),
+        };
+        let second = match cuts[first+1 ..].iter().position(|c| c.is_some()) {
+            Some(pos) => first + 1 + pos,
+            None => return (None, None),
+        };
+        //TODO: can be optimized for when the polygon has a redundant 4th vertex
+        let (a, b) = (cuts[first].unwrap(), cuts[second].unwrap());
+        match second-first {
+            2 => {
+                let mut other_points = self.points;
+                other_points[first] = a;
+                other_points[(first+3) % 4] = b;
+                self.points[first+1] = a;
+                self.points[first+2] = b;
+                let poly = Polygon {
+                    points: other_points,
+                    .. self.clone()
+                };
+                (Some(poly), None)
+            }
+            3 => {
+                let xpoints = [
+                    self.points[first+1],
+                    self.points[first+2],
+                    self.points[first+3],
+                    b];
+                let ypoints = [a, self.points[first+1], b, b];
+                self.points = [self.points[first], a, b, b];
+                let poly1 = Polygon {
+                    points: xpoints,
+                    .. self.clone()
+                };
+                let poly2 = Polygon {
+                    points: ypoints,
+                    .. self.clone()
+                };
+                (Some(poly1), Some(poly2))
+            }
+            1 => {
+                let xpoints = [
+                    b,
+                    self.points[(first+2) % 4],
+                    self.points[(first+3) % 4],
+                    self.points[first]
+                    ];
+                let ypoints = [self.points[first], a, b, b];
+                self.points = [a, self.points[first+1], b, b];
+                let poly1 = Polygon {
+                    points: xpoints,
+                    .. self.clone()
+                };
+                let poly2 = Polygon {
+                    points: ypoints,
+                    .. self.clone()
+                };
+                (Some(poly1), Some(poly2))
+            }
+            _ => panic!("Unexpected indices {} {}", first, second),
+        }
+    }
+}
+
+
+/// Generic plane splitter interface.
+pub trait Splitter<T, U> {
+    /// Reset the splitter results.
+    fn reset(&mut self);
+
+    /// Add a new polygon and return a slice of the subdivisions
+    /// that avoid collision with any of the previously added polygons.
+    fn add(&mut self, Polygon<T, U>);
+
+    /// Sort the produced polygon set by the ascending distance across
+    /// the specified view vector. Return the sorted slice.
+    fn sort(&mut self, TypedPoint3D<T, U>) -> &[Polygon<T, U>];
+
+    /// Process a set of polygons at once.
+    fn solve(&mut self, input: &[Polygon<T, U>], view: TypedPoint3D<T, U>)
+             -> &[Polygon<T, U>]
+    where T: Clone, U: Clone {
+        self.reset();
+        for p in input.iter() {
+            self.add(p.clone());
+        }
+        self.sort(view)
+    }
+}
+
+
+/// Helper method used for benchmarks and tests.
+/// Constructs a 3D grid of polygons.
+pub fn _make_grid(count: usize) -> Vec<Polygon<f32, ()>> {
+    let mut polys: Vec<Polygon<f32, ()>> = Vec::with_capacity(count*3);
+    let len = count as f32;
+    polys.extend((0 .. count).map(|i| Polygon {
+        points: [
+            TypedPoint3D::new(0.0, i as f32, 0.0),
+            TypedPoint3D::new(len, i as f32, 0.0),
+            TypedPoint3D::new(len, i as f32, len),
+            TypedPoint3D::new(0.0, i as f32, len),
+        ],
+        normal: TypedPoint3D::new(0.0, 1.0, 0.0),
+        offset: -(i as f32),
+        anchor: 0,
+    }));
+    polys.extend((0 .. count).map(|i| Polygon {
+        points: [
+            TypedPoint3D::new(i as f32, 0.0, 0.0),
+            TypedPoint3D::new(i as f32, len, 0.0),
+            TypedPoint3D::new(i as f32, len, len),
+            TypedPoint3D::new(i as f32, 0.0, len),
+        ],
+        normal: TypedPoint3D::new(1.0, 0.0, 0.0),
+        offset: -(i as f32),
+        anchor: 0,
+    }));
+    polys.extend((0 .. count).map(|i| Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 0.0, i as f32),
+            TypedPoint3D::new(len, 0.0, i as f32),
+            TypedPoint3D::new(len, len, i as f32),
+            TypedPoint3D::new(0.0, len, i as f32),
+        ],
+        normal: TypedPoint3D::new(0.0, 0.0, 1.0),
+        offset: -(i as f32),
+        anchor: 0,
+    }));
+    polys
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/src/naive.rs
@@ -0,0 +1,177 @@
+use std::{fmt, ops};
+use std::cmp::Ordering;
+use {Intersection, Line, Polygon, Splitter};
+use euclid::TypedPoint3D;
+use euclid::approxeq::ApproxEq;
+use num_traits::{Float, One, Zero};
+
+
+/// Naive plane splitter, has at least O(n^2) complexity.
+pub struct NaiveSplitter<T, U> {
+    result: Vec<Polygon<T, U>>,
+    current: Vec<Polygon<T, U>>,
+    temp: Vec<Polygon<T, U>>,
+}
+
+impl<T, U> NaiveSplitter<T, U> {
+    /// Create a new `NaiveSplitter`.
+    pub fn new() -> Self {
+        NaiveSplitter {
+            result: Vec::new(),
+            current: Vec::new(),
+            temp: Vec::new(),
+        }
+    }
+}
+
+/// Find a closest intersection point between two polygons,
+/// across the specified direction.
+fn intersect_across<T, U>(a: &Polygon<T, U>, b: &Polygon<T, U>,
+                          dir: TypedPoint3D<T, U>)
+                          -> TypedPoint3D<T, U>
+where
+    T: Copy + fmt::Debug + PartialOrd + ApproxEq<T> +
+        ops::Sub<T, Output=T> + ops::Add<T, Output=T> +
+        ops::Mul<T, Output=T> + ops::Div<T, Output=T> +
+        Zero + One + Float,
+{
+    let pa = a.project_on(&dir).get_bounds();
+    let pb = b.project_on(&dir).get_bounds();
+    let pmin = pa.0.max(pb.0);
+    let pmax = pa.1.min(pb.1);
+    let k = (pmin + pmax) / (T::one() + T::one());
+    debug!("\t\tIntersection pa {:?} pb {:?} k {:?}", pa, pb, k);
+    dir * k
+}
+
+fn partial_sort_by<T, F>(array: &mut [T], fun: F) where
+    F: Fn(&T, &T) -> Ordering,
+    T: fmt::Debug,
+{
+    debug!("\nSorting");
+    if array.is_empty() {
+        return
+    }
+    for i in 0 .. array.len() - 1 {
+        let mut up_start = array.len();
+        // placement is: [i, ... equals ..., up_start, ... greater ..., j]
+        // if this condition fails, everything to the right is greater
+        'find_smallest: while i + 1 != up_start {
+            let mut j = i + 1;
+            'partition: loop {
+                debug!("\tComparing {} to {}, up_start = {}", i, j, up_start);
+                let order = fun(&array[i], &array[j]);
+                debug!("\t\t{:?}", order);
+                match order {
+                    Ordering::Less => {
+                        // push back to "greater" area
+                        up_start -= 1;
+                        if j == up_start {
+                            break 'partition
+                        }
+                        array.swap(j, up_start);
+                    },
+                    Ordering::Equal => {
+                        // continue
+                        j += 1;
+                        if j == up_start {
+                            // we reached the end of the "equal" area
+                            // so our "i" can be placed anywhere
+                            break 'find_smallest;
+                        }
+                    }
+                    Ordering::Greater => {
+                        array.swap(i, j);
+                        up_start -= 1;
+                        array.swap(j, up_start);
+                        // found a smaller one, push "i" to the "greater" area
+                        // and restart the search from the current element
+                        break 'partition;
+                    },
+                }
+            }
+        }
+        debug!("\tEnding {} with up_start={}, poly {:?}", i, up_start, array[i]);
+    }
+}
+
+
+impl<
+    T: Copy + fmt::Debug + PartialOrd + ApproxEq<T> +
+       ops::Sub<T, Output=T> + ops::Add<T, Output=T> +
+       ops::Mul<T, Output=T> + ops::Div<T, Output=T> +
+       Zero + One + Float,
+    U: fmt::Debug,
+> Splitter<T, U> for NaiveSplitter<T, U> {
+    fn reset(&mut self) {
+        self.result.clear();
+        self.current.clear();
+        self.temp.clear();
+    }
+
+    fn add(&mut self, poly: Polygon<T, U>) {
+        // "current" accumulates all the subdivisions of the originally
+        // added polygon
+        self.current.push(poly);
+        for old in self.result.iter() {
+            for new in self.current.iter_mut() {
+                // temp accumulates all the new subdivisions to be added
+                // to the current, since we can't modify it in place
+                if let Intersection::Inside(line) = old.intersect(new) {
+                    let (res_add1, res_add2) = new.split(&line);
+                    if let Some(res) = res_add1 {
+                        self.temp.push(res);
+                    }
+                    if let Some(res) = res_add2 {
+                        self.temp.push(res);
+                    }
+                }
+            }
+            self.current.extend(self.temp.drain(..));
+        }
+        let index = self.result.len();
+        self.result.extend(self.current.drain(..));
+        debug!("Split result: {:?}", &self.result[index..]);
+    }
+
+    //TODO: verify/prove that the sorting approach is consistent
+    fn sort(&mut self, view: TypedPoint3D<T, U>) -> &[Polygon<T, U>] {
+        // choose the most perpendicular axis among these two
+        let axis_pre = {
+            let axis_pre0 = TypedPoint3D::new(T::one(), T::zero(), T::zero());
+            let axis_pre1 = TypedPoint3D::new(T::zero(), T::one(), T::zero());
+            if view.dot(axis_pre0).abs() < view.dot(axis_pre1).abs() {
+                axis_pre0
+            } else {
+                axis_pre1
+            }
+        };
+        // do the orthogonalization
+        let axis_x = view.cross(axis_pre);
+        let axis_y = view.cross(axis_x);
+        debug!("Chosen axis {:?} {:?}", axis_x, axis_y);
+        // sort everything
+        partial_sort_by(&mut self.result, |a, b| {
+            debug!("\t\t{:?}", a);
+            debug!("\t\t{:?}", b);
+            //TODO: proper intersection
+            // compute the origin
+            let comp_x = intersect_across(a, b, axis_x);
+            let comp_y = intersect_across(a, b, axis_y);
+            // line that tries to intersect both
+            let line = Line {
+                origin: comp_x + comp_y,
+                dir: view,
+            };
+            debug!("\t\tGot {:?}", line);
+            // distances across the line
+            let da = a.distance_to_line(&line);
+            let db = b.distance_to_line(&line);
+            debug!("\t\tDistances {:?} {:?}", da, db);
+            // final compare
+            da.partial_cmp(&db).unwrap_or(Ordering::Equal)
+        });
+        // done
+        &self.result
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/tests/main.rs
@@ -0,0 +1,237 @@
+extern crate euclid;
+extern crate plane_split;
+
+use euclid::{Point2D, Radians, TypedPoint2D, TypedPoint3D, TypedRect, TypedSize2D, TypedMatrix4D};
+use euclid::approxeq::ApproxEq;
+use plane_split::{Intersection, Line, LineProjection, Polygon};
+
+
+#[test]
+fn line_proj_bounds() {
+    assert_eq!((-5i8, 4), LineProjection { markers: [-5i8, 1, 4, 2] }.get_bounds());
+    assert_eq!((1f32, 4.0), LineProjection { markers: [4f32, 3.0, 2.0, 1.0] }.get_bounds());
+}
+
+#[test]
+fn valid() {
+    let poly_a: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 0.0, 0.0),
+            TypedPoint3D::new(1.0, 1.0, 1.0),
+            TypedPoint3D::new(1.0, 1.0, 0.0),
+            TypedPoint3D::new(0.0, 1.0, 1.0),
+        ],
+        normal: TypedPoint3D::new(0.0, 1.0, 0.0),
+        offset: -1.0,
+        anchor: 0,
+    };
+    assert!(!poly_a.is_valid()); // points[0] is outside
+    let poly_b: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 1.0, 0.0),
+            TypedPoint3D::new(1.0, 1.0, 1.0),
+            TypedPoint3D::new(1.0, 1.0, 0.0),
+            TypedPoint3D::new(0.0, 1.0, 1.0),
+        ],
+        normal: TypedPoint3D::new(0.0, 1.0, 0.0),
+        offset: -1.0,
+        anchor: 0,
+    };
+    assert!(!poly_b.is_valid()); // winding is incorrect
+    let poly_c: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 0.0, 1.0),
+            TypedPoint3D::new(1.0, 0.0, 1.0),
+            TypedPoint3D::new(1.0, 1.0, 1.0),
+            TypedPoint3D::new(0.0, 1.0, 1.0),
+        ],
+        normal: TypedPoint3D::new(0.0, 0.0, 1.0),
+        offset: -1.0,
+        anchor: 0,
+    };
+    assert!(poly_c.is_valid());
+}
+
+#[test]
+fn from_transformed_rect() {
+    let rect: TypedRect<f32, ()> = TypedRect::new(TypedPoint2D::new(10.0, 10.0), TypedSize2D::new(20.0, 30.0));
+    let transform: TypedMatrix4D<f32, (), ()> =
+        TypedMatrix4D::create_rotation(0.5f32.sqrt(), 0.0, 0.5f32.sqrt(), Radians::new(5.0))
+        .pre_translated(0.0, 0.0, 10.0);
+    let poly = Polygon::from_transformed_rect(rect, transform, 0);
+    assert!(poly.is_valid_eps(1e-5));
+}
+
+#[test]
+fn untransform_point() {
+    let poly: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 0.0, 0.0),
+            TypedPoint3D::new(0.5, 1.0, 0.0),
+            TypedPoint3D::new(1.5, 1.0, 0.0),
+            TypedPoint3D::new(1.0, 0.0, 0.0),
+        ],
+        normal: TypedPoint3D::new(0.0, 1.0, 0.0),
+        offset: 0.0,
+        anchor: 0,
+    };
+    assert_eq!(poly.untransform_point(poly.points[0]), Point2D::new(0.0, 0.0));
+    assert_eq!(poly.untransform_point(poly.points[1]), Point2D::new(1.0, 0.0));
+    assert_eq!(poly.untransform_point(poly.points[2]), Point2D::new(1.0, 1.0));
+    assert_eq!(poly.untransform_point(poly.points[3]), Point2D::new(0.0, 1.0));
+}
+
+#[test]
+fn are_outside() {
+    let poly: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 0.0, 1.0),
+            TypedPoint3D::new(1.0, 0.0, 1.0),
+            TypedPoint3D::new(1.0, 1.0, 1.0),
+            TypedPoint3D::new(0.0, 1.0, 1.0),
+        ],
+        normal: TypedPoint3D::new(0.0, 0.0, 1.0),
+        offset: -1.0,
+        anchor: 0,
+    };
+    assert!(poly.is_valid());
+    assert!(poly.are_outside(&[
+        TypedPoint3D::new(0.0, 0.0, 1.1),
+        TypedPoint3D::new(1.0, 1.0, 2.0),
+    ]));
+    assert!(poly.are_outside(&[
+        TypedPoint3D::new(0.5, 0.5, 1.0),
+    ]));
+    assert!(!poly.are_outside(&[
+        TypedPoint3D::new(0.0, 0.0, 1.0),
+        TypedPoint3D::new(0.0, 0.0, -1.0),
+    ]));
+}
+
+#[test]
+fn intersect() {
+    let poly_a: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 0.0, 1.0),
+            TypedPoint3D::new(1.0, 0.0, 1.0),
+            TypedPoint3D::new(1.0, 1.0, 1.0),
+            TypedPoint3D::new(0.0, 1.0, 1.0),
+        ],
+        normal: TypedPoint3D::new(0.0, 0.0, 1.0),
+        offset: -1.0,
+        anchor: 0,
+    };
+    assert!(poly_a.is_valid());
+    let poly_b: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.5, 0.0, 2.0),
+            TypedPoint3D::new(0.5, 1.0, 2.0),
+            TypedPoint3D::new(0.5, 1.0, 0.0),
+            TypedPoint3D::new(0.5, 0.0, 0.0),
+        ],
+        normal: TypedPoint3D::new(1.0, 0.0, 0.0),
+        offset: -0.5,
+        anchor: 0,
+    };
+    assert!(poly_b.is_valid());
+
+    let intersection = match poly_a.intersect(&poly_b) {
+        Intersection::Inside(result) => result,
+        _ => panic!("Bad intersection"),
+    };
+    assert!(intersection.is_valid());
+    // confirm the origin is on both planes
+    assert!(poly_a.signed_distance_to(&intersection.origin).approx_eq(&0.0));
+    assert!(poly_b.signed_distance_to(&intersection.origin).approx_eq(&0.0));
+    // confirm the direction is coplanar to both planes
+    assert!(poly_a.normal.dot(intersection.dir).approx_eq(&0.0));
+    assert!(poly_b.normal.dot(intersection.dir).approx_eq(&0.0));
+
+    let poly_c: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, -1.0, 2.0),
+            TypedPoint3D::new(0.0, -1.0, 0.0),
+            TypedPoint3D::new(0.0, 0.0, 0.0),
+            TypedPoint3D::new(0.0, 0.0, 2.0),
+        ],
+        normal: TypedPoint3D::new(1.0, 0.0, 0.0),
+        offset: 0.0,
+        anchor: 0,
+    };
+    assert!(poly_c.is_valid());
+    let poly_d: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 0.0, 0.5),
+            TypedPoint3D::new(1.0, 0.0, 0.5),
+            TypedPoint3D::new(1.0, 1.0, 0.5),
+            TypedPoint3D::new(0.0, 1.0, 0.5),
+        ],
+        normal: TypedPoint3D::new(0.0, 0.0, 1.0),
+        offset: -0.5,
+        anchor: 0,
+    };
+    assert!(poly_d.is_valid());
+
+    assert!(poly_a.intersect(&poly_c).is_outside());
+    assert!(poly_a.intersect(&poly_d).is_outside());
+}
+
+fn test_cut(poly_base: &Polygon<f32, ()>, extra_count: u8, line: Line<f32, ()>) {
+    assert!(line.is_valid());
+    let mut poly = poly_base.clone();
+    let (extra1, extra2) = poly.split(&line);
+    assert!(poly.is_valid() && poly_base.contains(&poly));
+    assert_eq!(extra_count > 0, extra1.is_some());
+    assert_eq!(extra_count > 1, extra2.is_some());
+    if let Some(extra) = extra1 {
+        assert!(extra.is_valid() && poly_base.contains(&extra));
+    }
+    if let Some(extra) = extra2 {
+        assert!(extra.is_valid() && poly_base.contains(&extra));
+    }
+}
+
+#[test]
+fn split() {
+    let poly: Polygon<f32, ()> = Polygon {
+        points: [
+            TypedPoint3D::new(0.0, 1.0, 0.0),
+            TypedPoint3D::new(1.0, 1.0, 0.0),
+            TypedPoint3D::new(1.0, 1.0, 1.0),
+            TypedPoint3D::new(0.0, 1.0, 1.0),
+        ],
+        normal: TypedPoint3D::new(0.0, 1.0, 0.0),
+        offset: -1.0,
+        anchor: 0,
+    };
+
+    // non-intersecting line
+    test_cut(&poly, 0, Line {
+        origin: TypedPoint3D::new(0.0, 1.0, 0.5),
+        dir: TypedPoint3D::new(0.0, 1.0, 0.0),
+    });
+
+    // simple cut (diff=2)
+    test_cut(&poly, 1, Line {
+        origin: TypedPoint3D::new(0.0, 1.0, 0.5),
+        dir: TypedPoint3D::new(1.0, 0.0, 0.0),
+    });
+
+    // complex cut (diff=1, wrapped)
+    test_cut(&poly, 2, Line {
+        origin: TypedPoint3D::new(0.0, 1.0, 0.5),
+        dir: TypedPoint3D::new(0.5f32.sqrt(), 0.0, -0.5f32.sqrt()),
+    });
+
+    // complex cut (diff=1, non-wrapped)
+    test_cut(&poly, 2, Line {
+        origin: TypedPoint3D::new(0.5, 1.0, 0.0),
+        dir: TypedPoint3D::new(0.5f32.sqrt(), 0.0, 0.5f32.sqrt()),
+    });
+
+    // complex cut (diff=3)
+    test_cut(&poly, 2, Line {
+        origin: TypedPoint3D::new(0.5, 1.0, 0.0),
+        dir: TypedPoint3D::new(-0.5f32.sqrt(), 0.0, 0.5f32.sqrt()),
+    });
+}
new file mode 100644
--- /dev/null
+++ b/third_party/rust/plane-split/tests/split.rs
@@ -0,0 +1,80 @@
+extern crate euclid;
+extern crate plane_split;
+
+use std::f32::consts::FRAC_PI_4;
+use euclid::{Radians, TypedMatrix4D, TypedPoint2D, TypedPoint3D, TypedSize2D, TypedRect};
+use plane_split::{BspSplitter, NaiveSplitter, Polygon, Splitter, _make_grid};
+
+
+fn grid_impl(count: usize, splitter: &mut Splitter<f32, ()>) {
+    let polys = _make_grid(count);
+    let result = splitter.solve(&polys, TypedPoint3D::new(0.0, 0.0, 1.0));
+    assert_eq!(result.len(), count + count*count + count*count*count);
+}
+
+#[test]
+fn grid_naive() {
+    grid_impl(2, &mut NaiveSplitter::new());
+}
+
+#[test]
+fn grid_bsp() {
+    grid_impl(2, &mut BspSplitter::new());
+}
+
+
+fn sort_rotation(splitter: &mut Splitter<f32, ()>) {
+    let transform0: TypedMatrix4D<f32, (), ()> =
+        TypedMatrix4D::create_rotation(0.0, 1.0, 0.0, Radians::new(-FRAC_PI_4));
+    let transform1: TypedMatrix4D<f32, (), ()> =
+        TypedMatrix4D::create_rotation(0.0, 1.0, 0.0, Radians::new(0.0));
+    let transform2: TypedMatrix4D<f32, (), ()> =
+        TypedMatrix4D::create_rotation(0.0, 1.0, 0.0, Radians::new(FRAC_PI_4));
+
+    let rect: TypedRect<f32, ()> = TypedRect::new(TypedPoint2D::new(-10.0, -10.0), TypedSize2D::new(20.0, 20.0));
+    let polys = [
+        Polygon::from_transformed_rect(rect, transform0, 0),
+        Polygon::from_transformed_rect(rect, transform1, 1),
+        Polygon::from_transformed_rect(rect, transform2, 2),
+    ];
+
+    let result = splitter.solve(&polys, TypedPoint3D::new(0.0, 0.0, -1.0));
+    let ids: Vec<_> = result.iter().map(|poly| poly.anchor).collect();
+    assert_eq!(&ids, &[2, 1, 0, 1, 2]);
+}
+
+#[test]
+fn rotation_naive() {
+    sort_rotation(&mut NaiveSplitter::new());
+}
+
+#[test]
+fn rotation_bsp() {
+    sort_rotation(&mut BspSplitter::new());
+}
+
+
+fn sort_trivial(splitter: &mut Splitter<f32, ()>) {
+    let anchors: Vec<_> = (0usize .. 10).collect();
+    let rect: TypedRect<f32, ()> = TypedRect::new(TypedPoint2D::new(-10.0, -10.0), TypedSize2D::new(20.0, 20.0));
+    let polys: Vec<_> = anchors.iter().map(|&anchor| {
+        let transform: TypedMatrix4D<f32, (), ()> = TypedMatrix4D::create_translation(0.0, 0.0, anchor as f32);
+        Polygon::from_transformed_rect(rect, transform, anchor)
+    }).collect();
+
+    let result = splitter.solve(&polys, TypedPoint3D::new(0.0, 0.0, -1.0));
+    let anchors1: Vec<_> = result.iter().map(|p| p.anchor).collect();
+    let mut anchors2 = anchors1.clone();
+    anchors2.sort_by_key(|&a| -(a as i32));
+    assert_eq!(anchors1, anchors2); //make sure Z is sorted backwards
+}
+
+#[test]
+fn trivial_naive() {
+    sort_trivial(&mut NaiveSplitter::new());
+}
+
+#[test]
+fn trivial_bsp() {
+    sort_trivial(&mut BspSplitter::new());
+}
--- a/toolkit/library/gtest/rust/Cargo.lock
+++ b/toolkit/library/gtest/rust/Cargo.lock
@@ -40,16 +40,21 @@ dependencies = [
 ]
 
 [[package]]
 name = "atomic_refcell"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "binary-space-partition"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "bincode"
 version = "1.0.0-alpha6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -252,17 +257,17 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "euclid"
-version = "0.11.0"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -571,16 +576,27 @@ dependencies = [
 ]
 
 [[package]]
 name = "pkg-config"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "plane-split"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "binary-space-partition 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "precomputed-hash"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "procedural-masquerade"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -763,17 +779,17 @@ dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bindgen 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "nsstring_vendor 0.1.0",
  "num-integer 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -793,17 +809,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "syn"
 version = "0.11.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1009,51 +1025,52 @@ dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gamma-lut 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "plane-split 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_traits 0.36.0",
 ]
 
 [[package]]
 name = "webrender_bindings"
 version = "0.1.0"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender 0.36.0",
  "webrender_traits 0.36.0",
 ]
 
 [[package]]
 name = "webrender_traits"
 version = "0.36.0"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1075,16 +1092,17 @@ dependencies = [
 ]
 
 [metadata]
 "checksum aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0638fd549427caa90c499814196d1b9e3725eb4d15d7339d6de073a680ed0ca2"
 "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
 "checksum app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a0c3b5be4ed53affe3e1a162b2e7ef9979bcaac80daa9026e9d7988c41e0e83"
 "checksum aster 0.38.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2c9b49e42a449c0b79d8acb91db37621de0978064dca7d3288ddcf030123e5b3"
 "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
+"checksum binary-space-partition 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "df65281d9b2b5c332f5bfbd9bb5e5f2e62f627c259cf9dc9cd10fecb758be33d"
 "checksum bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)" = "fb0cdeac1c5d567fdb487ae5853c024e4acf1ea85ba6a6552fe084e0805fea5d"
 "checksum bindgen 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "facc480c409c373db3c870e377ce223e5e07d979efc2604691dc6f583e8ded0f"
 "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
 "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
 "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
 "checksum bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80b13e2ab064ff3aa0bdbf1eff533f9822dc37899821f5f98c67f263eab51707"
 "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8"
 "checksum cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393a5f0088efbe41f9d1fcd062f24e83c278608420e62109feb2c8abee07de7d"
@@ -1096,17 +1114,17 @@ dependencies = [
 "checksum core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ead017dcf77f503dc991f6b52de6084eeea60a94b0a652baa9bf88654a28e83f"
 "checksum core-text 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e9719616a10f717628e074744f8c55df7b450f7a34d29c196d14f4498aad05d"
 "checksum cssparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b39404c5e04492194e3c69f1a10964b980f2c95c884a940f7903446779f6b027"
 "checksum cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "079adec4af52bb5275eadd004292028c79eb3c5f5b4ee8086a36d4197032f6df"
 "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf"
 "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90"
 "checksum dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74114b6b49d6731835da7a28a3642651451e315f7f9b9d04e907e65a45681796"
 "checksum env_logger 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ed39959122ea027670b704fb70539f4286ddf4a49eefede23bf0b4b2a069ec03"
-"checksum euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34559b159306de36203986eff799f83ef2bfb301a29fad333883f1a74a4cc6b0"
+"checksum euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5517462c626a893f3b027615e88d7102cc6dd3f7f1bcb90c7220fb1da4970b5"
 "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
 "checksum freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fde23272c687e4570aefec06cb71174ec0f5284b725deac4e77ba2665d635faf"
 "checksum gamma-lut 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8728df930776135895cbb25cbdd17791cde7d4285d53cf58fe6ee2e6412455"
 "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518"
 "checksum gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1d8edc81c5ae84605a62f5dac661a2313003b26d59839f81d47d46cf0f16a55"
 "checksum gleam 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9c46ff982a2e6abed1f50b3077d3176836875d9720906b248335f4c93827a345"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"
@@ -1129,16 +1147,17 @@ dependencies = [
 "checksum parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fa12d706797d42551663426a45e2db2e0364bd1dbf6aeada87e89c5f981f43e9"
 "checksum parking_lot_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "56a19dcbb5d1e32b6cccb8a9aa1fc2a38418c8699652e735e2bf391a3dc0aa16"
 "checksum pdqsort 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ceca1642c89148ca05611cc775a0c383abef355fc4907c4e95f49f7b09d6287c"
 "checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
 "checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f"
 "checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
 "checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
 "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
+"checksum plane-split 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a05e1e40e37630095627acfd2c7c6cf259e8b4f4ef4f01b2adf2a35331e45975"
 "checksum precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf1fc3616b3ef726a847f2cd2388c646ef6a1f1ba4835c2629004da48184150"
 "checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
 "checksum quasi 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dcbf815446dc6a0afbc72d88f9a8aa71b608d10b168e09437c80c0fd6fd410c9"
 "checksum quasi_codegen 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b06172e92ab0099427609854ffb1512c377be5fc4beaf572ae5d5a01b8359596"
 "checksum quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7375cf7ad34a92e8fd18dd9c42f58b9a11def59ab48bec955bf359a788335592"
 "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
 "checksum rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50c575b58c2b109e2fbc181820cbe177474f35610ff9e357dc75f6bac854ffbf"
 "checksum redox_syscall 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd35cc9a8bdec562c757e3d43c1526b5c6d2653e23e2315065bc25556550753"
--- a/toolkit/library/rust/Cargo.lock
+++ b/toolkit/library/rust/Cargo.lock
@@ -38,16 +38,21 @@ dependencies = [
 ]
 
 [[package]]
 name = "atomic_refcell"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "binary-space-partition"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
 name = "bincode"
 version = "1.0.0-alpha6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -250,17 +255,17 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "euclid"
-version = "0.11.0"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
@@ -558,16 +563,27 @@ dependencies = [
 ]
 
 [[package]]
 name = "pkg-config"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "plane-split"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "binary-space-partition 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "precomputed-hash"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "procedural-masquerade"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -750,17 +766,17 @@ dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bindgen 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "nsstring_vendor 0.1.0",
  "num-integer 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -780,17 +796,17 @@ dependencies = [
 ]
 
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "syn"
 version = "0.11.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -996,51 +1012,52 @@ dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gamma-lut 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "plane-split 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_traits 0.36.0",
 ]
 
 [[package]]
 name = "webrender_bindings"
 version = "0.1.0"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender 0.36.0",
  "webrender_traits 0.36.0",
 ]
 
 [[package]]
 name = "webrender_traits"
 version = "0.36.0"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1062,16 +1079,17 @@ dependencies = [
 ]
 
 [metadata]
 "checksum aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0638fd549427caa90c499814196d1b9e3725eb4d15d7339d6de073a680ed0ca2"
 "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
 "checksum app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a0c3b5be4ed53affe3e1a162b2e7ef9979bcaac80daa9026e9d7988c41e0e83"
 "checksum aster 0.38.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2c9b49e42a449c0b79d8acb91db37621de0978064dca7d3288ddcf030123e5b3"
 "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
+"checksum binary-space-partition 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "df65281d9b2b5c332f5bfbd9bb5e5f2e62f627c259cf9dc9cd10fecb758be33d"
 "checksum bincode 1.0.0-alpha6 (registry+https://github.com/rust-lang/crates.io-index)" = "fb0cdeac1c5d567fdb487ae5853c024e4acf1ea85ba6a6552fe084e0805fea5d"
 "checksum bindgen 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "facc480c409c373db3c870e377ce223e5e07d979efc2604691dc6f583e8ded0f"
 "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
 "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
 "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
 "checksum bitreader 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80b13e2ab064ff3aa0bdbf1eff533f9822dc37899821f5f98c67f263eab51707"
 "checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8"
 "checksum cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "393a5f0088efbe41f9d1fcd062f24e83c278608420e62109feb2c8abee07de7d"
@@ -1083,17 +1101,17 @@ dependencies = [
 "checksum core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ead017dcf77f503dc991f6b52de6084eeea60a94b0a652baa9bf88654a28e83f"
 "checksum core-text 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e9719616a10f717628e074744f8c55df7b450f7a34d29c196d14f4498aad05d"
 "checksum cssparser 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b39404c5e04492194e3c69f1a10964b980f2c95c884a940f7903446779f6b027"
 "checksum cssparser-macros 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "079adec4af52bb5275eadd004292028c79eb3c5f5b4ee8086a36d4197032f6df"
 "checksum deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1614659040e711785ed8ea24219140654da1729f3ec8a47a9719d041112fe7bf"
 "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90"
 "checksum dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74114b6b49d6731835da7a28a3642651451e315f7f9b9d04e907e65a45681796"
 "checksum env_logger 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ed39959122ea027670b704fb70539f4286ddf4a49eefede23bf0b4b2a069ec03"
-"checksum euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34559b159306de36203986eff799f83ef2bfb301a29fad333883f1a74a4cc6b0"
+"checksum euclid 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5517462c626a893f3b027615e88d7102cc6dd3f7f1bcb90c7220fb1da4970b5"
 "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
 "checksum freetype 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fde23272c687e4570aefec06cb71174ec0f5284b725deac4e77ba2665d635faf"
 "checksum gamma-lut 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8728df930776135895cbb25cbdd17791cde7d4285d53cf58fe6ee2e6412455"
 "checksum gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0912515a8ff24ba900422ecda800b52f4016a56251922d397c576bf92c690518"
 "checksum gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1d8edc81c5ae84605a62f5dac661a2313003b26d59839f81d47d46cf0f16a55"
 "checksum gleam 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9c46ff982a2e6abed1f50b3077d3176836875d9720906b248335f4c93827a345"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
 "checksum heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5a376f7402b85be6e0ba504243ecbc0709c48019ecc6286d0540c2e359050c88"
@@ -1116,16 +1134,17 @@ dependencies = [
 "checksum parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fa12d706797d42551663426a45e2db2e0364bd1dbf6aeada87e89c5f981f43e9"
 "checksum parking_lot_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "56a19dcbb5d1e32b6cccb8a9aa1fc2a38418c8699652e735e2bf391a3dc0aa16"
 "checksum pdqsort 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ceca1642c89148ca05611cc775a0c383abef355fc4907c4e95f49f7b09d6287c"
 "checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
 "checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f"
 "checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
 "checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
 "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
+"checksum plane-split 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a05e1e40e37630095627acfd2c7c6cf259e8b4f4ef4f01b2adf2a35331e45975"
 "checksum precomputed-hash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf1fc3616b3ef726a847f2cd2388c646ef6a1f1ba4835c2629004da48184150"
 "checksum procedural-masquerade 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f566249236c6ca4340f7ca78968271f0ed2b0f234007a61b66f9ecd0af09260"
 "checksum quasi 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dcbf815446dc6a0afbc72d88f9a8aa71b608d10b168e09437c80c0fd6fd410c9"
 "checksum quasi_codegen 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b06172e92ab0099427609854ffb1512c377be5fc4beaf572ae5d5a01b8359596"
 "checksum quote 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7375cf7ad34a92e8fd18dd9c42f58b9a11def59ab48bec955bf359a788335592"
 "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
 "checksum rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50c575b58c2b109e2fbc181820cbe177474f35610ff9e357dc75f6bac854ffbf"
 "checksum redox_syscall 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd35cc9a8bdec562c757e3d43c1526b5c6d2653e23e2315065bc25556550753"