Bug 1440664 - Update webrender to commit 22b831c02479eea31821f49a0fac7dd699083557. r=jrmuizel
authorKartikaya Gupta <kgupta@mozilla.com>
Thu, 01 Mar 2018 16:49:20 -0500
changeset 406231 6f54af02c2e61f623bfa847074b4c640e73c75a3
parent 406230 cfc5d19e856fc43766669069b7e2695dd519e5c3
child 406232 c066c419e3789c50158bd83dd641b148c9fbb886
push id100387
push useraciure@mozilla.com
push dateFri, 02 Mar 2018 10:21:40 +0000
treeherdermozilla-inbound@f1b566469841 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1440664
milestone60.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 1440664 - Update webrender to commit 22b831c02479eea31821f49a0fac7dd699083557. r=jrmuizel MozReview-Commit-ID: 38UtaEA1NuB
gfx/webrender/Cargo.toml
gfx/webrender/res/brush_blend.glsl
gfx/webrender/res/brush_image.glsl
gfx/webrender/res/brush_mask_corner.glsl
gfx/webrender/res/brush_picture.glsl
gfx/webrender/res/cs_blur.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/rect.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/border.rs
gfx/webrender/src/box_shadow.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/device.rs
gfx/webrender/src/display_list_flattener.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/glyph_rasterizer.rs
gfx/webrender/src/gpu_types.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/lib.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/record.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene.rs
gfx/webrender/src/scene_builder.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender/tests/angle_shader_validation.rs
gfx/webrender_api/Cargo.toml
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/channel.rs
gfx/webrender_api/src/channel_ipc.rs
gfx/webrender_api/src/channel_mpsc.rs
gfx/webrender_api/src/display_item.rs
gfx/webrender_api/src/display_list.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/src/binary_frame_reader.rs
gfx/wrench/src/json_frame_writer.rs
gfx/wrench/src/main.rs
gfx/wrench/src/reftest.rs
gfx/wrench/src/ron_frame_writer.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -13,28 +13,28 @@ profiler = ["thread_profiler/thread_prof
 debugger = ["ws", "serde_json", "serde", "image", "base64"]
 capture = ["webrender_api/serialize", "ron", "serde"]
 replay = ["webrender_api/deserialize", "ron", "serde"]
 
 [dependencies]
 app_units = "0.6"
 bincode = "0.9"
 byteorder = "1.0"
-euclid = "0.16"
+euclid = "0.17"
 fxhash = "0.2.1"
 gleam = "0.4.20"
 lazy_static = "1"
 log = "0.4"
 num-traits = "0.1.32"
 time = "0.1"
 rayon = "1"
 webrender_api = {path = "../webrender_api"}
 bitflags = "1.0"
 thread_profiler = "0.1.1"
-plane-split = "0.7"
+plane-split = "0.8"
 png = { optional = true, version = "0.11" }
 smallvec = "0.6"
 ws = { optional = true, version = "0.7.3" }
 serde_json = { optional = true, version = "1.0" }
 serde = { optional = true, version = "1.0", features = ["serde_derive"] }
 image = { optional = true, version = "0.17" }
 base64 = { optional = true, version = "0.3.0" }
 ron = { optional = true, version = "0.1.7" }
--- a/gfx/webrender/res/brush_blend.glsl
+++ b/gfx/webrender/res/brush_blend.glsl
@@ -8,16 +8,17 @@
 #include shared,prim_shared,brush
 
 varying vec3 vUv;
 
 flat varying float vAmount;
 flat varying int vOp;
 flat varying mat4 vColorMat;
 flat varying vec4 vColorOffset;
+flat varying vec4 vUvClipBounds;
 
 #ifdef WR_VERTEX_SHADER
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
@@ -25,16 +26,20 @@ void brush_vs(
 ) {
     PictureTask src_task = fetch_picture_task(user_data.x);
     vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
     vec2 uv = vi.snapped_device_pos +
               src_task.common_data.task_rect.p0 -
               src_task.content_origin;
     vUv = vec3(uv / texture_size, src_task.common_data.texture_layer_index);
 
+    vec2 uv0 = src_task.common_data.task_rect.p0;
+    vec2 uv1 = uv0 + src_task.common_data.task_rect.size;
+    vUvClipBounds = vec4(uv0, uv1) / texture_size.xyxy;
+
     vOp = user_data.y;
 
     float lumR = 0.2126;
     float lumG = 0.7152;
     float lumB = 0.0722;
     float oneMinusLumR = 1.0 - lumR;
     float oneMinusLumG = 1.0 - lumG;
     float oneMinusLumB = 1.0 - lumB;
@@ -137,14 +142,18 @@ vec4 brush_fs() {
             break;
         case 8:
             color = Opacity(Cs, vAmount);
             break;
         default:
             color = vColorMat * Cs + vColorOffset;
     }
 
+    // Fail-safe to ensure that we don't sample outside the rendered
+    // portion of a blend source.
+    color.a *= point_inside_rect(vUv.xy, vUvClipBounds.xy, vUvClipBounds.zw);
+
     // Pre-multiply the alpha into the output value.
     color.rgb *= color.a;
 
     return color;
 }
 #endif
--- a/gfx/webrender/res/brush_image.glsl
+++ b/gfx/webrender/res/brush_image.glsl
@@ -1,24 +1,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/. */
 
-#define VECS_PER_SPECIFIC_BRUSH 0
+#define VECS_PER_SPECIFIC_BRUSH 1
 
 #include shared,prim_shared,brush
 
 #ifdef WR_FEATURE_ALPHA_PASS
 varying vec2 vLocalPos;
 #endif
 
 varying vec3 vUv;
 flat varying vec4 vUvBounds;
+flat varying vec4 vColor;
+flat varying vec2 vSelect;
+#ifdef WR_VERTEX_SHADER
 
-#ifdef WR_VERTEX_SHADER
+#define IMAGE_SOURCE_COLOR              0
+#define IMAGE_SOURCE_ALPHA              1
+#define IMAGE_SOURCE_MASK_FROM_COLOR    2
 
 void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
     PictureTask pic_task
 ) {
@@ -30,39 +35,54 @@ void brush_vs(
     vec2 texture_size = vec2(textureSize(sColor0, 0));
 #endif
 
     ImageResource res = fetch_image_resource(user_data.x);
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
 
     vUv.z = res.layer;
+    vColor = res.color;
 
     vec2 f = (vi.local_pos - local_rect.p0) / local_rect.size;
     vUv.xy = mix(uv0, uv1, f);
     vUv.xy /= texture_size;
 
     // Handle case where the UV coords are inverted (e.g. from an
     // external image).
     vUvBounds = vec4(
         min(uv0, uv1) + vec2(0.5),
         max(uv0, uv1) - vec2(0.5)
     ) / texture_size.xyxy;
 
+    switch (user_data.y) {
+        case IMAGE_SOURCE_COLOR:
+            vSelect = vec2(0.0, 0.0);
+            break;
+        case IMAGE_SOURCE_ALPHA:
+            vSelect = vec2(0.0, 1.0);
+            break;
+        case IMAGE_SOURCE_MASK_FROM_COLOR:
+            vSelect = vec2(1.0, 1.0);
+            break;
+    }
+
 #ifdef WR_FEATURE_ALPHA_PASS
     vLocalPos = vi.local_pos;
 #endif
 }
 #endif
 
 #ifdef WR_FRAGMENT_SHADER
 vec4 brush_fs() {
     vec2 uv = clamp(vUv.xy, vUvBounds.xy, vUvBounds.zw);
 
-    vec4 color = TEX_SAMPLE(sColor0, vec3(uv, vUv.z));
+    vec4 texel = TEX_SAMPLE(sColor0, vec3(uv, vUv.z));
+    vec4 mask = mix(texel.rrrr, texel.aaaa, vSelect.x);
+    vec4 color = mix(texel, vColor * mask, vSelect.y);
 
 #ifdef WR_FEATURE_ALPHA_PASS
     color *= init_transform_fs(vLocalPos);
 #endif
 
     return color;
 }
 #endif
deleted file mode 100644
--- a/gfx/webrender/res/brush_mask_corner.glsl
+++ /dev/null
@@ -1,64 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#define VECS_PER_SPECIFIC_BRUSH 1
-
-#include shared,prim_shared,ellipse,brush
-
-flat varying float vClipMode;
-flat varying vec4 vClipCenter_Radius;
-flat varying vec4 vLocalRect;
-varying vec2 vLocalPos;
-
-#ifdef WR_VERTEX_SHADER
-
-struct BrushMaskCornerPrimitive {
-    vec2 radius;
-    float clip_mode;
-};
-
-BrushMaskCornerPrimitive fetch_primitive(int address) {
-    vec4 data = fetch_from_resource_cache_1(address);
-    return BrushMaskCornerPrimitive(data.xy, data.z);
-}
-
-void brush_vs(
-    VertexInfo vi,
-    int prim_address,
-    RectWithSize local_rect,
-    ivec3 user_data,
-    PictureTask pic_task
-) {
-    // Load the specific primitive.
-    BrushMaskCornerPrimitive prim = fetch_primitive(prim_address);
-
-    // Write clip parameters
-    vClipMode = prim.clip_mode;
-    vClipCenter_Radius = vec4(local_rect.p0 + prim.radius, prim.radius);
-
-    vLocalRect = vec4(local_rect.p0, local_rect.p0 + local_rect.size);
-    vLocalPos = vi.local_pos;
-}
-#endif
-
-#ifdef WR_FRAGMENT_SHADER
-vec4 brush_fs() {
-    float d = 1.0;
-    // NOTE: The AA range must be computed outside the if statement,
-    //       since otherwise the results can be undefined if the
-    //       input function is not continuous. I have observed this
-    //       as flickering behaviour on Intel GPUs.
-    float aa_range = compute_aa_range(vLocalPos);
-    // Check if in valid clip region.
-    if (vLocalPos.x < vClipCenter_Radius.x && vLocalPos.y < vClipCenter_Radius.y) {
-        // Apply ellipse clip on corner.
-        d = distance_to_ellipse(vLocalPos - vClipCenter_Radius.xy,
-                                vClipCenter_Radius.zw,
-                                aa_range);
-        d = distance_aa(aa_range, d);
-    }
-
-    return vec4(mix(d, 1.0 - d, vClipMode));
-}
-#endif
--- a/gfx/webrender/res/brush_picture.glsl
+++ b/gfx/webrender/res/brush_picture.glsl
@@ -10,24 +10,20 @@
 varying vec2 vLocalPos;
 #endif
 
 varying vec3 vUv;
 flat varying int vImageKind;
 flat varying vec4 vUvBounds;
 flat varying vec4 vUvBounds_NoClamp;
 flat varying vec4 vParams;
-
-#if defined WR_FEATURE_ALPHA_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
 flat varying vec4 vColor;
-#endif
 
 #define BRUSH_PICTURE_SIMPLE      0
 #define BRUSH_PICTURE_NINEPATCH   1
-#define BRUSH_PICTURE_MIRROR      2
 
 #ifdef WR_VERTEX_SHADER
 
 struct Picture {
     vec4 color;
 };
 
 Picture fetch_picture(int address) {
@@ -39,43 +35,24 @@ void brush_vs(
     VertexInfo vi,
     int prim_address,
     RectWithSize local_rect,
     ivec3 user_data,
     PictureTask pic_task
 ) {
     vImageKind = user_data.y;
 
-    // TODO(gw): There's quite a bit of code duplication here,
-    //           depending on which variation of brush image
-    //           this is being used for. This is because only
-    //           box-shadow pictures are currently supported
-    //           as texture cacheable items. Once we port the
-    //           drop-shadows and text-shadows to be cacheable,
-    //           most of this code can be merged together.
-#if defined WR_FEATURE_COLOR_TARGET || defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
-    BlurTask blur_task = fetch_blur_task(user_data.x);
-    vUv.z = blur_task.common_data.texture_layer_index;
-    vec2 texture_size = vec2(textureSize(sColor0, 0).xy);
-#if defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
-    vColor = blur_task.color;
-#endif
-    vec2 uv0 = blur_task.common_data.task_rect.p0;
-    vec2 src_size = blur_task.common_data.task_rect.size * blur_task.scale_factor;
-    vec2 uv1 = uv0 + blur_task.common_data.task_rect.size;
-#else
     Picture pic = fetch_picture(prim_address);
     ImageResource res = fetch_image_resource(user_data.x);
     vec2 texture_size = vec2(textureSize(sColor1, 0).xy);
     vColor = pic.color;
     vec2 uv0 = res.uv_rect.p0;
     vec2 uv1 = res.uv_rect.p1;
     vec2 src_size = (uv1 - uv0) * res.user_data.x;
     vUv.z = res.layer;
-#endif
 
     // TODO(gw): In the future we'll probably draw these as segments
     //           with the brush shader. When that occurs, we can
     //           modify the UVs for each segment in the VS, and the
     //           FS can become a simple shader that doesn't need
     //           to adjust the UVs.
 
     switch (vImageKind) {
@@ -87,22 +64,16 @@ void brush_vs(
         }
         case BRUSH_PICTURE_NINEPATCH: {
             vec2 local_src_size = src_size / uDevicePixelRatio;
             vUv.xy = (vi.local_pos - local_rect.p0) / local_src_size;
             vParams.xy = vec2(0.5);
             vParams.zw = (local_rect.size / local_src_size - 0.5);
             break;
         }
-        case BRUSH_PICTURE_MIRROR: {
-            vec2 local_src_size = src_size / uDevicePixelRatio;
-            vUv.xy = (vi.local_pos - local_rect.p0) / local_src_size;
-            vParams.xy = 0.5 * local_rect.size / local_src_size;
-            break;
-        }
         default:
             vUv.xy = vec2(0.0);
             vParams = vec4(0.0);
     }
 
     vUvBounds = vec4(uv0 + vec2(0.5), uv1 - vec2(0.5)) / texture_size.xyxy;
     vUvBounds_NoClamp = vec4(uv0, uv1) / texture_size.xyxy;
 
@@ -123,40 +94,21 @@ vec4 brush_fs() {
         }
         case BRUSH_PICTURE_NINEPATCH: {
             uv = clamp(vUv.xy, vec2(0.0), vParams.xy);
             uv += max(vec2(0.0), vUv.xy - vParams.zw);
             uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
             uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
             break;
         }
-        case BRUSH_PICTURE_MIRROR: {
-            // Mirror and stretch the box shadow corner over the entire
-            // primitives.
-            uv = vParams.xy - abs(vUv.xy - vParams.xy);
-
-            // Ensure that we don't fetch texels outside the box
-            // shadow corner. This can happen, for example, when
-            // drawing the outer parts of an inset box shadow.
-            uv = clamp(uv, vec2(0.0), vec2(1.0));
-            uv = mix(vUvBounds_NoClamp.xy, vUvBounds_NoClamp.zw, uv);
-            uv = clamp(uv, vUvBounds.xy, vUvBounds.zw);
-            break;
-        }
         default:
             uv = vec2(0.0);
     }
 
-#if defined WR_FEATURE_COLOR_TARGET
-    vec4 color = texture(sColor0, vec3(uv, vUv.z));
-#elif defined WR_FEATURE_COLOR_TARGET_ALPHA_MASK
-    vec4 color = vColor * texture(sColor0, vec3(uv, vUv.z)).a;
-#else
     vec4 color = vColor * texture(sColor1, vec3(uv, vUv.z)).r;
-#endif
 
 #ifdef WR_FEATURE_ALPHA_PASS
     color *= init_transform_fs(vLocalPos);
 #endif
 
     return color;
 }
 #endif
--- a/gfx/webrender/res/cs_blur.glsl
+++ b/gfx/webrender/res/cs_blur.glsl
@@ -16,16 +16,36 @@ flat varying int vBlurRadius;
 
 #define DIR_HORIZONTAL  0
 #define DIR_VERTICAL    1
 
 in int aBlurRenderTaskAddress;
 in int aBlurSourceTaskAddress;
 in int aBlurDirection;
 
+struct BlurTask {
+    RenderTaskCommonData common_data;
+    float blur_radius;
+    float scale_factor;
+    vec4 color;
+};
+
+BlurTask fetch_blur_task(int address) {
+    RenderTaskData task_data = fetch_render_task_data(address);
+
+    BlurTask task = BlurTask(
+        task_data.common_data,
+        task_data.data1.x,
+        task_data.data1.y,
+        task_data.data2
+    );
+
+    return task;
+}
+
 void main(void) {
     BlurTask blur_task = fetch_blur_task(aBlurRenderTaskAddress);
     RenderTaskCommonData src_task = fetch_render_task_common_data(aBlurSourceTaskAddress);
 
     RectWithSize src_rect = src_task.task_rect;
     RectWithSize target_rect = blur_task.common_data.task_rect;
 
 #if defined WR_FEATURE_COLOR_TARGET
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -111,16 +111,24 @@ vec4[3] fetch_from_resource_cache_3(int 
     ivec2 uv = get_resource_cache_uv(address);
     return vec4[3](
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(0, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(1, 0)),
         TEXEL_FETCH(sResourceCache, uv, 0, ivec2(2, 0))
     );
 }
 
+vec4[3] fetch_from_resource_cache_3_direct(ivec2 address) {
+    return vec4[3](
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
+        TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0))
+    );
+}
+
 vec4[4] fetch_from_resource_cache_4_direct(ivec2 address) {
     return vec4[4](
         TEXEL_FETCH(sResourceCache, address, 0, ivec2(0, 0)),
         TEXEL_FETCH(sResourceCache, address, 0, ivec2(1, 0)),
         TEXEL_FETCH(sResourceCache, address, 0, ivec2(2, 0)),
         TEXEL_FETCH(sResourceCache, address, 0, ivec2(3, 0))
     );
 }
@@ -256,36 +264,16 @@ PictureTask fetch_picture_task(int addre
         task_data.data1.xy,
         task_data.data1.z,
         task_data.data2
     );
 
     return task;
 }
 
-struct BlurTask {
-    RenderTaskCommonData common_data;
-    float blur_radius;
-    float scale_factor;
-    vec4 color;
-};
-
-BlurTask fetch_blur_task(int address) {
-    RenderTaskData task_data = fetch_render_task_data(address);
-
-    BlurTask task = BlurTask(
-        task_data.common_data,
-        task_data.data1.x,
-        task_data.data1.y,
-        task_data.data2
-    );
-
-    return task;
-}
-
 struct ClipArea {
     RenderTaskCommonData common_data;
     vec2 screen_origin;
 };
 
 ClipArea fetch_clip_area(int index) {
     ClipArea area;
 
@@ -683,29 +671,30 @@ GlyphResource fetch_glyph_resource(int a
     vec4 data[2] = fetch_from_resource_cache_2(address);
     return GlyphResource(data[0], data[1].x, data[1].yz, data[1].w);
 }
 
 struct ImageResource {
     RectWithEndpoint uv_rect;
     float layer;
     vec3 user_data;
+    vec4 color;
 };
 
 ImageResource fetch_image_resource(int address) {
     //Note: number of blocks has to match `renderer::BLOCKS_PER_UV_RECT`
-    vec4 data[2] = fetch_from_resource_cache_2(address);
+    vec4 data[3] = fetch_from_resource_cache_3(address);
     RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
-    return ImageResource(uv_rect, data[1].x, data[1].yzw);
+    return ImageResource(uv_rect, data[1].x, data[1].yzw, data[2]);
 }
 
 ImageResource fetch_image_resource_direct(ivec2 address) {
-    vec4 data[2] = fetch_from_resource_cache_2_direct(address);
+    vec4 data[3] = fetch_from_resource_cache_3_direct(address);
     RectWithEndpoint uv_rect = RectWithEndpoint(data[0].xy, data[0].zw);
-    return ImageResource(uv_rect, data[1].x, data[1].yzw);
+    return ImageResource(uv_rect, data[1].x, data[1].yzw, data[2]);
 }
 
 struct TextRun {
     vec4 color;
     vec4 bg_color;
     vec2 offset;
 };
 
--- a/gfx/webrender/res/rect.glsl
+++ b/gfx/webrender/res/rect.glsl
@@ -41,8 +41,13 @@ RectWithSize intersect_rects(RectWithSiz
 
     return result;
 }
 
 bool rect_inside_rect(RectWithSize little, RectWithSize big) {
     return all(lessThanEqual(vec4(big.p0, little.p0 + little.size),
                              vec4(little.p0, big.p0 + big.size)));
 }
+
+float point_inside_rect(vec2 p, vec2 p0, vec2 p1) {
+    vec2 s = step(p0, p) - step(p1, p);
+    return s.x * s.y;
+}
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -7,30 +7,30 @@ use api::{DeviceUintRect, DeviceUintPoin
 use api::{DeviceIntPoint, LayerPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore, ClipWorkItem};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
-use gpu_types::{BrushFlags, BrushImageKind, BrushInstance, ClipChainRectIndex};
+use gpu_types::{BrushFlags, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture};
 use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{CachedGradient, ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PrimitiveRun};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use std::{usize, f32, i32};
-use tiling::{RenderTargetContext, RenderTargetKind};
+use tiling::{RenderTargetContext};
 use util::{MatrixHelpers, TransformedRectKind};
 
 // Special sentinel value recognized by the shader. It is considered to be
 // a dummy task that doesn't mask out anything.
 const OPAQUE_TASK_ADDRESS: RenderTaskAddress = RenderTaskAddress(0x7fff);
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -41,35 +41,26 @@ pub enum TransformBatchKind {
     BorderCorner,
     BorderEdge,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushImageSourceKind {
-    Alpha,
-    Color,
-    ColorAlphaMask,
-}
-
-impl BrushImageSourceKind {
-    pub fn from_render_target_kind(render_target_kind: RenderTargetKind) -> BrushImageSourceKind {
-        match render_target_kind {
-            RenderTargetKind::Color => BrushImageSourceKind::Color,
-            RenderTargetKind::Alpha => BrushImageSourceKind::Alpha,
-        }
-    }
+    Color = 0,
+    //Alpha = 1,            // Unused for now, but left here as shaders need to match.
+    ColorAlphaMask = 2,
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum BrushBatchKind {
-    Picture(BrushImageSourceKind),
+    Picture,
     Solid,
     Line,
     Image(ImageBufferKind),
     Blend,
     MixBlend {
         task_id: RenderTaskId,
         source_id: RenderTaskId,
         backdrop_id: RenderTaskId,
@@ -305,22 +296,29 @@ impl BatchList {
 
         BatchList {
             alpha_batch_list: AlphaBatchList::new(),
             opaque_batch_list: OpaqueBatchList::new(batch_area_threshold),
             combined_bounding_rect: DeviceIntRect::zero(),
         }
     }
 
+    fn add_bounding_rect(
+        &mut self,
+        task_relative_bounding_rect: &DeviceIntRect,
+    ) {
+        self.combined_bounding_rect = self.combined_bounding_rect.union(task_relative_bounding_rect);
+    }
+
     pub fn get_suitable_batch(
         &mut self,
         key: BatchKey,
         task_relative_bounding_rect: &DeviceIntRect,
     ) -> &mut Vec<PrimitiveInstance> {
-        self.combined_bounding_rect = self.combined_bounding_rect.union(task_relative_bounding_rect);
+        self.add_bounding_rect(task_relative_bounding_rect);
 
         match key.blend_mode {
             BlendMode::None => {
                 self.opaque_batch_list
                     .get_suitable_batch(key, task_relative_bounding_rect)
             }
             BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
@@ -481,17 +479,17 @@ impl AlphaBatchBuilder {
         };
 
         // Even though most of the time a splitter isn't used or needed,
         // they are cheap to construct so we will always pass one down.
         let mut splitter = BspSplitter::new();
 
         // Add each run in this picture to the batch.
         for run in &pic.runs {
-            let scroll_node = &ctx.clip_scroll_tree.nodes[&run.clip_and_scroll.scroll_node_id];
+            let scroll_node = &ctx.clip_scroll_tree.nodes[run.clip_and_scroll.scroll_node_id.0];
             let scroll_id = scroll_node.node_data_index;
             self.add_run_to_batch(
                 run,
                 scroll_id,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
@@ -899,19 +897,17 @@ impl AlphaBatchBuilder {
                     Some(PictureSurface::TextureCache(ref cache_item)) => {
                         match picture.kind {
                             PictureKind::TextShadow { .. } |
                             PictureKind::Image { .. } => {
                                 panic!("BUG: only supported as render tasks for now");
                             }
                             PictureKind::BoxShadow { image_kind, .. } => {
                                 let textures = BatchTextures::color(cache_item.texture_id);
-                                let kind = BrushBatchKind::Picture(
-                                    BrushImageSourceKind::from_render_target_kind(picture.target_kind()),
-                                );
+                                let kind = BrushBatchKind::Picture;
 
                                 self.add_brush_to_batch(
                                     &picture.brush,
                                     prim_metadata,
                                     kind,
                                     specified_blend_mode,
                                     non_segmented_blend_mode,
                                     textures,
@@ -931,60 +927,63 @@ impl AlphaBatchBuilder {
                     }
                     Some(PictureSurface::RenderTask(cache_task_id)) => {
                         let cache_task_address = render_tasks.get_task_address(cache_task_id);
                         let textures = BatchTextures::render_target_cache();
 
                         match picture.kind {
                             PictureKind::TextShadow { .. } => {
                                 let kind = BatchKind::Brush(
-                                    BrushBatchKind::Picture(
-                                        BrushImageSourceKind::from_render_target_kind(picture.target_kind())),
+                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray)
                                 );
                                 let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
                                 let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
 
+                                let uv_rect_address = render_tasks[cache_task_id]
+                                    .get_texture_handle()
+                                    .as_int(gpu_cache);
+
                                 let instance = BrushInstance {
                                     picture_address: task_address,
                                     prim_address: prim_cache_address,
                                     clip_chain_rect_index,
                                     scroll_id,
                                     clip_task_address,
                                     z,
                                     segment_index: 0,
                                     edge_flags: EdgeAaSegmentMask::empty(),
                                     brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                                     user_data: [
-                                        cache_task_address.0 as i32,
-                                        BrushImageKind::Simple as i32,
+                                        uv_rect_address,
+                                        BrushImageSourceKind::Color as i32,
                                         0,
                                     ],
                                 };
                                 batch.push(PrimitiveInstance::from(instance));
                             }
                             PictureKind::BoxShadow { .. } => {
                                 panic!("BUG: should be handled as a texture cache surface");
                             }
                             PictureKind::Image {
                                 composite_mode,
                                 secondary_render_task_id,
                                 is_in_3d_context,
-                                reference_frame_id,
+                                reference_frame_index,
                                 real_local_rect,
                                 ref extra_gpu_data_handle,
                                 ..
                             } => {
                                 // If this picture is participating in a 3D rendering context,
                                 // then don't add it to any batches here. Instead, create a polygon
                                 // for it and add it to the current plane splitter.
                                 if is_in_3d_context {
                                     // Push into parent plane splitter.
 
                                     let real_xf = &ctx.clip_scroll_tree
-                                        .nodes[&reference_frame_id]
+                                        .nodes[reference_frame_index.0]
                                         .world_content_transform
                                         .into();
                                     let polygon = make_polygon(
                                         real_local_rect,
                                         &real_xf,
                                         prim_index.0,
                                     );
 
@@ -1023,33 +1022,37 @@ impl AlphaBatchBuilder {
                                                     item_bounding_rect.size.width,
                                                     item_bounding_rect.size.height,
                                                 );
 
                                                 batch.push(PrimitiveInstance::from(instance));
                                             }
                                             FilterOp::DropShadow(offset, _, _) => {
                                                 let kind = BatchKind::Brush(
-                                                    BrushBatchKind::Picture(BrushImageSourceKind::ColorAlphaMask),
+                                                    BrushBatchKind::Image(ImageBufferKind::Texture2DArray),
                                                 );
                                                 let key = BatchKey::new(kind, non_segmented_blend_mode, textures);
 
+                                                let uv_rect_address = render_tasks[cache_task_id]
+                                                    .get_texture_handle()
+                                                    .as_int(gpu_cache);
+
                                                 let instance = BrushInstance {
                                                     picture_address: task_address,
                                                     prim_address: prim_cache_address,
                                                     clip_chain_rect_index,
                                                     scroll_id,
                                                     clip_task_address,
                                                     z,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
                                                     user_data: [
-                                                        cache_task_address.0 as i32,
-                                                        BrushImageKind::Simple as i32,
+                                                        uv_rect_address,
+                                                        BrushImageSourceKind::ColorAlphaMask as i32,
                                                         0,
                                                     ],
                                                 };
 
                                                 {
                                                     let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                                                     batch.push(PrimitiveInstance::from(instance));
                                                 }
@@ -1241,16 +1244,18 @@ impl AlphaBatchBuilder {
             clip_task_address,
             z,
             segment_index: 0,
             edge_flags: EdgeAaSegmentMask::all(),
             brush_flags: BrushFlags::PERSPECTIVE_INTERPOLATION,
             user_data,
         };
 
+        self.batch_list.add_bounding_rect(task_relative_bounding_rect);
+
         match brush.segment_desc {
             Some(ref segment_desc) => {
                 let alpha_batch_key = BatchKey {
                     blend_mode: alpha_blend_mode,
                     kind: BatchKind::Brush(batch_kind),
                     textures,
                 };
 
@@ -1334,17 +1339,21 @@ impl BrushPrimitive {
                 if cache_item.texture_id == SourceTexture::Invalid {
                     None
                 } else {
                     let textures = BatchTextures::color(cache_item.texture_id);
 
                     Some((
                         BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)),
                         textures,
-                        [cache_item.uv_rect_handle.as_int(gpu_cache), 0, 0],
+                        [
+                            cache_item.uv_rect_handle.as_int(gpu_cache),
+                            BrushImageSourceKind::Color as i32,
+                            0,
+                        ],
                     ))
                 }
             }
             BrushKind::Picture => {
                 panic!("bug: get_batch_key is handled at higher level for pictures");
             }
             BrushKind::Solid { .. } => {
                 Some((
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BorderRadius, BorderSide, BorderStyle, BorderWidths, ColorF, LayerPoint};
 use api::{LayerPrimitiveInfo, LayerRect, LayerSize, NormalBorder, RepeatMode, TexelRect};
 use clip::ClipSource;
 use ellipse::Ellipse;
-use frame_builder::FrameBuilder;
+use display_list_flattener::DisplayListFlattener;
 use gpu_cache::GpuDataRequest;
 use prim_store::{BorderPrimitiveCpu, BrushClipMaskKind, BrushSegment, BrushSegmentDescriptor};
 use prim_store::{EdgeAaSegmentMask, PrimitiveContainer, ScrollNodeAndClipChain};
 use util::{lerp, pack_as_float};
 
 #[repr(u8)]
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum BorderCornerInstance {
@@ -274,17 +274,17 @@ pub fn ensure_no_corner_overlap(
         bottom_left_radius.width *= ratio;
         bottom_left_radius.height *= ratio;
 
         bottom_right_radius.width *= ratio;
         bottom_right_radius.height *= ratio;
     }
 }
 
-impl FrameBuilder {
+impl<'a> DisplayListFlattener<'a> {
     fn add_normal_border_primitive(
         &mut self,
         info: &LayerPrimitiveInfo,
         border: &NormalBorder,
         widths: &BorderWidths,
         clip_and_scroll: ScrollNodeAndClipChain,
         corner_instances: [BorderCornerInstance; 4],
         edges: [BorderEdgeKind; 4],
--- a/gfx/webrender/src/box_shadow.rs
+++ b/gfx/webrender/src/box_shadow.rs
@@ -2,36 +2,31 @@
  * 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 api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ComplexClipRegion, LayerPoint};
 use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, LocalClip};
 use api::PipelineId;
 use app_units::Au;
 use clip::ClipSource;
-use frame_builder::FrameBuilder;
+use display_list_flattener::DisplayListFlattener;
 use gpu_types::BrushImageKind;
-use prim_store::{BrushKind, BrushMaskKind, BrushPrimitive, PrimitiveContainer};
+use prim_store::{BrushKind, BrushPrimitive, PrimitiveContainer};
 use prim_store::ScrollNodeAndClipChain;
 use picture::PicturePrimitive;
+use render_task::MAX_BLUR_STD_DEVIATION;
 use util::RectHelpers;
-use render_task::MAX_BLUR_STD_DEVIATION;
 
 // The blur shader samples BLUR_SAMPLE_SCALE * blur_radius surrounding texels.
 pub const BLUR_SAMPLE_SCALE: f32 = 3.0;
 
 // Maximum blur radius.
 // Taken from https://searchfox.org/mozilla-central/rev/c633ffa4c4611f202ca11270dcddb7b29edddff8/layout/painting/nsCSSRendering.cpp#4412
 pub const MAX_BLUR_RADIUS : f32 = 300.;
 
-// The amount of padding added to the border corner drawn in the box shadow
-// mask. This ensures that we get a few pixels past the corner that can be
-// blurred without being affected by the border radius.
-pub const MASK_CORNER_PADDING: f32 = 4.0;
-
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BoxShadowCacheKey {
     pub width: Au,
     pub height: Au,
     pub blur_radius: Au,
     pub spread_radius: Au,
@@ -43,17 +38,17 @@ pub struct BoxShadowCacheKey {
     pub br_top_right_h: Au,
     pub br_bottom_left_w: Au,
     pub br_bottom_left_h: Au,
     pub br_bottom_right_w: Au,
     pub br_bottom_right_h: Au,
     pub clip_mode: BoxShadowClipMode,
 }
 
-impl FrameBuilder {
+impl<'a> DisplayListFlattener<'a> {
     pub fn add_box_shadow(
         &mut self,
         pipeline_id: PipelineId,
         clip_and_scroll: ScrollNodeAndClipChain,
         prim_info: &LayerPrimitiveInfo,
         box_offset: &LayerVector2D,
         color: &ColorF,
         mut blur_radius: f32,
@@ -166,78 +161,60 @@ impl FrameBuilder {
                 clip_mode,
             };
 
             match clip_mode {
                 BoxShadowClipMode::Outset => {
                     let mut width;
                     let mut height;
                     let brush_prim;
-                    let corner_size = shadow_radius.is_uniform_size();
-                    let mut image_kind;
+                    let mut image_kind = BrushImageKind::NinePatch;
 
                     if !shadow_rect.is_well_formed_and_nonempty() {
                         return;
                     }
 
-                    // If the outset box shadow has a uniform corner side, we can
-                    // just blur the top left corner, and stretch / mirror that
-                    // across the primitive.
-                    if let Some(corner_size) = corner_size {
-                        image_kind = BrushImageKind::Mirror;
-                        width = MASK_CORNER_PADDING + corner_size.width.max(BLUR_SAMPLE_SCALE * blur_radius);
-                        height = MASK_CORNER_PADDING + corner_size.height.max(BLUR_SAMPLE_SCALE * blur_radius);
+                    // Create a minimal size primitive mask to blur. In this
+                    // case, we ensure the size of each corner is the same,
+                    // to simplify the shader logic that stretches the blurred
+                    // result across the primitive.
+                    let max_width = shadow_radius.top_left.width
+                                        .max(shadow_radius.bottom_left.width)
+                                        .max(shadow_radius.top_right.width)
+                                        .max(shadow_radius.bottom_right.width);
+                    let max_height = shadow_radius.top_left.height
+                                        .max(shadow_radius.bottom_left.height)
+                                        .max(shadow_radius.top_right.height)
+                                        .max(shadow_radius.bottom_right.height);
+
+                    width = 2.0 * max_width + BLUR_SAMPLE_SCALE * blur_radius;
+                    height = 2.0 * max_height + BLUR_SAMPLE_SCALE * blur_radius;
 
-                        brush_prim = BrushPrimitive::new(
-                            BrushKind::Mask {
-                                clip_mode: brush_clip_mode,
-                                kind: BrushMaskKind::Corner(corner_size),
-                            },
-                            None,
-                        );
-                    } else {
-                        // Create a minimal size primitive mask to blur. In this
-                        // case, we ensure the size of each corner is the same,
-                        // to simplify the shader logic that stretches the blurred
-                        // result across the primitive.
-                        image_kind = BrushImageKind::NinePatch;
-                        let max_width = shadow_radius.top_left.width
-                                            .max(shadow_radius.bottom_left.width)
-                                            .max(shadow_radius.top_right.width)
-                                            .max(shadow_radius.bottom_right.width);
-                        let max_height = shadow_radius.top_left.height
-                                            .max(shadow_radius.bottom_left.height)
-                                            .max(shadow_radius.top_right.height)
-                                            .max(shadow_radius.bottom_right.height);
+                    // If the width or height ends up being bigger than the original
+                    // primitive shadow rect, just blur the entire rect and draw that
+                    // as a simple blit.
+                    if width > prim_info.rect.size.width || height > prim_info.rect.size.height {
+                        image_kind = BrushImageKind::Simple;
+                        width = prim_info.rect.size.width + spread_amount * 2.0;
+                        height = prim_info.rect.size.height + spread_amount * 2.0;
+                    }
 
-                        width = 2.0 * max_width + BLUR_SAMPLE_SCALE * blur_radius;
-                        height = 2.0 * max_height + BLUR_SAMPLE_SCALE * blur_radius;
-
-                        // If the width or height ends up being bigger than the original
-                        // primitive shadow rect, just blur the entire rect and draw that
-                        // as a simple blit.
-                        if width > prim_info.rect.size.width || height > prim_info.rect.size.height {
-                            image_kind = BrushImageKind::Simple;
-                            width = prim_info.rect.size.width + spread_amount * 2.0;
-                            height = prim_info.rect.size.height + spread_amount * 2.0;
-                        }
+                    let clip_rect = LayerRect::new(
+                        LayerPoint::zero(),
+                        LayerSize::new(width, height)
+                    );
 
-                        let clip_rect = LayerRect::new(
-                            LayerPoint::zero(),
-                            LayerSize::new(width, height)
-                        );
-
-                        brush_prim = BrushPrimitive::new(
-                            BrushKind::Mask {
-                                clip_mode: brush_clip_mode,
-                                kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
-                            },
-                            None,
-                        );
-                    };
+                    brush_prim = BrushPrimitive::new(
+                        BrushKind::Mask {
+                            clip_mode: brush_clip_mode,
+                            rect: clip_rect,
+                            radii: shadow_radius,
+                        },
+                        None,
+                    );
 
                     // Construct a mask primitive to add to the picture.
                     let brush_rect = LayerRect::new(LayerPoint::zero(),
                                                     LayerSize::new(width, height));
                     let brush_info = LayerPrimitiveInfo::new(brush_rect);
                     let brush_prim_index = self.create_primitive(
                         &brush_info,
                         Vec::new(),
@@ -302,17 +279,18 @@ impl FrameBuilder {
                         adjusted_blur_std_deviation *= 0.5;
                         inflate_size *= 2.0;
                     }
 
                     let brush_rect = brush_rect.inflate(inflate_size + box_offset.x.abs(), inflate_size + box_offset.y.abs());
                     let brush_prim = BrushPrimitive::new(
                         BrushKind::Mask {
                             clip_mode: brush_clip_mode,
-                            kind: BrushMaskKind::RoundedRect(clip_rect, shadow_radius),
+                            rect: clip_rect,
+                            radii: shadow_radius,
                         },
                         None,
                     );
                     let brush_info = LayerPrimitiveInfo::new(brush_rect);
                     let brush_prim_index = self.create_primitive(
                         &brush_info,
                         Vec::new(),
                         PrimitiveContainer::Brush(brush_prim),
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -9,17 +9,17 @@ use clip_scroll_tree::{ClipChainIndex, C
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::ClipScrollNodeIndex;
 use prim_store::{ClipData, ImageMaskData};
 use resource_cache::{ImageRequest, ResourceCache};
 use util::{LayerToWorldFastTransform, MaxRect, calculate_screen_bounding_rect};
 use util::extract_inner_rect_safe;
-use std::rc::Rc;
+use std::sync::Arc;
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
 #[derive(Clone, Debug)]
 pub struct ClipRegion {
     pub main: LayerRect,
@@ -348,17 +348,17 @@ pub fn rounded_rectangle_contains_point(
     if bottom_left_center.x > point.x && bottom_left_center.y < point.y &&
        !Ellipse::new(radii.bottom_left).contains(*point - bottom_left_center.to_vector()) {
         return false;
     }
 
     true
 }
 
-pub type ClipChainNodeRef = Option<Rc<ClipChainNode>>;
+pub type ClipChainNodeRef = Option<Arc<ClipChainNode>>;
 
 #[derive(Debug, Clone)]
 pub struct ClipChainNode {
     pub work_item: ClipWorkItem,
     pub local_clip_rect: LayerRect,
     pub screen_outer_rect: DeviceIntRect,
     pub screen_inner_rect: DeviceIntRect,
     pub prev: ClipChainNodeRef,
@@ -377,39 +377,25 @@ impl ClipChain {
         ClipChain {
             parent_index: None,
             combined_inner_screen_rect: *screen_rect,
             combined_outer_screen_rect: *screen_rect,
             nodes: None,
         }
     }
 
-    pub fn new_with_added_node(
-        &self,
-        work_item: ClipWorkItem,
-        local_clip_rect: LayerRect,
-        screen_outer_rect: DeviceIntRect,
-        screen_inner_rect: DeviceIntRect,
-    ) -> ClipChain {
+    pub fn new_with_added_node(&self, new_node: &ClipChainNode) -> ClipChain {
         // If the new node's inner rectangle completely surrounds our outer rectangle,
         // we can discard the new node entirely since it isn't going to affect anything.
-        if screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
+        if new_node.screen_inner_rect.contains_rect(&self.combined_outer_screen_rect) {
             return self.clone();
         }
 
-        let new_node = ClipChainNode {
-            work_item,
-            local_clip_rect,
-            screen_outer_rect,
-            screen_inner_rect,
-            prev: None,
-        };
-
         let mut new_chain = self.clone();
-        new_chain.add_node(new_node);
+        new_chain.add_node(new_node.clone());
         new_chain
     }
 
     pub fn add_node(&mut self, mut new_node: ClipChainNode) {
         new_node.prev = self.nodes.clone();
 
         // If this clip's outer rectangle is completely enclosed by the clip
         // chain's inner rectangle, then the only clip that matters from this point
@@ -420,26 +406,26 @@ impl ClipChain {
 
         self.combined_outer_screen_rect =
             self.combined_outer_screen_rect.intersection(&new_node.screen_outer_rect)
             .unwrap_or_else(DeviceIntRect::zero);
         self.combined_inner_screen_rect =
             self.combined_inner_screen_rect.intersection(&new_node.screen_inner_rect)
             .unwrap_or_else(DeviceIntRect::zero);
 
-        self.nodes = Some(Rc::new(new_node));
+        self.nodes = Some(Arc::new(new_node));
     }
 }
 
 pub struct ClipChainNodeIter {
     pub current: ClipChainNodeRef,
 }
 
 impl Iterator for ClipChainNodeIter {
-    type Item = Rc<ClipChainNode>;
+    type Item = Arc<ClipChainNode>;
 
     fn next(&mut self) -> ClipChainNodeRef {
         let previous = self.current.clone();
         self.current = match self.current {
             Some(ref item) => item.prev.clone(),
             None => return None,
         };
         previous
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,22 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ClipId, DevicePixelScale, ExternalScrollId, LayerPixel, LayerPoint, LayerRect};
-use api::{LayerSize, LayerVector2D, LayoutTransform, LayoutVector2D, PipelineId, PropertyBinding};
+use api::{DevicePixelScale, ExternalScrollId, LayerPixel, LayerPoint, LayerRect, LayerSize};
+use api::{LayerVector2D, LayoutTransform, LayoutVector2D, PipelineId, PropertyBinding};
 use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, StickyOffsetBounds};
 use api::WorldPoint;
-use clip::{ClipChain, ClipSourcesHandle, ClipStore, ClipWorkItem};
-use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId, TransformUpdateState};
+use clip::{ClipChain, ClipChainNode, ClipSourcesHandle, ClipStore, ClipWorkItem};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
+use clip_scroll_tree::TransformUpdateState;
 use euclid::SideOffsets2D;
 use geometry::ray_intersects_rect;
 use gpu_cache::GpuCache;
-use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
+use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use spring::{DAMPING, STIFFNESS, Spring};
 use util::{LayerToWorldFastTransform, LayerFastTransform, LayoutFastTransform};
 use util::{TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
@@ -53,28 +54,38 @@ impl StickyFrameInfo {
 #[derive(Debug)]
 pub enum NodeType {
     /// A reference frame establishes a new coordinate space in the tree.
     ReferenceFrame(ReferenceFrameInfo),
 
     /// Other nodes just do clipping, but no transformation.
     Clip {
         handle: ClipSourcesHandle,
-        clip_chain_index: ClipChainIndex
+        clip_chain_index: ClipChainIndex,
+
+        /// A copy of the ClipChainNode this node would produce. We need to keep a copy,
+        /// because the ClipChain may not contain our node if is optimized out, but API
+        /// defined ClipChains will still need to access it.
+        clip_chain_node: Option<ClipChainNode>,
     },
 
     /// Transforms it's content, but doesn't clip it. Can also be adjusted
     /// by scroll events or setting scroll offsets.
     ScrollFrame(ScrollFrameInfo),
 
     /// A special kind of node that adjusts its position based on the position
     /// of its parent node and a given set of sticky positioning offset bounds.
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
     /// https://www.w3.org/TR/css-position-3/#sticky-pos
     StickyFrame(StickyFrameInfo),
+
+    /// An empty node, used to pad the ClipScrollTree's array of nodes so that
+    /// we can immediately use each assigned ClipScrollNodeIndex. After display
+    /// list flattening this node type should never be used.
+    Empty,
 }
 
 impl NodeType {
     fn is_reference_frame(&self) -> bool {
         match *self {
             NodeType::ReferenceFrame(_) => true,
             _ => false,
         }
@@ -96,20 +107,20 @@ pub struct ClipScrollNode {
 
     /// World transform for content transformed by this node.
     pub world_content_transform: LayerToWorldFastTransform,
 
     /// Pipeline that this layer belongs to
     pub pipeline_id: PipelineId,
 
     /// Parent layer. If this is None, we are the root node.
-    pub parent: Option<ClipId>,
+    pub parent: Option<ClipScrollNodeIndex>,
 
     /// Child layers
-    pub children: Vec<ClipId>,
+    pub children: Vec<ClipScrollNodeIndex>,
 
     /// The type of this node and any data associated with that node type.
     pub node_type: NodeType,
 
     /// True if this node is transformed by an invertible transform.  If not, display items
     /// transformed by this node will not be displayed and display items not transformed by this
     /// node will not be clipped by clips that are transformed by this node.
     pub invertible: bool,
@@ -119,94 +130,98 @@ pub struct ClipScrollNode {
 
     /// The transformation from the coordinate system which established our compatible coordinate
     /// system (same coordinate system id) and us. This can change via scroll offsets and via new
     /// reference frame transforms.
     pub coordinate_system_relative_transform: LayerFastTransform,
 
     /// A linear ID / index of this clip-scroll node. Used as a reference to
     /// pass to shaders, to allow them to fetch a given clip-scroll node.
-    pub node_data_index: ClipScrollNodeIndex,
+    pub node_data_index: GPUClipScrollNodeIndex,
 }
 
 impl ClipScrollNode {
     pub fn new(
         pipeline_id: PipelineId,
-        parent_id: Option<ClipId>,
+        parent_index: Option<ClipScrollNodeIndex>,
         rect: &LayerRect,
         node_type: NodeType
     ) -> Self {
         ClipScrollNode {
             local_viewport_rect: *rect,
             world_viewport_transform: LayerToWorldFastTransform::identity(),
             world_content_transform: LayerToWorldFastTransform::identity(),
-            parent: parent_id,
+            parent: parent_index,
             children: Vec::new(),
             pipeline_id,
             node_type: node_type,
             invertible: true,
             coordinate_system_id: CoordinateSystemId(0),
             coordinate_system_relative_transform: LayerFastTransform::identity(),
-            node_data_index: ClipScrollNodeIndex(0),
+            node_data_index: GPUClipScrollNodeIndex(0),
         }
     }
 
+    pub fn empty() -> ClipScrollNode {
+        ClipScrollNode::new(PipelineId::dummy(), None, &LayerRect::zero(), NodeType::Empty)
+    }
+
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
-        parent_id: ClipId,
+        parent_index: ClipScrollNodeIndex,
         external_id: Option<ExternalScrollId>,
         frame_rect: &LayerRect,
         content_size: &LayerSize,
         scroll_sensitivity: ScrollSensitivity,
     ) -> Self {
         let node_type = NodeType::ScrollFrame(ScrollFrameInfo::new(
             scroll_sensitivity,
             LayerSize::new(
                 (content_size.width - frame_rect.size.width).max(0.0),
                 (content_size.height - frame_rect.size.height).max(0.0)
             ),
             external_id,
         ));
 
-        Self::new(pipeline_id, Some(parent_id), frame_rect, node_type)
+        Self::new(pipeline_id, Some(parent_index), frame_rect, node_type)
     }
 
     pub fn new_reference_frame(
-        parent_id: Option<ClipId>,
+        parent_index: Option<ClipScrollNodeIndex>,
         frame_rect: &LayerRect,
         source_transform: Option<PropertyBinding<LayoutTransform>>,
         source_perspective: Option<LayoutTransform>,
         origin_in_parent_reference_frame: LayerVector2D,
         pipeline_id: PipelineId,
     ) -> Self {
         let identity = LayoutTransform::identity();
         let source_perspective = source_perspective.map_or_else(
             LayoutFastTransform::identity, |perspective| perspective.into());
         let info = ReferenceFrameInfo {
             resolved_transform: LayerFastTransform::identity(),
             source_transform: source_transform.unwrap_or(PropertyBinding::Value(identity)),
             source_perspective: source_perspective,
             origin_in_parent_reference_frame,
             invertible: true,
         };
-        Self::new(pipeline_id, parent_id, frame_rect, NodeType::ReferenceFrame(info))
+        Self::new(pipeline_id, parent_index, frame_rect, NodeType::ReferenceFrame(info))
     }
 
     pub fn new_sticky_frame(
-        parent_id: ClipId,
+        parent_index: ClipScrollNodeIndex,
         frame_rect: LayerRect,
         sticky_frame_info: StickyFrameInfo,
         pipeline_id: PipelineId,
     ) -> Self {
         let node_type = NodeType::StickyFrame(sticky_frame_info);
-        Self::new(pipeline_id, Some(parent_id), &frame_rect, node_type)
+        Self::new(pipeline_id, Some(parent_index), &frame_rect, node_type)
     }
 
 
-    pub fn add_child(&mut self, child: ClipId) {
+    pub fn add_child(&mut self, child: ClipScrollNodeIndex) {
         self.children.push(child);
     }
 
     pub fn apply_old_scrolling_state(&mut self, old_scrolling_state: &ScrollFrameInfo) {
         match self.node_type {
             NodeType::ScrollFrame(ref mut scrolling) => {
                 let scroll_sensitivity = scrolling.scroll_sensitivity;
                 let scrollable_size = scrolling.scrollable_size;
@@ -331,18 +346,19 @@ impl ClipScrollNode {
         &mut self,
         state: &mut TransformUpdateState,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         clip_chains: &mut Vec<ClipChain>,
     ) {
-        let (clip_sources_handle, clip_chain_index) = match self.node_type {
-            NodeType::Clip { ref handle, clip_chain_index } => (handle, clip_chain_index),
+        let (clip_sources_handle, clip_chain_index, stored_clip_chain_node) = match self.node_type {
+            NodeType::Clip { ref handle, clip_chain_index, ref mut clip_chain_node } =>
+                (handle, clip_chain_index, clip_chain_node),
             _ => {
                 self.invertible = true;
                 return;
             }
         };
 
         let clip_sources = clip_store.get_mut(clip_sources_handle);
         clip_sources.update(gpu_cache, resource_cache);
@@ -352,29 +368,33 @@ impl ClipScrollNode {
         // All clipping ClipScrollNodes should have outer rectangles, because they never
         // use the BorderCorner clip type and they always have at last one non-ClipOut
         // Rectangle ClipSource.
         let screen_outer_rect = screen_outer_rect.expect("Clipping node didn't have outer rect.");
         let local_outer_rect = clip_sources.local_outer_rect.expect(
             "Clipping node didn't have outer rect."
         );
 
-        let work_item = ClipWorkItem {
-            scroll_node_data_index: self.node_data_index,
-            clip_sources: clip_sources_handle.weak(),
-            coordinate_system_id: state.current_coordinate_system_id,
+        let new_node = ClipChainNode {
+            work_item: ClipWorkItem {
+                scroll_node_data_index: self.node_data_index,
+                clip_sources: clip_sources_handle.weak(),
+                coordinate_system_id: state.current_coordinate_system_id,
+            },
+            local_clip_rect:
+                self.coordinate_system_relative_transform.transform_rect(&local_outer_rect),
+            screen_outer_rect,
+            screen_inner_rect,
+            prev: None,
         };
 
-        let mut clip_chain = clip_chains[state.parent_clip_chain_index.0].new_with_added_node(
-            work_item,
-            self.coordinate_system_relative_transform.transform_rect(&local_outer_rect),
-            screen_outer_rect,
-            screen_inner_rect,
-        );
+        let mut clip_chain =
+            clip_chains[state.parent_clip_chain_index.0].new_with_added_node(&new_node);
 
+        *stored_clip_chain_node = Some(new_node);
         clip_chain.parent_index = Some(state.parent_clip_chain_index);
         clip_chains[clip_chain_index.0] = clip_chain;
         state.parent_clip_chain_index = clip_chain_index;
     }
 
     pub fn update_transform(
         &mut self,
         state: &mut TransformUpdateState,
@@ -616,16 +636,17 @@ impl ClipScrollNode {
             }
             NodeType::StickyFrame(ref info) => {
                 // We don't translate the combined rect by the sticky offset, because sticky
                 // offsets actually adjust the node position itself, whereas scroll offsets
                 // only apply to contents inside the node.
                 state.parent_accumulated_scroll_offset =
                     info.current_offset + state.parent_accumulated_scroll_offset;
             }
+            NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
         }
     }
 
     pub fn scrollable_size(&self) -> LayerSize {
         match self.node_type {
            NodeType:: ScrollFrame(state) => state.scrollable_size,
             _ => LayerSize::zero(),
         }
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,19 +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 api::{ClipId, DeviceIntRect, DevicePixelScale, ExternalScrollId, LayerPoint, LayerRect};
-use api::{LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLocation};
-use api::{ScrollNodeState, WorldPoint};
+use api::{DeviceIntRect, DevicePixelScale, ExternalScrollId, LayerPoint, LayerRect, LayerVector2D};
+use api::{PipelineId, ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollNodeState};
+use api::WorldPoint;
 use clip::{ClipChain, ClipSourcesHandle, ClipStore};
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollFrameInfo, StickyFrameInfo};
 use gpu_cache::GpuCache;
-use gpu_types::{ClipScrollNodeIndex, ClipScrollNodeData};
+use gpu_types::{ClipScrollNodeIndex as GPUClipScrollNodeIndex, ClipScrollNodeData};
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use resource_cache::ResourceCache;
 use scene::SceneProperties;
 use util::{LayerFastTransform, LayerToWorldFastTransform};
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
@@ -21,16 +21,22 @@ pub type ScrollStates = FastHashMap<Exte
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CoordinateSystemId(pub u32);
 
+#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
+pub struct ClipScrollNodeIndex(pub usize);
+
+const ROOT_REFERENCE_FRAME_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(0);
+const TOPMOST_SCROLL_NODE_INDEX: ClipScrollNodeIndex = ClipScrollNodeIndex(1);
+
 impl CoordinateSystemId {
     pub fn root() -> Self {
         CoordinateSystemId(0)
     }
 
     pub fn next(&self) -> Self {
         let CoordinateSystemId(id) = *self;
         CoordinateSystemId(id + 1)
@@ -39,53 +45,45 @@ impl CoordinateSystemId {
     pub fn advance(&mut self) {
         self.0 += 1;
     }
 }
 
 pub struct ClipChainDescriptor {
     pub index: ClipChainIndex,
     pub parent: Option<ClipChainIndex>,
-    pub clips: Vec<ClipId>,
+    pub clips: Vec<ClipScrollNodeIndex>,
 }
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ClipChainIndex(pub usize);
 
 pub struct ClipScrollTree {
-    pub nodes: FastHashMap<ClipId, ClipScrollNode>,
+    pub nodes: Vec<ClipScrollNode>,
 
     /// A Vec of all descriptors that describe ClipChains in the order in which they are
     /// encountered during display list flattening. ClipChains are expected to never be
     /// the children of ClipChains later in the list.
     pub clip_chains_descriptors: Vec<ClipChainDescriptor>,
 
     /// A vector of all ClipChains in this ClipScrollTree including those from
     /// ClipChainDescriptors and also those defined by the clipping node hierarchy.
     pub clip_chains: Vec<ClipChain>,
 
     pub pending_scroll_offsets: FastHashMap<ExternalScrollId, (LayerPoint, ScrollClamping)>,
 
     /// The ClipId of the currently scrolling node. Used to allow the same
     /// node to scroll even if a touch operation leaves the boundaries of that node.
-    pub currently_scrolling_node_id: Option<ClipId>,
+    pub currently_scrolling_node_index: Option<ClipScrollNodeIndex>,
 
     /// The current frame id, used for giving a unique id to all new dynamically
     /// added frames and clips. The ClipScrollTree increments this by one every
     /// time a new dynamic frame is created.
     current_new_node_item: u64,
 
-    /// The root reference frame, which is the true root of the ClipScrollTree. Initially
-    /// this ID is not valid, which is indicated by ```node``` being empty.
-    pub root_reference_frame_id: ClipId,
-
-    /// The root scroll node which is the first child of the root reference frame.
-    /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
-    pub topmost_scrolling_node_id: ClipId,
-
     /// A set of pipelines which should be discarded the next time this
     /// tree is drained.
     pub pipelines_to_discard: FastHashSet<PipelineId>,
 }
 
 #[derive(Clone)]
 pub struct TransformUpdateState {
     pub parent_reference_frame_transform: LayerToWorldFastTransform,
@@ -108,106 +106,103 @@ pub struct TransformUpdateState {
     /// True if this node is transformed by an invertible transform.  If not, display items
     /// transformed by this node will not be displayed and display items not transformed by this
     /// node will not be clipped by clips that are transformed by this node.
     pub invertible: bool,
 }
 
 impl ClipScrollTree {
     pub fn new() -> Self {
-        let dummy_pipeline = PipelineId::dummy();
         ClipScrollTree {
-            nodes: FastHashMap::default(),
+            nodes: Vec::new(),
             clip_chains_descriptors: Vec::new(),
             clip_chains: vec![ClipChain::empty(&DeviceIntRect::zero())],
             pending_scroll_offsets: FastHashMap::default(),
-            currently_scrolling_node_id: None,
-            root_reference_frame_id: ClipId::root_reference_frame(dummy_pipeline),
-            topmost_scrolling_node_id: ClipId::root_scroll_node(dummy_pipeline),
+            currently_scrolling_node_index: None,
             current_new_node_item: 1,
             pipelines_to_discard: FastHashSet::default(),
         }
     }
 
-    pub fn root_reference_frame_id(&self) -> ClipId {
-        // TODO(mrobinson): We should eventually make this impossible to misuse.
-        debug_assert!(!self.nodes.is_empty());
-        debug_assert!(self.nodes.contains_key(&self.root_reference_frame_id));
-        self.root_reference_frame_id
-    }
-
-    pub fn topmost_scrolling_node_id(&self) -> ClipId {
+    /// The root reference frame, which is the true root of the ClipScrollTree. Initially
+    /// this ID is not valid, which is indicated by ```nodes``` being empty.
+    pub fn root_reference_frame_index(&self) -> ClipScrollNodeIndex {
         // TODO(mrobinson): We should eventually make this impossible to misuse.
         debug_assert!(!self.nodes.is_empty());
-        debug_assert!(self.nodes.contains_key(&self.topmost_scrolling_node_id));
-        self.topmost_scrolling_node_id
+        ROOT_REFERENCE_FRAME_INDEX
     }
 
-    pub fn collect_nodes_bouncing_back(&self) -> FastHashSet<ClipId> {
+    /// The root scroll node which is the first child of the root reference frame.
+    /// Initially this ID is not valid, which is indicated by ```nodes``` being empty.
+    pub fn topmost_scroll_node_index(&self) -> ClipScrollNodeIndex {
+        // TODO(mrobinson): We should eventually make this impossible to misuse.
+        debug_assert!(self.nodes.len() >= 1);
+        TOPMOST_SCROLL_NODE_INDEX
+    }
+
+    pub fn collect_nodes_bouncing_back(&self) -> FastHashSet<ClipScrollNodeIndex> {
         let mut nodes_bouncing_back = FastHashSet::default();
-        for (clip_id, node) in self.nodes.iter() {
+        for (index, node) in self.nodes.iter().enumerate() {
             if let NodeType::ScrollFrame(ref scrolling) = node.node_type {
                 if scrolling.bouncing_back {
-                    nodes_bouncing_back.insert(*clip_id);
+                    nodes_bouncing_back.insert(ClipScrollNodeIndex(index));
                 }
             }
         }
         nodes_bouncing_back
     }
 
     fn find_scrolling_node_at_point_in_node(
         &self,
         cursor: &WorldPoint,
-        clip_id: ClipId,
-    ) -> Option<ClipId> {
-        self.nodes.get(&clip_id).and_then(|node| {
-            for child_layer_id in node.children.iter().rev() {
-                if let Some(layer_id) =
-                    self.find_scrolling_node_at_point_in_node(cursor, *child_layer_id)
-                {
-                    return Some(layer_id);
-                }
+        index: ClipScrollNodeIndex,
+    ) -> Option<ClipScrollNodeIndex> {
+        let node = &self.nodes[index.0];
+        for child_index in node.children.iter().rev() {
+            let found_index = self.find_scrolling_node_at_point_in_node(cursor, *child_index);
+            if found_index.is_some() {
+                return found_index;
             }
+        }
 
-            match node.node_type {
-                NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {}
-                _ => return None,
-            }
+        match node.node_type {
+            NodeType::ScrollFrame(state) if state.sensitive_to_input_events() => {}
+            _ => return None,
+        }
 
-            if node.ray_intersects_node(cursor) {
-                Some(clip_id)
-            } else {
-                None
-            }
-        })
+        if node.ray_intersects_node(cursor) {
+            Some(index)
+        } else {
+            None
+        }
     }
 
-    pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipId {
-        self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_id())
-            .unwrap_or(self.topmost_scrolling_node_id())
+    pub fn find_scrolling_node_at_point(&self, cursor: &WorldPoint) -> ClipScrollNodeIndex {
+        self.find_scrolling_node_at_point_in_node(cursor, self.root_reference_frame_index())
+            .unwrap_or(self.topmost_scroll_node_index())
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         let mut result = vec![];
-        for node in self.nodes.values() {
+        for node in &self.nodes {
             if let NodeType::ScrollFrame(info) = node.node_type {
                 if let Some(id) = info.external_id {
                     result.push(ScrollNodeState { id, scroll_offset: info.offset })
                 }
             }
         }
         result
     }
 
     pub fn drain(&mut self) -> ScrollStates {
         self.current_new_node_item = 1;
 
         let mut scroll_states = FastHashMap::default();
-        for (node_id, old_node) in &mut self.nodes.drain() {
-            if self.pipelines_to_discard.contains(&node_id.pipeline_id()) {
+        for old_node in &mut self.nodes.drain(..) {
+            if self.pipelines_to_discard.contains(&old_node.pipeline_id) {
                 continue;
             }
 
             match old_node.node_type {
                 NodeType::ScrollFrame(info) if info.external_id.is_some() => {
                     scroll_states.insert(info.external_id.unwrap(), info);
                 }
                 _ => {}
@@ -221,17 +216,17 @@ impl ClipScrollTree {
     }
 
     pub fn scroll_node(
         &mut self,
         origin: LayerPoint,
         id: ExternalScrollId,
         clamp: ScrollClamping
     ) -> bool {
-        for node in &mut self.nodes.values_mut() {
+        for node in &mut self.nodes {
             if node.matches_external_id(id) {
                 return node.set_scroll_origin(&origin, clamp);
             }
         }
 
         self.pending_scroll_offsets.insert(id, (origin, clamp));
         false
     }
@@ -241,47 +236,48 @@ impl ClipScrollTree {
         scroll_location: ScrollLocation,
         cursor: WorldPoint,
         phase: ScrollEventPhase,
     ) -> bool {
         if self.nodes.is_empty() {
             return false;
         }
 
-        let clip_id = match (
+        let node_index = match (
             phase,
             self.find_scrolling_node_at_point(&cursor),
-            self.currently_scrolling_node_id,
+            self.currently_scrolling_node_index,
         ) {
-            (ScrollEventPhase::Start, scroll_node_at_point_id, _) => {
-                self.currently_scrolling_node_id = Some(scroll_node_at_point_id);
-                scroll_node_at_point_id
+            (ScrollEventPhase::Start, scroll_node_at_point_index, _) => {
+                self.currently_scrolling_node_index = Some(scroll_node_at_point_index);
+                scroll_node_at_point_index
             }
-            (_, scroll_node_at_point_id, Some(cached_clip_id)) => {
-                let clip_id = match self.nodes.get(&cached_clip_id) {
-                    Some(_) => cached_clip_id,
+            (_, scroll_node_at_point_index, Some(cached_node_index)) => {
+                let node_index = match self.nodes.get(cached_node_index.0) {
+                    Some(_) => cached_node_index,
                     None => {
-                        self.currently_scrolling_node_id = Some(scroll_node_at_point_id);
-                        scroll_node_at_point_id
+                        self.currently_scrolling_node_index = Some(scroll_node_at_point_index);
+                        scroll_node_at_point_index
                     }
                 };
-                clip_id
+                node_index
             }
             (_, _, None) => return false,
         };
 
-        let topmost_scrolling_node_id = self.topmost_scrolling_node_id();
-        let non_root_overscroll = if clip_id != topmost_scrolling_node_id {
-            self.nodes.get(&clip_id).unwrap().is_overscrolling()
+        let topmost_scroll_node_index = self.topmost_scroll_node_index();
+        let non_root_overscroll = if node_index != topmost_scroll_node_index {
+            self.nodes[node_index.0].is_overscrolling()
         } else {
             false
         };
 
         let mut switch_node = false;
-        if let Some(node) = self.nodes.get_mut(&clip_id) {
+        {
+            let node = &mut self.nodes[node_index.0];
             if let NodeType::ScrollFrame(ref mut scrolling) = node.node_type {
                 match phase {
                     ScrollEventPhase::Start => {
                         // if this is a new gesture, we do not switch node,
                         // however we do save the state of non_root_overscroll,
                         // for use in the subsequent Move phase.
                         scrolling.should_handoff_scroll = non_root_overscroll;
                     }
@@ -293,26 +289,23 @@ impl ClipScrollTree {
                     ScrollEventPhase::End => {
                         // clean-up when gesture ends.
                         scrolling.should_handoff_scroll = false;
                     }
                 }
             }
         }
 
-        let clip_id = if switch_node {
-            topmost_scrolling_node_id
+        let node_index = if switch_node {
+            topmost_scroll_node_index
         } else {
-            clip_id
+            node_index
         };
 
-        self.nodes
-            .get_mut(&clip_id)
-            .unwrap()
-            .scroll(scroll_location, phase)
+        self.nodes[node_index.0].scroll(scroll_location, phase)
     }
 
     pub fn update_tree(
         &mut self,
         screen_rect: &DeviceIntRect,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
@@ -322,66 +315,66 @@ impl ClipScrollTree {
         scene_properties: &SceneProperties,
     ) {
         if self.nodes.is_empty() {
             return;
         }
 
         self.clip_chains[0] = ClipChain::empty(screen_rect);
 
-        let root_reference_frame_id = self.root_reference_frame_id();
+        let root_reference_frame_index = self.root_reference_frame_index();
         let mut state = TransformUpdateState {
             parent_reference_frame_transform: LayerVector2D::new(pan.x, pan.y).into(),
             parent_accumulated_scroll_offset: LayerVector2D::zero(),
             nearest_scrolling_ancestor_offset: LayerVector2D::zero(),
             nearest_scrolling_ancestor_viewport: LayerRect::zero(),
             parent_clip_chain_index: ClipChainIndex(0),
             current_coordinate_system_id: CoordinateSystemId::root(),
             coordinate_system_relative_transform: LayerFastTransform::identity(),
             invertible: true,
         };
         let mut next_coordinate_system_id = state.current_coordinate_system_id.next();
         self.update_node(
-            root_reference_frame_id,
+            root_reference_frame_index,
             &mut state,
             &mut next_coordinate_system_id,
             device_pixel_scale,
             clip_store,
             resource_cache,
             gpu_cache,
             node_data,
             scene_properties,
         );
 
         self.build_clip_chains(screen_rect);
     }
 
     fn update_node(
         &mut self,
-        layer_id: ClipId,
+        node_index: ClipScrollNodeIndex,
         state: &mut TransformUpdateState,
         next_coordinate_system_id: &mut CoordinateSystemId,
         device_pixel_scale: DevicePixelScale,
         clip_store: &mut ClipStore,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         gpu_node_data: &mut Vec<ClipScrollNodeData>,
         scene_properties: &SceneProperties,
     ) {
         // TODO(gw): This is an ugly borrow check workaround to clone these.
         //           Restructure this to avoid the clones!
         let mut state = state.clone();
         let node_children = {
-            let node = match self.nodes.get_mut(&layer_id) {
+            let node = match self.nodes.get_mut(node_index.0) {
                 Some(node) => node,
                 None => return,
             };
 
             // We set this early so that we can use it to populate the ClipChain.
-            node.node_data_index = ClipScrollNodeIndex(gpu_node_data.len() as u32);
+            node.node_data_index = GPUClipScrollNodeIndex(gpu_node_data.len() as u32);
 
             node.update(
                 &mut state,
                 next_coordinate_system_id,
                 device_pixel_scale,
                 clip_store,
                 resource_cache,
                 gpu_cache,
@@ -394,19 +387,19 @@ impl ClipScrollTree {
             if node.children.is_empty() {
                 return;
             }
 
             node.prepare_state_for_children(&mut state);
             node.children.clone()
         };
 
-        for child_node_id in node_children {
+        for child_node_index in node_children {
             self.update_node(
-                child_node_id,
+                child_node_index,
                 &mut state,
                 next_coordinate_system_id,
                 device_pixel_scale,
                 clip_store,
                 resource_cache,
                 gpu_cache,
                 gpu_node_data,
                 scene_properties,
@@ -421,193 +414,201 @@ impl ClipScrollTree {
             // parent's node, if necessary.
             let mut chain = match descriptor.parent {
                 Some(index) => self.clip_chains[index.0].clone(),
                 None => ClipChain::empty(screen_rect),
             };
 
             // Now we walk through each ClipScrollNode in the vector of clip nodes and
             // extract their ClipChain nodes to construct the final list.
-            for clip_id in &descriptor.clips {
-                let node_clip_chain_index = match self.nodes[&clip_id].node_type {
-                    NodeType::Clip { clip_chain_index, .. } => clip_chain_index,
-                    _ => {
-                        warn!("Tried to create a clip chain with non-clipping node.");
-                        continue;
+            for clip_index in &descriptor.clips {
+                match self.nodes[clip_index.0].node_type {
+                    NodeType::Clip { clip_chain_node: Some(ref node), .. } => {
+                        chain.add_node(node.clone());
                     }
+                    NodeType::Clip { .. } => warn!("Found uninitialized clipping ClipScrollNode."),
+                    _ => warn!("Tried to create a clip chain with non-clipping node."),
                 };
-
-                if let Some(ref nodes) = self.clip_chains[node_clip_chain_index.0].nodes {
-                    chain.add_node((**nodes).clone());
-                }
             }
 
             chain.parent_index = descriptor.parent;
             self.clip_chains[descriptor.index.0] = chain;
         }
     }
 
     pub fn tick_scrolling_bounce_animations(&mut self) {
-        for (_, node) in &mut self.nodes {
+        for node in &mut self.nodes {
             node.tick_scrolling_bounce_animation()
         }
     }
 
     pub fn finalize_and_apply_pending_scroll_offsets(&mut self, old_states: ScrollStates) {
-        for node in self.nodes.values_mut() {
+        for node in &mut self.nodes {
             let external_id = match node.node_type {
                 NodeType::ScrollFrame(ScrollFrameInfo { external_id: Some(id), ..} ) => id,
                 _ => continue,
             };
 
             if let Some(scrolling_state) = old_states.get(&external_id) {
                 node.apply_old_scrolling_state(scrolling_state);
             }
 
-
             if let Some((offset, clamping)) = self.pending_scroll_offsets.remove(&external_id) {
                 node.set_scroll_origin(&offset, clamping);
             }
         }
     }
 
     pub fn add_clip_node(
         &mut self,
-        id: ClipId,
-        parent_id: ClipId,
+        index: ClipScrollNodeIndex,
+        parent_index: ClipScrollNodeIndex,
         handle: ClipSourcesHandle,
         clip_rect: LayerRect,
+        pipeline_id: PipelineId,
     )  -> ClipChainIndex {
         let clip_chain_index = self.allocate_clip_chain();
-        let node_type = NodeType::Clip { handle, clip_chain_index };
-        let node = ClipScrollNode::new(id.pipeline_id(), Some(parent_id), &clip_rect, node_type);
-        self.add_node(node, id);
+        let node_type = NodeType::Clip { handle, clip_chain_index, clip_chain_node: None };
+        let node = ClipScrollNode::new(pipeline_id, Some(parent_index), &clip_rect, node_type);
+        self.add_node(node, index);
         clip_chain_index
     }
 
     pub fn add_sticky_frame(
         &mut self,
-        id: ClipId,
-        parent_id: ClipId,
+        index: ClipScrollNodeIndex,
+        parent_index: ClipScrollNodeIndex,
         frame_rect: LayerRect,
         sticky_frame_info: StickyFrameInfo,
+        pipeline_id: PipelineId,
     ) {
         let node = ClipScrollNode::new_sticky_frame(
-            parent_id,
+            parent_index,
             frame_rect,
             sticky_frame_info,
-            id.pipeline_id(),
+            pipeline_id,
         );
-        self.add_node(node, id);
+        self.add_node(node, index);
     }
 
     pub fn add_clip_chain_descriptor(
         &mut self,
         parent: Option<ClipChainIndex>,
-        clips: Vec<ClipId>
+        clips: Vec<ClipScrollNodeIndex>
     ) -> ClipChainIndex {
         let index = self.allocate_clip_chain();
         self.clip_chains_descriptors.push(ClipChainDescriptor { index, parent, clips });
         index
     }
 
-    pub fn add_node(&mut self, node: ClipScrollNode, id: ClipId) {
+    pub fn add_node(&mut self, node: ClipScrollNode, index: ClipScrollNodeIndex) {
         // When the parent node is None this means we are adding the root.
-        match node.parent {
-            Some(parent_id) => self.nodes.get_mut(&parent_id).unwrap().add_child(id),
-            None => self.root_reference_frame_id = id,
+        if let Some(parent_index) = node.parent {
+            self.nodes[parent_index.0].add_child(index);
+        }
+
+        if index.0 == self.nodes.len() {
+            self.nodes.push(node);
+            return;
         }
 
-        debug_assert!(!self.nodes.contains_key(&id));
-        self.nodes.insert(id, node);
+
+        if let Some(empty_node) = self.nodes.get_mut(index.0) {
+            *empty_node = node;
+            return
+        }
+
+        let length_to_reserve = index.0 + 1 - self.nodes.len();
+        self.nodes.reserve_exact(length_to_reserve);
+
+        // We would like to use `Vec::resize` here, but the Clone trait is not supported
+        // for ClipScrollNodes. We can fix this either by splitting the clip nodes out into
+        // their own tree or when support is added for something like `Vec::resize_default`.
+        let length_to_extend = self.nodes.len() .. index.0;
+        self.nodes.extend(length_to_extend.map(|_| ClipScrollNode::empty()));
+
+        self.nodes.push(node);
     }
 
     pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
         self.pipelines_to_discard.insert(pipeline_id);
 
-        match self.currently_scrolling_node_id {
-            Some(id) if id.pipeline_id() == pipeline_id => self.currently_scrolling_node_id = None,
-            _ => {}
+        if let Some(index) = self.currently_scrolling_node_index {
+            if self.nodes[index.0].pipeline_id == pipeline_id {
+                self.currently_scrolling_node_index = None;
+            }
         }
     }
 
-    fn print_node<T: PrintTreePrinter>(&self, id: &ClipId, pt: &mut T, clip_store: &ClipStore) {
-        let node = self.nodes.get(id).unwrap();
-
+    fn print_node<T: PrintTreePrinter>(
+        &self,
+        index: ClipScrollNodeIndex,
+        pt: &mut T,
+        clip_store: &ClipStore
+    ) {
+        let node = &self.nodes[index.0];
         match node.node_type {
             NodeType::Clip { ref handle, .. } => {
                 pt.new_level("Clip".to_owned());
 
-                pt.add_item(format!("id: {:?}", id));
+                pt.add_item(format!("index: {:?}", index));
                 let clips = clip_store.get(&handle).clips();
                 pt.new_level(format!("Clip Sources [{}]", clips.len()));
                 for source in clips {
                     pt.add_item(format!("{:?}", source));
                 }
                 pt.end_level();
             }
             NodeType::ReferenceFrame(ref info) => {
                 pt.new_level(format!("ReferenceFrame {:?}", info.resolved_transform));
-                pt.add_item(format!("id: {:?}", id));
+                pt.add_item(format!("index: {:?}", index));
             }
             NodeType::ScrollFrame(scrolling_info) => {
                 pt.new_level(format!("ScrollFrame"));
-                pt.add_item(format!("id: {:?}", id));
+                pt.add_item(format!("index: {:?}", index));
                 pt.add_item(format!("scrollable_size: {:?}", scrolling_info.scrollable_size));
                 pt.add_item(format!("scroll.offset: {:?}", scrolling_info.offset));
             }
             NodeType::StickyFrame(ref sticky_frame_info) => {
                 pt.new_level(format!("StickyFrame"));
-                pt.add_item(format!("id: {:?}", id));
+                pt.add_item(format!("index: {:?}", index));
                 pt.add_item(format!("sticky info: {:?}", sticky_frame_info));
             }
+            NodeType::Empty => unreachable!("Empty node remaining in ClipScrollTree."),
         }
 
-        pt.add_item(format!(
-            "local_viewport_rect: {:?}",
-            node.local_viewport_rect
-        ));
-        pt.add_item(format!(
-            "world_viewport_transform: {:?}",
-            node.world_viewport_transform
-        ));
-        pt.add_item(format!(
-            "world_content_transform: {:?}",
-            node.world_content_transform
-        ));
-        pt.add_item(format!(
-            "coordinate_system_id: {:?}",
-            node.coordinate_system_id
-        ));
+        pt.add_item(format!("local_viewport_rect: {:?}", node.local_viewport_rect));
+        pt.add_item(format!("world_viewport_transform: {:?}", node.world_viewport_transform));
+        pt.add_item(format!("world_content_transform: {:?}", node.world_content_transform));
+        pt.add_item(format!("coordinate_system_id: {:?}", node.coordinate_system_id));
 
-        for child_id in &node.children {
-            self.print_node(child_id, pt, clip_store);
+        for child_index in &node.children {
+            self.print_node(*child_index, pt, clip_store);
         }
 
         pt.end_level();
     }
 
     #[allow(dead_code)]
     pub fn print(&self, clip_store: &ClipStore) {
         if !self.nodes.is_empty() {
             let mut pt = PrintTree::new("clip_scroll tree");
             self.print_with(clip_store, &mut pt);
         }
     }
 
     pub fn print_with<T: PrintTreePrinter>(&self, clip_store: &ClipStore, pt: &mut T) {
         if !self.nodes.is_empty() {
-            self.print_node(&self.root_reference_frame_id, pt, clip_store);
+            self.print_node(self.root_reference_frame_index(), pt, clip_store);
         }
     }
 
     pub fn allocate_clip_chain(&mut self) -> ClipChainIndex {
         debug_assert!(!self.clip_chains.is_empty());
         let new_clip_chain =self.clip_chains[0].clone();
         self.clip_chains.push(new_clip_chain);
         ClipChainIndex(self.clip_chains.len() - 1)
     }
 
     pub fn get_clip_chain(&self, index: ClipChainIndex) -> &ClipChain {
         &self.clip_chains[index.0]
     }
-
 }
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1004,25 +1004,31 @@ impl Device {
         }
         self.gl.delete_textures(&[old_texture_id]);
         self.bind_read_target(None);
     }
 
     pub fn init_texture(
         &mut self,
         texture: &mut Texture,
-        width: u32,
-        height: u32,
+        mut width: u32,
+        mut height: u32,
         filter: TextureFilter,
         render_target: Option<RenderTargetInfo>,
         layer_count: i32,
         pixels: Option<&[u8]>,
     ) {
         debug_assert!(self.inside_frame);
 
+        if width > self.max_texture_size || height > self.max_texture_size {
+            error!("Attempting to allocate a texture of size {}x{} above the limit, trimming", width, height);
+            width = width.min(self.max_texture_size);
+            height = height.min(self.max_texture_size);
+        }
+
         let is_resized = texture.width != width || texture.height != height;
 
         texture.width = width;
         texture.height = height;
         texture.filter = filter;
         texture.layer_count = layer_count;
         texture.render_target = render_target;
         texture.last_frame_used = self.frame_id;
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -0,0 +1,2744 @@
+
+/* 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 api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter};
+use api::{ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect};
+use api::{DeviceIntSize, DevicePixelScale, DeviceUintRect, DeviceUintSize};
+use api::{DisplayItemRef, Epoch, ExtendMode, ExternalScrollId, FilterOp};
+use api::{FontInstanceKey, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop};
+use api::{IframeDisplayItem, ImageDisplayItem, ImageKey, ImageRendering, ItemRange, LayerPoint};
+use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, LayoutTransform};
+use api::{LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId};
+use api::{PropertyBinding, RepeatMode, ScrollFrameDisplayItem, ScrollPolicy, ScrollSensitivity};
+use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
+use api::{TileOffset, TransformStyle, YuvColorSpace, YuvData};
+use app_units::Au;
+use border::ImageBorderSegment;
+use clip::{ClipRegion, ClipSource, ClipSources, ClipStore};
+use clip_scroll_node::{ClipScrollNode, NodeType, StickyFrameInfo};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
+use euclid::{SideOffsets2D, rect, vec2};
+use frame_builder::{FrameBuilder, FrameBuilderConfig};
+use glyph_rasterizer::FontInstance;
+use hit_test::{HitTestingItem, HitTestingRun};
+use internal_types::{FastHashMap, FastHashSet};
+use picture::{PictureCompositeMode, PictureKind, PicturePrimitive};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient};
+use prim_store::{CachedGradientIndex, ImageCacheKey, ImagePrimitiveCpu, ImageSource};
+use prim_store::{PrimitiveContainer, PrimitiveIndex, PrimitiveKind, PrimitiveStore};
+use prim_store::{ScrollNodeAndClipChain, TextRunPrimitiveCpu};
+use render_backend::{DocumentView};
+use resource_cache::{FontInstanceMap, ImageRequest, TiledImageMap};
+use scene::{Scene, ScenePipeline, StackingContextHelpers};
+use scene_builder::{BuiltScene, SceneRequest};
+use std::{f32, mem, usize};
+use tiling::{CompositeOps, ScrollbarPrimitive};
+use util::{MaxRect, RectHelpers, recycle_vec};
+
+static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
+    r: 0.3,
+    g: 0.3,
+    b: 0.3,
+    a: 0.6,
+};
+
+/// A data structure that keeps track of mapping between API ClipIds and the indices used
+/// internally in the ClipScrollTree to avoid having to do HashMap lookups. ClipIdToIndexMapper is
+/// responsible for mapping both ClipId to ClipChainIndex and ClipId to ClipScrollNodeIndex.  We
+/// also include two small LRU caches. Currently the caches are small (1 entry), but in the future
+/// we could use uluru here to do something more involved.
+#[derive(Default)]
+pub struct ClipIdToIndexMapper {
+    /// A map which converts a ClipId for a clipping node or an API-defined ClipChain into
+    /// a ClipChainIndex, which is the index used internally in the ClipScrollTree to
+    /// identify ClipChains.
+    clip_chain_map: FastHashMap<ClipId, ClipChainIndex>,
+
+    /// The last mapped ClipChainIndex, used to avoid having to do lots of consecutive
+    /// HashMap lookups.
+    cached_clip_chain_index: Option<(ClipId, ClipChainIndex)>,
+
+    /// The offset in the ClipScrollTree's array of ClipScrollNodes for a particular pipeline.
+    /// This is used to convert a ClipId into a ClipScrollNodeIndex.
+    pipeline_offsets: FastHashMap<PipelineId, usize>,
+
+    /// The last mapped pipeline offset for this mapper. This is used to avoid having to
+    /// consult `pipeline_offsets` repeatedly when flattening the display list.
+    cached_pipeline_offset: Option<(PipelineId, usize)>,
+
+    /// The next available pipeline offset for ClipScrollNodeIndex. When we encounter a pipeline
+    /// we will use this value and increment it by the total number of ClipScrollNodes in the
+    /// pipeline's display list.
+    next_available_offset: usize,
+}
+
+impl ClipIdToIndexMapper {
+    pub fn add_clip_chain(&mut self, id: ClipId, index: ClipChainIndex) {
+        debug_assert!(!self.clip_chain_map.contains_key(&id));
+        self.clip_chain_map.insert(id, index);
+    }
+
+    pub fn map_to_parent_clip_chain(&mut self, id: ClipId, parent_id: &ClipId) {
+        let parent_chain_index = self.get_clip_chain_index(parent_id);
+        self.add_clip_chain(id, parent_chain_index);
+    }
+
+    pub fn get_clip_chain_index(&mut self, id: &ClipId) -> ClipChainIndex {
+        match self.cached_clip_chain_index {
+            Some((cached_id, cached_clip_chain_index)) if cached_id == *id =>
+                return cached_clip_chain_index,
+            _ => {}
+        }
+
+        self.clip_chain_map[id]
+    }
+
+    pub fn get_clip_chain_index_and_cache_result(&mut self, id: &ClipId) -> ClipChainIndex {
+        let index = self.get_clip_chain_index(id);
+        self.cached_clip_chain_index = Some((*id, index));
+        index
+    }
+
+    pub fn map_clip_and_scroll(&mut self, info: &ClipAndScrollInfo) -> ScrollNodeAndClipChain {
+        ScrollNodeAndClipChain::new(
+            self.get_node_index(info.scroll_node_id),
+            self.get_clip_chain_index_and_cache_result(&info.clip_node_id())
+        )
+    }
+
+    pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
+        self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
+    }
+
+    pub fn initialize_for_pipeline(&mut self, pipeline: &ScenePipeline) {
+        debug_assert!(!self.pipeline_offsets.contains_key(&pipeline.pipeline_id));
+        self.pipeline_offsets.insert(pipeline.pipeline_id, self.next_available_offset);
+        self.next_available_offset += pipeline.display_list.total_clip_ids();
+    }
+
+    pub fn get_node_index(&mut self, id: ClipId) -> ClipScrollNodeIndex {
+        let (index, pipeline_id) = match id {
+            ClipId::Clip(index, pipeline_id) => (index, pipeline_id),
+            ClipId::ClipChain(_) => panic!("Tried to use ClipChain as scroll node."),
+        };
+
+        let pipeline_offset = match self.cached_pipeline_offset {
+            Some((last_used_id, offset)) if last_used_id == pipeline_id => offset,
+            _ => {
+                let offset = self.pipeline_offsets[&pipeline_id];
+                self.cached_pipeline_offset = Some((pipeline_id, offset));
+                offset
+            }
+        };
+
+        ClipScrollNodeIndex(pipeline_offset + index)
+    }
+}
+
+/// A structure that converts a serialized display list into a form that WebRender
+/// can use to later build a frame. This structure produces a FrameBuilder. Public
+/// members are typically those that are destructured into the FrameBuilder.
+pub struct DisplayListFlattener<'a> {
+    /// The scene that we are currently flattening.
+    scene: &'a Scene,
+
+    /// The ClipScrollTree that we are currently building during flattening.
+    clip_scroll_tree: &'a mut ClipScrollTree,
+
+    /// The map of all font instances.
+    font_instances: FontInstanceMap,
+
+    /// The map of tiled images.
+    tiled_image_map: TiledImageMap,
+
+    /// Used to track the latest flattened epoch for each pipeline.
+    pipeline_epochs: Vec<(PipelineId, Epoch)>,
+
+    /// A set of pipelines that the caller has requested be made available as
+    /// output textures.
+    output_pipelines: &'a FastHashSet<PipelineId>,
+
+    /// A list of replacements to make in order to properly handle fixed position
+    /// content as well as stacking contexts that create reference frames.
+    replacements: Vec<(ClipId, ClipId)>,
+
+    /// The data structure that converting between ClipId and the various index
+    /// types that the ClipScrollTree uses.
+    id_to_index_mapper: ClipIdToIndexMapper,
+
+    /// A stack of the current shadow primitives.  The sub-Vec stores
+    /// a buffer of fast-path primitives to be appended on pop.
+    shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ScrollNodeAndClipChain)>)>,
+
+    /// A buffer of "real" content when doing fast-path shadows. This is appended
+    /// when the shadow stack is empty.
+    pending_shadow_contents: Vec<(PrimitiveIndex, ScrollNodeAndClipChain, LayerPrimitiveInfo)>,
+
+    /// A stack of scroll nodes used during display list processing to properly
+    /// parent new scroll nodes.
+    reference_frame_stack: Vec<(ClipId, ClipScrollNodeIndex)>,
+
+    /// A stack of stacking context properties.
+    sc_stack: Vec<FlattenedStackingContext>,
+
+    /// A stack of the current pictures.
+    picture_stack: Vec<PrimitiveIndex>,
+
+    /// A list of scrollbar primitives.
+    pub scrollbar_prims: Vec<ScrollbarPrimitive>,
+
+    /// The store of primitives.
+    pub prim_store: PrimitiveStore,
+
+    /// Information about all primitives involved in hit testing.
+    pub hit_testing_runs: Vec<HitTestingRun>,
+
+    /// The store which holds all complex clipping information.
+    pub clip_store: ClipStore,
+
+    /// The configuration to use for the FrameBuilder. We consult this in
+    /// order to determine the default font.
+    pub config: FrameBuilderConfig,
+
+    /// The gradients collecting during display list flattening.
+    pub cached_gradients: Vec<CachedGradient>,
+}
+
+impl<'a> DisplayListFlattener<'a> {
+    pub fn create_frame_builder(
+        old_builder: FrameBuilder,
+        scene: &Scene,
+        clip_scroll_tree: &mut ClipScrollTree,
+        font_instances: FontInstanceMap,
+        tiled_image_map: TiledImageMap,
+        view: &DocumentView,
+        output_pipelines: &FastHashSet<PipelineId>,
+        frame_builder_config: &FrameBuilderConfig,
+        pipeline_epochs: &mut FastHashMap<PipelineId, Epoch>,
+    ) -> FrameBuilder {
+        // We checked that the root pipeline is available on the render backend.
+        let root_pipeline_id = scene.root_pipeline_id.unwrap();
+        let root_pipeline = scene.pipelines.get(&root_pipeline_id).unwrap();
+
+        let root_epoch = scene.pipeline_epochs[&root_pipeline_id];
+        pipeline_epochs.insert(root_pipeline_id, root_epoch);
+
+        let background_color = root_pipeline
+            .background_color
+            .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
+
+        let mut flattener = DisplayListFlattener {
+            scene,
+            clip_scroll_tree,
+            font_instances,
+            tiled_image_map,
+            config: *frame_builder_config,
+            pipeline_epochs: Vec::new(),
+            replacements: Vec::new(),
+            output_pipelines,
+            id_to_index_mapper: ClipIdToIndexMapper::default(),
+            hit_testing_runs: recycle_vec(old_builder.hit_testing_runs),
+            shadow_prim_stack: Vec::new(),
+            cached_gradients: recycle_vec(old_builder.cached_gradients),
+            pending_shadow_contents: Vec::new(),
+            scrollbar_prims: recycle_vec(old_builder.scrollbar_prims),
+            reference_frame_stack: Vec::new(),
+            picture_stack: Vec::new(),
+            sc_stack: Vec::new(),
+            prim_store: old_builder.prim_store.recycle(),
+            clip_store: old_builder.clip_store.recycle(),
+        };
+
+        flattener.id_to_index_mapper.initialize_for_pipeline(root_pipeline);
+        flattener.push_root(
+            root_pipeline_id,
+            &root_pipeline.viewport_size,
+            &root_pipeline.content_size,
+        );
+        flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
+        flattener.flatten_root(root_pipeline, &root_pipeline.viewport_size);
+
+        debug_assert!(flattener.picture_stack.is_empty());
+        pipeline_epochs.extend(flattener.pipeline_epochs.drain(..));
+
+        FrameBuilder::with_display_list_flattener(
+            view.inner_rect,
+            background_color,
+            view.window_size,
+            flattener
+        )
+    }
+
+    /// Since WebRender still handles fixed position and reference frame content internally
+    /// we need to apply this table of id replacements only to the id that affects the
+    /// position of a node. We can eventually remove this when clients start handling
+    /// reference frames themselves. This method applies these replacements.
+    fn apply_scroll_frame_id_replacement(&self, index: ClipId) -> ClipId {
+        match self.replacements.last() {
+            Some(&(to_replace, replacement)) if to_replace == index => replacement,
+            _ => index,
+        }
+    }
+
+    fn get_complex_clips(
+        &self,
+        pipeline_id: PipelineId,
+        complex_clips: ItemRange<ComplexClipRegion>,
+    ) -> Vec<ComplexClipRegion> {
+        if complex_clips.is_empty() {
+            return vec![];
+        }
+
+        self.scene
+            .pipelines
+            .get(&pipeline_id)
+            .expect("No display list?")
+            .display_list
+            .get(complex_clips)
+            .collect()
+    }
+
+    fn get_clip_chain_items(
+        &self,
+        pipeline_id: PipelineId,
+        items: ItemRange<ClipId>,
+    ) -> Vec<ClipId> {
+        if items.is_empty() {
+            return vec![];
+        }
+
+        self.scene
+            .pipelines
+            .get(&pipeline_id)
+            .expect("No display list?")
+            .display_list
+            .get(items)
+            .collect()
+    }
+
+    fn flatten_root(&mut self, pipeline: &'a ScenePipeline, frame_size: &LayoutSize) {
+        let pipeline_id = pipeline.pipeline_id;
+        let reference_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
+            &ClipId::root_reference_frame(pipeline_id)
+        );
+        let scroll_frame_info = self.id_to_index_mapper.simple_scroll_and_clip_chain(
+            &ClipId::root_scroll_node(pipeline_id)
+        );
+
+        self.push_stacking_context(
+            pipeline_id,
+            CompositeOps::default(),
+            TransformStyle::Flat,
+            true,
+            true,
+            scroll_frame_info,
+        );
+
+        // For the root pipeline, there's no need to add a full screen rectangle
+        // here, as it's handled by the framebuffer clear.
+        if self.scene.root_pipeline_id != Some(pipeline_id) {
+            if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
+                if let Some(bg_color) = pipeline.background_color {
+                    let root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
+                    let info = LayerPrimitiveInfo::new(root_bounds);
+                    self.add_solid_rectangle(
+                        reference_frame_info,
+                        &info,
+                        bg_color,
+                        None,
+                    );
+                }
+            }
+        }
+
+        self.flatten_items(&mut pipeline.display_list.iter(), pipeline_id, LayerVector2D::zero());
+
+        if self.config.enable_scrollbars {
+            let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
+            let container_rect = LayerRect::new(LayerPoint::zero(), *frame_size);
+            self.add_scroll_bar(
+                reference_frame_info,
+                &LayerPrimitiveInfo::new(scrollbar_rect),
+                DEFAULT_SCROLLBAR_COLOR,
+                ScrollbarInfo(scroll_frame_info.scroll_node_id, container_rect),
+            );
+        }
+
+        self.pop_stacking_context();
+    }
+
+    fn flatten_items(
+        &mut self,
+        traversal: &mut BuiltDisplayListIter<'a>,
+        pipeline_id: PipelineId,
+        reference_frame_relative_offset: LayerVector2D,
+    ) {
+        loop {
+            let subtraversal = {
+                let item = match traversal.next() {
+                    Some(item) => item,
+                    None => break,
+                };
+
+                if SpecificDisplayItem::PopStackingContext == *item.item() {
+                    return;
+                }
+
+                self.flatten_item(
+                    item,
+                    pipeline_id,
+                    reference_frame_relative_offset,
+                )
+            };
+
+            // If flatten_item created a sub-traversal, we need `traversal` to have the
+            // same state as the completed subtraversal, so we reinitialize it here.
+            if let Some(subtraversal) = subtraversal {
+                *traversal = subtraversal;
+            }
+        }
+    }
+
+    fn flatten_sticky_frame(
+        &mut self,
+        item: &DisplayItemRef,
+        info: &StickyFrameDisplayItem,
+        clip_and_scroll: &ScrollNodeAndClipChain,
+        parent_id: &ClipId,
+        reference_frame_relative_offset: &LayerVector2D,
+    ) {
+        let frame_rect = item.rect().translate(&reference_frame_relative_offset);
+        let sticky_frame_info = StickyFrameInfo::new(
+            info.margins,
+            info.vertical_offset_bounds,
+            info.horizontal_offset_bounds,
+            info.previously_applied_offset,
+        );
+
+        let index = self.id_to_index_mapper.get_node_index(info.id);
+        self.clip_scroll_tree.add_sticky_frame(
+            index,
+            clip_and_scroll.scroll_node_id, /* parent id */
+            frame_rect,
+            sticky_frame_info,
+            info.id.pipeline_id(),
+        );
+        self.id_to_index_mapper.map_to_parent_clip_chain(info.id, &parent_id);
+    }
+
+    fn flatten_scroll_frame(
+        &mut self,
+        item: &DisplayItemRef,
+        info: &ScrollFrameDisplayItem,
+        pipeline_id: PipelineId,
+        clip_and_scroll_ids: &ClipAndScrollInfo,
+        reference_frame_relative_offset: &LayerVector2D,
+    ) {
+        let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
+        let clip_region = ClipRegion::create_for_clip_node(
+            *item.local_clip().clip_rect(),
+            complex_clips,
+            info.image_mask,
+            &reference_frame_relative_offset,
+        );
+        // Just use clip rectangle as the frame rect for this scroll frame.
+        // This is useful when calculating scroll extents for the
+        // ClipScrollNode::scroll(..) API as well as for properly setting sticky
+        // positioning offsets.
+        let frame_rect = item.local_clip()
+            .clip_rect()
+            .translate(&reference_frame_relative_offset);
+        let content_rect = item.rect().translate(&reference_frame_relative_offset);
+
+        debug_assert!(info.clip_id != info.scroll_frame_id);
+
+        self.add_clip_node(info.clip_id, clip_and_scroll_ids.scroll_node_id, clip_region);
+
+        self.add_scroll_frame(
+            info.scroll_frame_id,
+            info.clip_id,
+            info.external_id,
+            pipeline_id,
+            &frame_rect,
+            &content_rect.size,
+            info.scroll_sensitivity,
+        );
+    }
+
+    fn flatten_stacking_context(
+        &mut self,
+        traversal: &mut BuiltDisplayListIter<'a>,
+        pipeline_id: PipelineId,
+        unreplaced_scroll_id: ClipId,
+        mut scroll_node_id: ClipId,
+        mut reference_frame_relative_offset: LayerVector2D,
+        bounds: &LayerRect,
+        stacking_context: &StackingContext,
+        filters: ItemRange<FilterOp>,
+        is_backface_visible: bool,
+    ) {
+        // Avoid doing unnecessary work for empty stacking contexts.
+        if traversal.current_stacking_context_empty() {
+            traversal.skip_current_stacking_context();
+            return;
+        }
+
+        let composition_operations = {
+            // TODO(optimization?): self.traversal.display_list()
+            let display_list = &self
+                .scene
+                .pipelines
+                .get(&pipeline_id)
+                .expect("No display list?!")
+                .display_list;
+            CompositeOps::new(
+                stacking_context.filter_ops_for_compositing(display_list, filters),
+                stacking_context.mix_blend_mode_for_compositing(),
+            )
+        };
+
+        if stacking_context.scroll_policy == ScrollPolicy::Fixed {
+            scroll_node_id = self.current_reference_frame_id();
+            self.replacements.push((unreplaced_scroll_id, scroll_node_id));
+        }
+
+        reference_frame_relative_offset += bounds.origin.to_vector();
+
+        // If we have a transformation or a perspective, we should have been assigned a new
+        // reference frame id. This means this stacking context establishes a new reference frame.
+        // Descendant fixed position content will be positioned relative to us.
+        if let Some(reference_frame_id) = stacking_context.reference_frame_id {
+            debug_assert!(
+                stacking_context.transform.is_some() ||
+                stacking_context.perspective.is_some()
+            );
+
+            let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
+            self.push_reference_frame(
+                reference_frame_id,
+                Some(scroll_node_id),
+                pipeline_id,
+                &reference_frame_bounds,
+                stacking_context.transform,
+                stacking_context.perspective,
+                reference_frame_relative_offset,
+            );
+            self.replacements.push((unreplaced_scroll_id, reference_frame_id));
+            reference_frame_relative_offset = LayerVector2D::zero();
+        }
+
+        // We apply the replacements one more time in case we need to set it to a replacement
+        // that we just pushed above.
+        let sc_scroll_node = self.apply_scroll_frame_id_replacement(unreplaced_scroll_id);
+        let stacking_context_clip_and_scroll =
+            self.id_to_index_mapper.simple_scroll_and_clip_chain(&sc_scroll_node);
+        self.push_stacking_context(
+            pipeline_id,
+            composition_operations,
+            stacking_context.transform_style,
+            is_backface_visible,
+            false,
+            stacking_context_clip_and_scroll,
+        );
+
+        self.flatten_items(
+            traversal,
+            pipeline_id,
+            reference_frame_relative_offset,
+        );
+
+        if stacking_context.scroll_policy == ScrollPolicy::Fixed {
+            self.replacements.pop();
+        }
+
+        if stacking_context.reference_frame_id.is_some() {
+            self.replacements.pop();
+            self.pop_reference_frame();
+        }
+
+        self.pop_stacking_context();
+    }
+
+    fn flatten_iframe(
+        &mut self,
+        item: &DisplayItemRef,
+        info: &IframeDisplayItem,
+        clip_and_scroll_ids: &ClipAndScrollInfo,
+        reference_frame_relative_offset: &LayerVector2D,
+    ) {
+        let iframe_pipeline_id = info.pipeline_id;
+        let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
+            Some(pipeline) => pipeline,
+            None => return,
+        };
+
+        self.id_to_index_mapper.initialize_for_pipeline(pipeline);
+
+        self.add_clip_node(
+            info.clip_id,
+            clip_and_scroll_ids.scroll_node_id,
+            ClipRegion::create_for_clip_node_with_local_clip(
+                &item.local_clip(),
+                &reference_frame_relative_offset
+            ),
+        );
+
+        let epoch = self.scene.pipeline_epochs[&iframe_pipeline_id];
+        self.pipeline_epochs.push((iframe_pipeline_id, epoch));
+
+        let bounds = item.rect();
+        let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
+        let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
+        self.push_reference_frame(
+            ClipId::root_reference_frame(iframe_pipeline_id),
+            Some(info.clip_id),
+            iframe_pipeline_id,
+            &iframe_rect,
+            None,
+            None,
+            origin,
+        );
+
+        self.add_scroll_frame(
+            ClipId::root_scroll_node(iframe_pipeline_id),
+            ClipId::root_reference_frame(iframe_pipeline_id),
+            Some(ExternalScrollId(0, iframe_pipeline_id)),
+            iframe_pipeline_id,
+            &iframe_rect,
+            &pipeline.content_size,
+            ScrollSensitivity::ScriptAndInputEvents,
+        );
+
+        self.flatten_root(pipeline, &iframe_rect.size);
+
+        self.pop_reference_frame();
+    }
+
+    fn flatten_item<'b>(
+        &'b mut self,
+        item: DisplayItemRef<'a, 'b>,
+        pipeline_id: PipelineId,
+        reference_frame_relative_offset: LayerVector2D,
+    ) -> Option<BuiltDisplayListIter<'a>> {
+        let mut clip_and_scroll_ids = item.clip_and_scroll();
+        let unreplaced_scroll_id = clip_and_scroll_ids.scroll_node_id;
+        clip_and_scroll_ids.scroll_node_id =
+            self.apply_scroll_frame_id_replacement(clip_and_scroll_ids.scroll_node_id);
+        let clip_and_scroll = self.id_to_index_mapper.map_clip_and_scroll(&clip_and_scroll_ids);
+
+        let prim_info = item.get_layer_primitive_info(&reference_frame_relative_offset);
+        match *item.item() {
+            SpecificDisplayItem::Image(ref info) => {
+                match self.tiled_image_map.get(&info.image_key).cloned() {
+                    Some(tiling) => {
+                        // The image resource is tiled. We have to generate an image primitive
+                        // for each tile.
+                        self.decompose_image(
+                            clip_and_scroll,
+                            &prim_info,
+                            info,
+                            tiling.image_size,
+                            tiling.tile_size as u32,
+                        );
+                    }
+                    None => {
+                        self.add_image(
+                            clip_and_scroll,
+                            &prim_info,
+                            info.stretch_size,
+                            info.tile_spacing,
+                            None,
+                            info.image_key,
+                            info.image_rendering,
+                            info.alpha_type,
+                            None,
+                        );
+                    }
+                }
+            }
+            SpecificDisplayItem::YuvImage(ref info) => {
+                self.add_yuv_image(
+                    clip_and_scroll,
+                    &prim_info,
+                    info.yuv_data,
+                    info.color_space,
+                    info.image_rendering,
+                );
+            }
+            SpecificDisplayItem::Text(ref text_info) => {
+                self.add_text(
+                    clip_and_scroll,
+                    reference_frame_relative_offset,
+                    &prim_info,
+                    &text_info.font_key,
+                    &text_info.color,
+                    item.glyphs(),
+                    item.display_list().get(item.glyphs()).count(),
+                    text_info.glyph_options,
+                );
+            }
+            SpecificDisplayItem::Rectangle(ref info) => {
+                self.add_solid_rectangle(
+                    clip_and_scroll,
+                    &prim_info,
+                    info.color,
+                    None,
+                );
+            }
+            SpecificDisplayItem::ClearRectangle => {
+                self.add_clear_rectangle(
+                    clip_and_scroll,
+                    &prim_info,
+                );
+            }
+            SpecificDisplayItem::Line(ref info) => {
+                self.add_line(
+                    clip_and_scroll,
+                    &prim_info,
+                    info.wavy_line_thickness,
+                    info.orientation,
+                    &info.color,
+                    info.style,
+                );
+            }
+            SpecificDisplayItem::Gradient(ref info) => {
+                self.add_gradient(
+                    clip_and_scroll,
+                    &prim_info,
+                    info.gradient.start_point,
+                    info.gradient.end_point,
+                    item.gradient_stops(),
+                    item.display_list().get(item.gradient_stops()).count(),
+                    info.gradient.extend_mode,
+                    info.tile_size,
+                    info.tile_spacing,
+                );
+            }
+            SpecificDisplayItem::RadialGradient(ref info) => {
+                self.add_radial_gradient(
+                    clip_and_scroll,
+                    &prim_info,
+                    info.gradient.start_center,
+                    info.gradient.start_radius,
+                    info.gradient.end_center,
+                    info.gradient.end_radius,
+                    info.gradient.ratio_xy,
+                    item.gradient_stops(),
+                    info.gradient.extend_mode,
+                    info.tile_size,
+                    info.tile_spacing,
+                );
+            }
+            SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
+                let bounds = box_shadow_info
+                    .box_bounds
+                    .translate(&reference_frame_relative_offset);
+                let mut prim_info = prim_info.clone();
+                prim_info.rect = bounds;
+                self.add_box_shadow(
+                    pipeline_id,
+                    clip_and_scroll,
+                    &prim_info,
+                    &box_shadow_info.offset,
+                    &box_shadow_info.color,
+                    box_shadow_info.blur_radius,
+                    box_shadow_info.spread_radius,
+                    box_shadow_info.border_radius,
+                    box_shadow_info.clip_mode,
+                );
+            }
+            SpecificDisplayItem::Border(ref info) => {
+                self.add_border(
+                    clip_and_scroll,
+                    &prim_info,
+                    info,
+                    item.gradient_stops(),
+                    item.display_list().get(item.gradient_stops()).count(),
+                );
+            }
+            SpecificDisplayItem::PushStackingContext(ref info) => {
+                let mut subtraversal = item.sub_iter();
+                self.flatten_stacking_context(
+                    &mut subtraversal,
+                    pipeline_id,
+                    unreplaced_scroll_id,
+                    clip_and_scroll_ids.scroll_node_id,
+                    reference_frame_relative_offset,
+                    &item.rect(),
+                    &info.stacking_context,
+                    item.filters(),
+                    prim_info.is_backface_visible,
+                );
+                return Some(subtraversal);
+            }
+            SpecificDisplayItem::Iframe(ref info) => {
+                self.flatten_iframe(
+                    &item,
+                    info,
+                    &clip_and_scroll_ids,
+                    &reference_frame_relative_offset
+                );
+            }
+            SpecificDisplayItem::Clip(ref info) => {
+                let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
+                let clip_region = ClipRegion::create_for_clip_node(
+                    *item.local_clip().clip_rect(),
+                    complex_clips,
+                    info.image_mask,
+                    &reference_frame_relative_offset,
+                );
+                self.add_clip_node(info.id, clip_and_scroll_ids.scroll_node_id, clip_region);
+            }
+            SpecificDisplayItem::ClipChain(ref info) => {
+                let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items())
+                                .iter()
+                                .map(|id| self.id_to_index_mapper.get_node_index(*id))
+                                .collect();
+                let parent = info.parent.map(|id|
+                     self.id_to_index_mapper.get_clip_chain_index(&ClipId::ClipChain(id))
+                );
+                let clip_chain_index =
+                    self.clip_scroll_tree.add_clip_chain_descriptor(parent, items);
+                self.id_to_index_mapper.add_clip_chain(ClipId::ClipChain(info.id), clip_chain_index);
+            },
+            SpecificDisplayItem::ScrollFrame(ref info) => {
+                self.flatten_scroll_frame(
+                    &item,
+                    info,
+                    pipeline_id,
+                    &clip_and_scroll_ids,
+                    &reference_frame_relative_offset
+                );
+            }
+            SpecificDisplayItem::StickyFrame(ref info) => {
+                self.flatten_sticky_frame(
+                    &item,
+                    info,
+                    &clip_and_scroll,
+                    &clip_and_scroll_ids.scroll_node_id,
+                    &reference_frame_relative_offset
+                );
+            }
+
+            // Do nothing; these are dummy items for the display list parser
+            SpecificDisplayItem::SetGradientStops => {}
+
+            SpecificDisplayItem::PopStackingContext => {
+                unreachable!("Should have returned in parent method.")
+            }
+            SpecificDisplayItem::PushShadow(shadow) => {
+                let mut prim_info = prim_info.clone();
+                prim_info.rect = LayerRect::zero();
+                self
+                    .push_shadow(shadow, clip_and_scroll, &prim_info);
+            }
+            SpecificDisplayItem::PopAllShadows => {
+                self.pop_all_shadows();
+            }
+        }
+        None
+    }
+
+    /// Decomposes an image display item that is repeated into an image per individual repetition.
+    /// We need to do this when we are unable to perform the repetition in the shader,
+    /// for example if the image is tiled.
+    ///
+    /// In all of the "decompose" methods below, we independently handle horizontal and vertical
+    /// decomposition. This lets us generate the minimum amount of primitives by, for  example,
+    /// decompositing the repetition horizontally while repeating vertically in the shader (for
+    /// an image where the width is too bug but the height is not).
+    ///
+    /// decompose_image and decompose_image_row handle image repetitions while decompose_tiled_image
+    /// takes care of the decomposition required by the internal tiling of the image.
+    fn decompose_image(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        prim_info: &LayerPrimitiveInfo,
+        info: &ImageDisplayItem,
+        image_size: DeviceUintSize,
+        tile_size: u32,
+    ) {
+        let no_vertical_tiling = image_size.height <= tile_size;
+        let no_vertical_spacing = info.tile_spacing.height == 0.0;
+        let item_rect = prim_info.rect;
+        if no_vertical_tiling && no_vertical_spacing {
+            self.decompose_image_row(
+                clip_and_scroll,
+                prim_info,
+                info,
+                image_size,
+                tile_size,
+            );
+            return;
+        }
+
+        // Decompose each vertical repetition into rows.
+        let layout_stride = info.stretch_size.height + info.tile_spacing.height;
+        let num_repetitions = (item_rect.size.height / layout_stride).ceil() as u32;
+        for i in 0 .. num_repetitions {
+            if let Some(row_rect) = rect(
+                item_rect.origin.x,
+                item_rect.origin.y + (i as f32) * layout_stride,
+                item_rect.size.width,
+                info.stretch_size.height,
+            ).intersection(&item_rect)
+            {
+                let mut prim_info = prim_info.clone();
+                prim_info.rect = row_rect;
+                self.decompose_image_row(
+                    clip_and_scroll,
+                    &prim_info,
+                    info,
+                    image_size,
+                    tile_size,
+                );
+            }
+        }
+    }
+
+    fn decompose_image_row(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        prim_info: &LayerPrimitiveInfo,
+        info: &ImageDisplayItem,
+        image_size: DeviceUintSize,
+        tile_size: u32,
+    ) {
+        let no_horizontal_tiling = image_size.width <= tile_size;
+        let no_horizontal_spacing = info.tile_spacing.width == 0.0;
+        if no_horizontal_tiling && no_horizontal_spacing {
+            self.decompose_tiled_image(
+                clip_and_scroll,
+                prim_info,
+                info,
+                image_size,
+                tile_size,
+            );
+            return;
+        }
+
+        // Decompose each horizontal repetition.
+        let item_rect = prim_info.rect;
+        let layout_stride = info.stretch_size.width + info.tile_spacing.width;
+        let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32;
+        for i in 0 .. num_repetitions {
+            if let Some(decomposed_rect) = rect(
+                item_rect.origin.x + (i as f32) * layout_stride,
+                item_rect.origin.y,
+                info.stretch_size.width,
+                item_rect.size.height,
+            ).intersection(&item_rect)
+            {
+                let mut prim_info = prim_info.clone();
+                prim_info.rect = decomposed_rect;
+                self.decompose_tiled_image(
+                    clip_and_scroll,
+                    &prim_info,
+                    info,
+                    image_size,
+                    tile_size,
+                );
+            }
+        }
+    }
+
+    fn decompose_tiled_image(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        prim_info: &LayerPrimitiveInfo,
+        info: &ImageDisplayItem,
+        image_size: DeviceUintSize,
+        tile_size: u32,
+    ) {
+        // The image resource is tiled. We have to generate an image primitive
+        // for each tile.
+        // We need to do this because the image is broken up into smaller tiles in the texture
+        // cache and the image shader is not able to work with this type of sparse representation.
+
+        // The tiling logic works as follows:
+        //
+        //  ###################-+  -+
+        //  #    |    |    |//# |   | image size
+        //  #    |    |    |//# |   |
+        //  #----+----+----+--#-+   |  -+
+        //  #    |    |    |//# |   |   | regular tile size
+        //  #    |    |    |//# |   |   |
+        //  #----+----+----+--#-+   |  -+-+
+        //  #////|////|////|//# |   |     | "leftover" height
+        //  ################### |  -+  ---+
+        //  #----+----+----+----+
+        //
+        // In the ascii diagram above, a large image is plit into tiles of almost regular size.
+        // The tiles on the right and bottom edges (hatched in the diagram) are smaller than
+        // the regular tiles and are handled separately in the code see leftover_width/height.
+        // each generated image primitive corresponds to a tile in the texture cache, with the
+        // assumption that the smaller tiles with leftover sizes are sized to fit their own
+        // irregular size in the texture cache.
+        //
+        // For the case where we don't tile along an axis, we can still perform the repetition in
+        // the shader (for this particular axis), and it is worth special-casing for this to avoid
+        // generating many primitives.
+        // This can happen with very tall and thin images used as a repeating background.
+        // Apparently web authors do that...
+
+        let item_rect = prim_info.rect;
+        let needs_repeat_x = info.stretch_size.width < item_rect.size.width;
+        let needs_repeat_y = info.stretch_size.height < item_rect.size.height;
+
+        let tiled_in_x = image_size.width > tile_size;
+        let tiled_in_y = image_size.height > tile_size;
+
+        // If we don't actually tile in this dimension, repeating can be done in the shader.
+        let shader_repeat_x = needs_repeat_x && !tiled_in_x;
+        let shader_repeat_y = needs_repeat_y && !tiled_in_y;
+
+        let tile_size_f32 = tile_size as f32;
+
+        // Note: this rounds down so it excludes the partially filled tiles on the right and
+        // bottom edges (we handle them separately below).
+        let num_tiles_x = (image_size.width / tile_size) as u16;
+        let num_tiles_y = (image_size.height / tile_size) as u16;
+
+        // Ratio between (image space) tile size and image size.
+        let img_dw = tile_size_f32 / (image_size.width as f32);
+        let img_dh = tile_size_f32 / (image_size.height as f32);
+
+        // Strected size of the tile in layout space.
+        let stretched_tile_size = LayerSize::new(
+            img_dw * info.stretch_size.width,
+            img_dh * info.stretch_size.height,
+        );
+
+        // The size in pixels of the tiles on the right and bottom edges, smaller
+        // than the regular tile size if the image is not a multiple of the tile size.
+        // Zero means the image size is a multiple of the tile size.
+        let leftover =
+            DeviceUintSize::new(image_size.width % tile_size, image_size.height % tile_size);
+
+        for ty in 0 .. num_tiles_y {
+            for tx in 0 .. num_tiles_x {
+                self.add_tile_primitive(
+                    clip_and_scroll,
+                    prim_info,
+                    info,
+                    TileOffset::new(tx, ty),
+                    stretched_tile_size,
+                    1.0,
+                    1.0,
+                    shader_repeat_x,
+                    shader_repeat_y,
+                );
+            }
+            if leftover.width != 0 {
+                // Tiles on the right edge that are smaller than the tile size.
+                self.add_tile_primitive(
+                    clip_and_scroll,
+                    prim_info,
+                    info,
+                    TileOffset::new(num_tiles_x, ty),
+                    stretched_tile_size,
+                    (leftover.width as f32) / tile_size_f32,
+                    1.0,
+                    shader_repeat_x,
+                    shader_repeat_y,
+                );
+            }
+        }
+
+        if leftover.height != 0 {
+            for tx in 0 .. num_tiles_x {
+                // Tiles on the bottom edge that are smaller than the tile size.
+                self.add_tile_primitive(
+                    clip_and_scroll,
+                    prim_info,
+                    info,
+                    TileOffset::new(tx, num_tiles_y),
+                    stretched_tile_size,
+                    1.0,
+                    (leftover.height as f32) / tile_size_f32,
+                    shader_repeat_x,
+                    shader_repeat_y,
+                );
+            }
+
+            if leftover.width != 0 {
+                // Finally, the bottom-right tile with a "leftover" size.
+                self.add_tile_primitive(
+                    clip_and_scroll,
+                    prim_info,
+                    info,
+                    TileOffset::new(num_tiles_x, num_tiles_y),
+                    stretched_tile_size,
+                    (leftover.width as f32) / tile_size_f32,
+                    (leftover.height as f32) / tile_size_f32,
+                    shader_repeat_x,
+                    shader_repeat_y,
+                );
+            }
+        }
+    }
+
+    fn add_tile_primitive(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        prim_info: &LayerPrimitiveInfo,
+        info: &ImageDisplayItem,
+        tile_offset: TileOffset,
+        stretched_tile_size: LayerSize,
+        tile_ratio_width: f32,
+        tile_ratio_height: f32,
+        shader_repeat_x: bool,
+        shader_repeat_y: bool,
+    ) {
+        // If the the image is tiled along a given axis, we can't have the shader compute
+        // the image repetition pattern. In this case we base the primitive's rectangle size
+        // on the stretched tile size which effectively cancels the repetion (and repetition
+        // has to be emulated by generating more primitives).
+        // If the image is not tiled along this axis, we can perform the repetition in the
+        // shader. in this case we use the item's size in the primitive (on that particular
+        // axis).
+        // See the shader_repeat_x/y code below.
+
+        let stretched_size = LayerSize::new(
+            stretched_tile_size.width * tile_ratio_width,
+            stretched_tile_size.height * tile_ratio_height,
+        );
+
+        let mut prim_rect = LayerRect::new(
+            prim_info.rect.origin +
+                LayerVector2D::new(
+                    tile_offset.x as f32 * stretched_tile_size.width,
+                    tile_offset.y as f32 * stretched_tile_size.height,
+                ),
+            stretched_size,
+        );
+
+        if shader_repeat_x {
+            assert_eq!(tile_offset.x, 0);
+            prim_rect.size.width = prim_info.rect.size.width;
+        }
+
+        if shader_repeat_y {
+            assert_eq!(tile_offset.y, 0);
+            prim_rect.size.height = prim_info.rect.size.height;
+        }
+
+        // Fix up the primitive's rect if it overflows the original item rect.
+        if let Some(prim_rect) = prim_rect.intersection(&prim_info.rect) {
+            let mut prim_info = prim_info.clone();
+            prim_info.rect = prim_rect;
+            self.add_image(
+                clip_and_scroll,
+                &prim_info,
+                stretched_size,
+                info.tile_spacing,
+                None,
+                info.image_key,
+                info.image_rendering,
+                info.alpha_type,
+                Some(tile_offset),
+            );
+        }
+    }
+
+    /// Create a primitive and add it to the prim store. This method doesn't
+    /// add the primitive to the draw list, so can be used for creating
+    /// sub-primitives.
+    pub fn create_primitive(
+        &mut self,
+        info: &LayerPrimitiveInfo,
+        mut clip_sources: Vec<ClipSource>,
+        container: PrimitiveContainer,
+    ) -> PrimitiveIndex {
+        if let &LocalClip::RoundedRect(main, region) = &info.local_clip {
+            clip_sources.push(ClipSource::Rectangle(main));
+
+            clip_sources.push(ClipSource::new_rounded_rect(
+                region.rect,
+                region.radii,
+                region.mode,
+            ));
+        }
+
+        let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
+
+        let clip_sources = self.clip_store.insert(ClipSources::new(clip_sources));
+        let prim_index = self.prim_store.add_primitive(
+            &info.rect,
+            &info.local_clip.clip_rect(),
+            info.is_backface_visible && stacking_context.is_backface_visible,
+            clip_sources,
+            info.tag,
+            container,
+        );
+
+        prim_index
+    }
+
+    pub fn add_primitive_to_hit_testing_list(
+        &mut self,
+        info: &LayerPrimitiveInfo,
+        clip_and_scroll: ScrollNodeAndClipChain
+    ) {
+        let tag = match info.tag {
+            Some(tag) => tag,
+            None => return,
+        };
+
+        let new_item = HitTestingItem::new(tag, info);
+        match self.hit_testing_runs.last_mut() {
+            Some(&mut HitTestingRun(ref mut items, prev_clip_and_scroll))
+                if prev_clip_and_scroll == clip_and_scroll => {
+                items.push(new_item);
+                return;
+            }
+            _ => {}
+        }
+
+        self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
+    }
+
+    /// Add an already created primitive to the draw lists.
+    pub fn add_primitive_to_draw_list(
+        &mut self,
+        prim_index: PrimitiveIndex,
+        clip_and_scroll: ScrollNodeAndClipChain,
+    ) {
+        // Add primitive to the top-most Picture on the stack.
+        // TODO(gw): Let's consider removing the extra indirection
+        //           needed to get a specific primitive index...
+        let pic_prim_index = self.picture_stack.last().unwrap();
+        let metadata = &self.prim_store.cpu_metadata[pic_prim_index.0];
+        let pic = &mut self.prim_store.cpu_pictures[metadata.cpu_prim_index.0];
+        pic.add_primitive(
+            prim_index,
+            clip_and_scroll
+        );
+    }
+
+    /// Convenience interface that creates a primitive entry and adds it
+    /// to the draw list.
+    pub fn add_primitive(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        clip_sources: Vec<ClipSource>,
+        container: PrimitiveContainer,
+    ) -> PrimitiveIndex {
+        self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+        let prim_index = self.create_primitive(info, clip_sources, container);
+
+        self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+        prim_index
+    }
+
+    pub fn push_stacking_context(
+        &mut self,
+        pipeline_id: PipelineId,
+        composite_ops: CompositeOps,
+        transform_style: TransformStyle,
+        is_backface_visible: bool,
+        is_pipeline_root: bool,
+        clip_and_scroll: ScrollNodeAndClipChain,
+    ) {
+        // Construct the necessary set of Picture primitives
+        // to draw this stacking context.
+        let current_reference_frame_index = self.current_reference_frame_index();
+
+        // An arbitrary large clip rect. For now, we don't
+        // specify a clip specific to the stacking context.
+        // However, now that they are represented as Picture
+        // primitives, we can apply any kind of clip mask
+        // to them, as for a normal primitive. This is needed
+        // to correctly handle some CSS cases (see #1957).
+        let max_clip = LayerRect::max_rect();
+
+        // If there is no root picture, create one for the main framebuffer.
+        if self.sc_stack.is_empty() {
+            // Should be no pictures at all if the stack is empty...
+            debug_assert!(self.prim_store.cpu_pictures.is_empty());
+            debug_assert_eq!(transform_style, TransformStyle::Flat);
+
+            // This picture stores primitive runs for items on the
+            // main framebuffer.
+            let pic = PicturePrimitive::new_image(
+                None,
+                false,
+                pipeline_id,
+                current_reference_frame_index,
+                None,
+            );
+
+            // No clip sources needed for the main framebuffer.
+            let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+            // Add root picture primitive. The provided layer rect
+            // is zero, because we don't yet know the size of the
+            // picture. Instead, this is calculated recursively
+            // when we cull primitives.
+            let prim_index = self.prim_store.add_primitive(
+                &LayerRect::zero(),
+                &max_clip,
+                true,
+                clip_sources,
+                None,
+                PrimitiveContainer::Picture(pic),
+            );
+
+            self.picture_stack.push(prim_index);
+        } else if composite_ops.mix_blend_mode.is_some() && self.sc_stack.len() > 2 {
+            // If we have a mix-blend-mode, and we aren't the primary framebuffer,
+            // the stacking context needs to be isolated to blend correctly as per
+            // the CSS spec.
+            // TODO(gw): The way we detect not being the primary framebuffer (len > 2)
+            //           is hacky and depends on how we create a root stacking context
+            //           during flattening.
+            let current_pic_prim_index = self.picture_stack.last().unwrap();
+            let pic_cpu_prim_index = self.prim_store.cpu_metadata[current_pic_prim_index.0].cpu_prim_index;
+            let parent_pic = &mut self.prim_store.cpu_pictures[pic_cpu_prim_index.0];
+
+            match parent_pic.kind {
+                PictureKind::Image { ref mut composite_mode, .. } => {
+                    // If not already isolated for some other reason,
+                    // make this picture as isolated.
+                    if composite_mode.is_none() {
+                        *composite_mode = Some(PictureCompositeMode::Blit);
+                    }
+                }
+                PictureKind::TextShadow { .. } |
+                PictureKind::BoxShadow { .. } => {
+                    panic!("bug: text/box pictures invalid here");
+                }
+            }
+        }
+
+        // Get the transform-style of the parent stacking context,
+        // which determines if we *might* need to draw this on
+        // an intermediate surface for plane splitting purposes.
+        let parent_transform_style = match self.sc_stack.last() {
+            Some(sc) => sc.transform_style,
+            None => TransformStyle::Flat,
+        };
+
+        // If this is preserve-3d *or* the parent is, then this stacking
+        // context is participating in the 3d rendering context. In that
+        // case, hoist the picture up to the 3d rendering context
+        // container, so that it's rendered as a sibling with other
+        // elements in this context.
+        let participating_in_3d_context =
+            composite_ops.count() == 0 &&
+            (parent_transform_style == TransformStyle::Preserve3D ||
+             transform_style == TransformStyle::Preserve3D);
+
+        // If this is participating in a 3d context *and* the
+        // parent was not a 3d context, then this must be the
+        // element that establishes a new 3d context.
+        let establishes_3d_context =
+            participating_in_3d_context &&
+            parent_transform_style == TransformStyle::Flat;
+
+        let rendering_context_3d_prim_index = if establishes_3d_context {
+            // If establishing a 3d context, we need to add a picture
+            // that will be the container for all the planes and any
+            // un-transformed content.
+            let container = PicturePrimitive::new_image(
+                None,
+                false,
+                pipeline_id,
+                current_reference_frame_index,
+                None,
+            );
+
+            let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+            let prim_index = self.prim_store.add_primitive(
+                &LayerRect::zero(),
+                &max_clip,
+                is_backface_visible,
+                clip_sources,
+                None,
+                PrimitiveContainer::Picture(container),
+            );
+
+            let parent_pic_prim_index = *self.picture_stack.last().unwrap();
+            let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+            let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+            pic.add_primitive(
+                prim_index,
+                clip_and_scroll,
+            );
+
+            self.picture_stack.push(prim_index);
+
+            Some(prim_index)
+        } else {
+            None
+        };
+
+        let mut parent_pic_prim_index = if !establishes_3d_context && participating_in_3d_context {
+            // If we're in a 3D context, we will parent the picture
+            // to the first stacking context we find that is a
+            // 3D rendering context container. This follows the spec
+            // by hoisting these items out into the same 3D context
+            // for plane splitting.
+            self.sc_stack
+                .iter()
+                .rev()
+                .find(|sc| sc.rendering_context_3d_prim_index.is_some())
+                .map(|sc| sc.rendering_context_3d_prim_index.unwrap())
+                .unwrap()
+        } else {
+            *self.picture_stack.last().unwrap()
+        };
+
+        // For each filter, create a new image with that composite mode.
+        for filter in composite_ops.filters.iter().rev() {
+            let src_prim = PicturePrimitive::new_image(
+                Some(PictureCompositeMode::Filter(*filter)),
+                false,
+                pipeline_id,
+                current_reference_frame_index,
+                None,
+            );
+            let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+            let src_prim_index = self.prim_store.add_primitive(
+                &LayerRect::zero(),
+                &max_clip,
+                is_backface_visible,
+                src_clip_sources,
+                None,
+                PrimitiveContainer::Picture(src_prim),
+            );
+
+            let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+            parent_pic_prim_index = src_prim_index;
+            let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+            pic.add_primitive(
+                src_prim_index,
+                clip_and_scroll,
+            );
+
+            self.picture_stack.push(src_prim_index);
+        }
+
+        // Same for mix-blend-mode.
+        if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
+            let src_prim = PicturePrimitive::new_image(
+                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
+                false,
+                pipeline_id,
+                current_reference_frame_index,
+                None,
+            );
+            let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+
+            let src_prim_index = self.prim_store.add_primitive(
+                &LayerRect::zero(),
+                &max_clip,
+                is_backface_visible,
+                src_clip_sources,
+                None,
+                PrimitiveContainer::Picture(src_prim),
+            );
+
+            let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+            parent_pic_prim_index = src_prim_index;
+            let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+            pic.add_primitive(
+                src_prim_index,
+                clip_and_scroll,
+            );
+
+            self.picture_stack.push(src_prim_index);
+        }
+
+        // By default, this picture will be collapsed into
+        // the owning target.
+        let mut composite_mode = None;
+        let mut frame_output_pipeline_id = None;
+
+        // If this stacking context if the root of a pipeline, and the caller
+        // has requested it as an output frame, create a render task to isolate it.
+        if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
+            composite_mode = Some(PictureCompositeMode::Blit);
+            frame_output_pipeline_id = Some(pipeline_id);
+        }
+
+        if participating_in_3d_context {
+            // TODO(gw): For now, as soon as this picture is in
+            //           a 3D context, we draw it to an intermediate
+            //           surface and apply plane splitting. However,
+            //           there is a large optimization opportunity here.
+            //           During culling, we can check if there is actually
+            //           perspective present, and skip the plane splitting
+            //           completely when that is not the case.
+            composite_mode = Some(PictureCompositeMode::Blit);
+        }
+
+        // Add picture for this actual stacking context contents to render into.
+        let sc_prim = PicturePrimitive::new_image(
+            composite_mode,
+            participating_in_3d_context,
+            pipeline_id,
+            current_reference_frame_index,
+            frame_output_pipeline_id,
+        );
+
+        let sc_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
+        let sc_prim_index = self.prim_store.add_primitive(
+            &LayerRect::zero(),
+            &max_clip,
+            is_backface_visible,
+            sc_clip_sources,
+            None,
+            PrimitiveContainer::Picture(sc_prim),
+        );
+
+        let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
+        let sc_pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
+        sc_pic.add_primitive(
+            sc_prim_index,
+            clip_and_scroll,
+        );
+
+        // Add this as the top-most picture for primitives to be added to.
+        self.picture_stack.push(sc_prim_index);
+
+        // TODO(gw): This is super conservative. We can expand on this a lot
+        //           once all the picture code is in place and landed.
+        let allow_subpixel_aa = composite_ops.count() == 0 &&
+                                transform_style == TransformStyle::Flat;
+
+        // Push the SC onto the stack, so we know how to handle things in
+        // pop_stacking_context.
+        let sc = FlattenedStackingContext {
+            composite_ops,
+            is_backface_visible,
+            pipeline_id,
+            allow_subpixel_aa,
+            transform_style,
+            rendering_context_3d_prim_index,
+        };
+
+        self.sc_stack.push(sc);
+    }
+
+    pub fn pop_stacking_context(&mut self) {
+        let sc = self.sc_stack.pop().unwrap();
+
+        // Always pop at least the main picture for this stacking context.
+        let mut pop_count = 1;
+
+        // Remove the picture for any filter/mix-blend-mode effects.
+        pop_count += sc.composite_ops.count();
+
+        // Remove the 3d context container if created
+        if sc.rendering_context_3d_prim_index.is_some() {
+            pop_count += 1;
+        }
+
+        for _ in 0 .. pop_count {
+            self.picture_stack.pop().expect("bug: mismatched picture stack");
+        }
+
+        // By the time the stacking context stack is empty, we should
+        // also have cleared the picture stack.
+        if self.sc_stack.is_empty() {
+            self.picture_stack.pop().expect("bug: picture stack invalid");
+            debug_assert!(self.picture_stack.is_empty());
+        }
+
+        assert!(
+            self.shadow_prim_stack.is_empty(),
+            "Found unpopped text shadows when popping stacking context!"
+        );
+    }
+
+    pub fn push_reference_frame(
+        &mut self,
+        reference_frame_id: ClipId,
+        parent_id: Option<ClipId>,
+        pipeline_id: PipelineId,
+        rect: &LayerRect,
+        source_transform: Option<PropertyBinding<LayoutTransform>>,
+        source_perspective: Option<LayoutTransform>,
+        origin_in_parent_reference_frame: LayerVector2D,
+    ) -> ClipScrollNodeIndex {
+        let index = self.id_to_index_mapper.get_node_index(reference_frame_id);
+        let node = ClipScrollNode::new_reference_frame(
+            parent_id.map(|id| self.id_to_index_mapper.get_node_index(id)),
+            rect,
+            source_transform,
+            source_perspective,
+            origin_in_parent_reference_frame,
+            pipeline_id,
+        );
+        self.clip_scroll_tree.add_node(node, index);
+        self.reference_frame_stack.push((reference_frame_id, index));
+
+        match parent_id {
+            Some(ref parent_id) =>
+                self.id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
+            _ => self.id_to_index_mapper.add_clip_chain(reference_frame_id, ClipChainIndex(0)),
+        }
+        index
+    }
+
+    pub fn current_reference_frame_index(&self) -> ClipScrollNodeIndex {
+        self.reference_frame_stack.last().unwrap().1
+    }
+
+    pub fn current_reference_frame_id(&self) -> ClipId{
+        self.reference_frame_stack.last().unwrap().0
+    }
+
+    pub fn setup_viewport_offset(
+        &mut self,
+        inner_rect: DeviceUintRect,
+        device_pixel_scale: DevicePixelScale,
+    ) {
+        let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
+        let root_id = self.clip_scroll_tree.root_reference_frame_index();
+        let root_node = &mut self.clip_scroll_tree.nodes[root_id.0];
+        if let NodeType::ReferenceFrame(ref mut info) = root_node.node_type {
+            info.resolved_transform =
+                LayerVector2D::new(viewport_offset.x, viewport_offset.y).into();
+        }
+    }
+
+    pub fn push_root(
+        &mut self,
+        pipeline_id: PipelineId,
+        viewport_size: &LayerSize,
+        content_size: &LayerSize,
+    ) {
+        let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
+
+        self.push_reference_frame(
+            ClipId::root_reference_frame(pipeline_id),
+            None,
+            pipeline_id,
+            &viewport_rect,
+            None,
+            None,
+            LayerVector2D::zero(),
+        );
+
+        self.add_scroll_frame(
+            ClipId::root_scroll_node(pipeline_id),
+            ClipId::root_reference_frame(pipeline_id),
+            Some(ExternalScrollId(0, pipeline_id)),
+            pipeline_id,
+            &viewport_rect,
+            content_size,
+            ScrollSensitivity::ScriptAndInputEvents,
+        );
+    }
+
+    pub fn add_clip_node(
+        &mut self,
+        new_node_id: ClipId,
+        parent_id: ClipId,
+        clip_region: ClipRegion,
+    ) -> ClipScrollNodeIndex {
+        let clip_rect = clip_region.main;
+        let clip_sources = ClipSources::from(clip_region);
+
+        debug_assert!(clip_sources.has_clips());
+        let handle = self.clip_store.insert(clip_sources);
+
+        let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
+        let clip_chain_index = self.clip_scroll_tree.add_clip_node(
+            node_index,
+            self.id_to_index_mapper.get_node_index(parent_id),
+            handle,
+            clip_rect,
+            new_node_id.pipeline_id(),
+        );
+        self.id_to_index_mapper.add_clip_chain(new_node_id, clip_chain_index);
+        node_index
+    }
+
+    pub fn add_scroll_frame(
+        &mut self,
+        new_node_id: ClipId,
+        parent_id: ClipId,
+        external_id: Option<ExternalScrollId>,
+        pipeline_id: PipelineId,
+        frame_rect: &LayerRect,
+        content_size: &LayerSize,
+        scroll_sensitivity: ScrollSensitivity,
+    ) -> ClipScrollNodeIndex {
+        let node_index = self.id_to_index_mapper.get_node_index(new_node_id);
+        let node = ClipScrollNode::new_scroll_frame(
+            pipeline_id,
+            self.id_to_index_mapper.get_node_index(parent_id),
+            external_id,
+            frame_rect,
+            content_size,
+            scroll_sensitivity,
+        );
+
+        self.clip_scroll_tree.add_node(node, node_index);
+        self.id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
+        node_index
+    }
+
+    pub fn pop_reference_frame(&mut self) {
+        self.reference_frame_stack.pop();
+    }
+
+    pub fn push_shadow(
+        &mut self,
+        shadow: Shadow,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+    ) {
+        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
+        let prim = PicturePrimitive::new_text_shadow(shadow, pipeline_id);
+
+        // Create an empty shadow primitive. Insert it into
+        // the draw lists immediately so that it will be drawn
+        // before any visual text elements that are added as
+        // part of this shadow context.
+        let prim_index = self.create_primitive(
+            info,
+            Vec::new(),
+            PrimitiveContainer::Picture(prim),
+        );
+
+        let pending = vec![(prim_index, clip_and_scroll)];
+        self.shadow_prim_stack.push((prim_index, pending));
+    }
+
+    pub fn pop_all_shadows(&mut self) {
+        assert!(self.shadow_prim_stack.len() > 0, "popped shadows, but none were present");
+
+        // Borrowcheck dance
+        let mut shadows = mem::replace(&mut self.shadow_prim_stack, Vec::new());
+        for (_, pending_primitives) in shadows.drain(..) {
+            // Push any fast-path shadows now
+            for (prim_index, clip_and_scroll) in pending_primitives {
+                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+            }
+        }
+
+        let mut pending_primitives = mem::replace(&mut self.pending_shadow_contents, Vec::new());
+        for (prim_index, clip_and_scroll, info) in pending_primitives.drain(..) {
+            self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
+            self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+        }
+
+        mem::replace(&mut self.pending_shadow_contents, pending_primitives);
+        mem::replace(&mut self.shadow_prim_stack, shadows);
+    }
+
+    pub fn add_solid_rectangle(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        color: ColorF,
+        segments: Option<BrushSegmentDescriptor>,
+    ) {
+        if color.a == 0.0 {
+            // Don't add transparent rectangles to the draw list, but do consider them for hit
+            // testing. This allows specifying invisible hit testing areas.
+            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+            return;
+        }
+
+        let prim = BrushPrimitive::new(
+            BrushKind::Solid {
+                color,
+            },
+            segments,
+        );
+
+        self.add_primitive(
+            clip_and_scroll,
+            info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
+    }
+
+    pub fn add_clear_rectangle(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+    ) {
+        let prim = BrushPrimitive::new(
+            BrushKind::Clear,
+            None,
+        );
+
+        self.add_primitive(
+            clip_and_scroll,
+            info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
+    }
+
+    pub fn add_scroll_bar(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        color: ColorF,
+        scrollbar_info: ScrollbarInfo,
+    ) {
+        if color.a == 0.0 {
+            return;
+        }
+
+        let prim = BrushPrimitive::new(
+            BrushKind::Solid {
+                color,
+            },
+            None,
+        );
+
+        let prim_index = self.add_primitive(
+            clip_and_scroll,
+            info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
+
+        self.scrollbar_prims.push(ScrollbarPrimitive {
+            prim_index,
+            scroll_frame_index: scrollbar_info.0,
+            frame_rect: scrollbar_info.1,
+        });
+    }
+
+    pub fn add_line(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        wavy_line_thickness: f32,
+        orientation: LineOrientation,
+        line_color: &ColorF,
+        style: LineStyle,
+    ) {
+        let line = BrushPrimitive::new(
+            BrushKind::Line {
+                wavy_line_thickness,
+                color: line_color.premultiplied(),
+                style,
+                orientation,
+            },
+            None,
+        );
+
+        let mut fast_shadow_prims = Vec::new();
+        for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
+            let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
+            let picture = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
+            match picture.kind {
+                PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
+                    fast_shadow_prims.push((idx, offset, color));
+                }
+                _ => {}
+            }
+        }
+
+        for (idx, shadow_offset, shadow_color) in fast_shadow_prims {
+            let line = BrushPrimitive::new(
+                BrushKind::Line {
+                    wavy_line_thickness,
+                    color: shadow_color.premultiplied(),
+                    style,
+                    orientation,
+                },
+                None,
+            );
+            let mut info = info.clone();
+            info.rect = info.rect.translate(&shadow_offset);
+            info.local_clip =
+              LocalClip::from(info.local_clip.clip_rect().translate(&shadow_offset));
+            let prim_index = self.create_primitive(
+                &info,
+                Vec::new(),
+                PrimitiveContainer::Brush(line),
+            );
+            self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
+        }
+
+        let prim_index = self.create_primitive(
+            &info,
+            Vec::new(),
+            PrimitiveContainer::Brush(line),
+        );
+
+        if line_color.a > 0.0 {
+            if self.shadow_prim_stack.is_empty() {
+                self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
+                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+            } else {
+                self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
+            }
+        }
+
+        for &(shadow_prim_index, _) in &self.shadow_prim_stack {
+            let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
+            debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Picture);
+            let picture =
+                &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
+
+            match picture.kind {
+                // Only run real blurs here (fast path zero blurs are handled above).
+                PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
+                    picture.add_primitive(
+                        prim_index,
+                        clip_and_scroll,
+                    );
+                }
+                _ => {}
+            }
+        }
+    }
+
+    pub fn add_border(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        border_item: &BorderDisplayItem,
+        gradient_stops: ItemRange<GradientStop>,
+        gradient_stops_count: usize,
+    ) {
+        let rect = info.rect;
+        let create_segments = |outset: SideOffsets2D<f32>| {
+            // Calculate the modified rect as specific by border-image-outset
+            let origin = LayerPoint::new(rect.origin.x - outset.left, rect.origin.y - outset.top);
+            let size = LayerSize::new(
+                rect.size.width + outset.left + outset.right,
+                rect.size.height + outset.top + outset.bottom,
+            );
+            let rect = LayerRect::new(origin, size);
+
+            let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
+            let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
+
+            let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
+            let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
+
+            let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
+            let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
+
+            let br_outer = LayerPoint::new(
+                rect.origin.x + rect.size.width,
+                rect.origin.y + rect.size.height,
+            );
+            let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
+
+            // Build the list of gradient segments
+            vec![
+                // Top left
+                LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
+                // Top right
+                LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
+                // Bottom right
+                LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
+                // Bottom left
+                LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
+                // Top
+                LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
+                // Bottom
+                LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
+                // Left
+                LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
+                // Right
+                LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
+            ]
+        };
+
+        match border_item.details {
+            BorderDetails::Image(ref border) => {
+                // Calculate the modified rect as specific by border-image-outset
+                let origin = LayerPoint::new(
+                    rect.origin.x - border.outset.left,
+                    rect.origin.y - border.outset.top,
+                );
+                let size = LayerSize::new(
+                    rect.size.width + border.outset.left + border.outset.right,
+                    rect.size.height + border.outset.top + border.outset.bottom,
+                );
+                let rect = LayerRect::new(origin, size);
+
+                // Calculate the local texel coords of the slices.
+                let px0 = 0.0;
+                let px1 = border.patch.slice.left as f32;
+                let px2 = border.patch.width as f32 - border.patch.slice.right as f32;
+                let px3 = border.patch.width as f32;
+
+                let py0 = 0.0;
+                let py1 = border.patch.slice.top as f32;
+                let py2 = border.patch.height as f32 - border.patch.slice.bottom as f32;
+                let py3 = border.patch.height as f32;
+
+                let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
+                let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
+
+                let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
+                let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
+
+                let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
+                let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
+
+                let br_outer = LayerPoint::new(
+                    rect.origin.x + rect.size.width,
+                    rect.origin.y + rect.size.height,
+                );
+                let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
+
+                fn add_segment(
+                    segments: &mut Vec<ImageBorderSegment>,
+                    rect: LayerRect,
+                    uv_rect: TexelRect,
+                    repeat_horizontal: RepeatMode,
+                    repeat_vertical: RepeatMode) {
+                    if uv_rect.uv1.x > uv_rect.uv0.x &&
+                       uv_rect.uv1.y > uv_rect.uv0.y {
+                        segments.push(ImageBorderSegment::new(
+                            rect,
+                            uv_rect,
+                            repeat_horizontal,
+                            repeat_vertical,
+                        ));
+                    }
+                }
+
+                // Build the list of image segments
+                let mut segments = vec![];
+
+                // Top left
+                add_segment(
+                    &mut segments,
+                    LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
+                    TexelRect::new(px0, py0, px1, py1),
+                    RepeatMode::Stretch,
+                    RepeatMode::Stretch
+                );
+                // Top right
+                add_segment(
+                    &mut segments,
+                    LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
+                    TexelRect::new(px2, py0, px3, py1),
+                    RepeatMode::Stretch,
+                    RepeatMode::Stretch
+                );
+                // Bottom right
+                add_segment(
+                    &mut segments,
+                    LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
+                    TexelRect::new(px2, py2, px3, py3),
+                    RepeatMode::Stretch,
+                    RepeatMode::Stretch
+                );
+                // Bottom left
+                add_segment(
+                    &mut segments,
+                    LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
+                    TexelRect::new(px0, py2, px1, py3),
+                    RepeatMode::Stretch,
+                    RepeatMode::Stretch
+                );
+
+                // Center
+                if border.fill {
+                    add_segment(
+                        &mut segments,
+                        LayerRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
+                        TexelRect::new(px1, py1, px2, py2),
+                        border.repeat_horizontal,
+                        border.repeat_vertical
+                    );
+                }
+
+                // Add edge segments.
+
+                // Top
+                add_segment(
+                    &mut segments,
+                    LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
+                    TexelRect::new(px1, py0, px2, py1),
+                    border.repeat_horizontal,
+                    RepeatMode::Stretch,
+                );
+                // Bottom
+                add_segment(
+                    &mut segments,
+                    LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
+                    TexelRect::new(px1, py2, px2, py3),
+                    border.repeat_horizontal,
+                    RepeatMode::Stretch,
+                );
+                // Left
+                add_segment(
+                    &mut segments,
+                    LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
+                    TexelRect::new(px0, py1, px1, py2),
+                    RepeatMode::Stretch,
+                    border.repeat_vertical,
+                );
+                // Right
+                add_segment(
+                    &mut segments,
+                    LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
+                    TexelRect::new(px2, py1, px3, py2),
+                    RepeatMode::Stretch,
+                    border.repeat_vertical,
+                );
+
+                for segment in segments {
+                    let mut info = info.clone();
+                    info.rect = segment.geom_rect;
+                    self.add_image(
+                        clip_and_scroll,
+                        &info,
+                        segment.stretch_size,
+                        segment.tile_spacing,
+                        Some(segment.sub_rect),
+                        border.image_key,
+                        ImageRendering::Auto,
+                        AlphaType::PremultipliedAlpha,
+                        None,
+                    );
+                }
+            }
+            BorderDetails::Normal(ref border) => {
+                self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
+            }
+            BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
+                let segment_rel = segment.origin - rect.origin;
+                let mut info = info.clone();
+                info.rect = segment;
+
+                self.add_gradient(
+                    clip_and_scroll,
+                    &info,
+                    border.gradient.start_point - segment_rel,
+                    border.gradient.end_point - segment_rel,
+                    gradient_stops,
+                    gradient_stops_count,
+                    border.gradient.extend_mode,
+                    segment.size,
+                    LayerSize::zero(),
+                );
+            },
+            BorderDetails::RadialGradient(ref border) => {
+                for segment in create_segments(border.outset) {
+                    let segment_rel = segment.origin - rect.origin;
+                    let mut info = info.clone();
+                    info.rect = segment;
+
+                    self.add_radial_gradient(
+                        clip_and_scroll,
+                        &info,
+                        border.gradient.start_center - segment_rel,
+                        border.gradient.start_radius,
+                        border.gradient.end_center - segment_rel,
+                        border.gradient.end_radius,
+                        border.gradient.ratio_xy,
+                        gradient_stops,
+                        border.gradient.extend_mode,
+                        segment.size,
+                        LayerSize::zero(),
+                    );
+                }
+            }
+        }
+    }
+
+    fn add_gradient_impl(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        start_point: LayerPoint,
+        end_point: LayerPoint,
+        stops: ItemRange<GradientStop>,
+        stops_count: usize,
+        extend_mode: ExtendMode,
+        gradient_index: CachedGradientIndex,
+    ) {
+        // Try to ensure that if the gradient is specified in reverse, then so long as the stops
+        // are also supplied in reverse that the rendered result will be equivalent. To do this,
+        // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
+        // just designate the reference orientation as start < end. Aligned gradient rendering
+        // manages to produce the same result regardless of orientation, so don't worry about
+        // reversing in that case.
+        let reverse_stops = start_point.x > end_point.x ||
+            (start_point.x == end_point.x && start_point.y > end_point.y);
+
+        // To get reftests exactly matching with reverse start/end
+        // points, it's necessary to reverse the gradient
+        // line in some cases.
+        let (sp, ep) = if reverse_stops {
+            (end_point, start_point)
+        } else {
+            (start_point, end_point)
+        };
+
+        let prim = BrushPrimitive::new(
+            BrushKind::LinearGradient {
+                stops_range: stops,
+                stops_count,
+                extend_mode,
+                reverse_stops,
+                start_point: sp,
+                end_point: ep,
+                gradient_index,
+            },
+            None,
+        );
+
+        let prim = PrimitiveContainer::Brush(prim);
+
+        self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
+    }
+
+    pub fn add_gradient(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        start_point: LayerPoint,
+        end_point: LayerPoint,
+        stops: ItemRange<GradientStop>,
+        stops_count: usize,
+        extend_mode: ExtendMode,
+        tile_size: LayerSize,
+        tile_spacing: LayerSize,
+    ) {
+        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
+        self.cached_gradients.push(CachedGradient::new());
+
+        let prim_infos = info.decompose(
+            tile_size,
+            tile_spacing,
+            64 * 64,
+        );
+
+        if prim_infos.is_empty() {
+            self.add_gradient_impl(
+                clip_and_scroll,
+                info,
+                start_point,
+                end_point,
+                stops,
+                stops_count,
+                extend_mode,
+                gradient_index,
+            );
+        } else {
+            for prim_info in prim_infos {
+                self.add_gradient_impl(
+                    clip_and_scroll,
+                    &prim_info,
+                    start_point,
+                    end_point,
+                    stops,
+                    stops_count,
+                    extend_mode,
+                    gradient_index,
+                );
+            }
+        }
+    }
+
+    fn add_radial_gradient_impl(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        start_center: LayerPoint,
+        start_radius: f32,
+        end_center: LayerPoint,
+        end_radius: f32,
+        ratio_xy: f32,
+        stops: ItemRange<GradientStop>,
+        extend_mode: ExtendMode,
+        gradient_index: CachedGradientIndex,
+    ) {
+        let prim = BrushPrimitive::new(
+            BrushKind::RadialGradient {
+                stops_range: stops,
+                extend_mode,
+                start_center,
+                end_center,
+                start_radius,
+                end_radius,
+                ratio_xy,
+                gradient_index,
+            },
+            None,
+        );
+
+        self.add_primitive(
+            clip_and_scroll,
+            info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
+    }
+
+    pub fn add_radial_gradient(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        start_center: LayerPoint,
+        start_radius: f32,
+        end_center: LayerPoint,
+        end_radius: f32,
+        ratio_xy: f32,
+        stops: ItemRange<GradientStop>,
+        extend_mode: ExtendMode,
+        tile_size: LayerSize,
+        tile_spacing: LayerSize,
+    ) {
+        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
+        self.cached_gradients.push(CachedGradient::new());
+
+        let prim_infos = info.decompose(
+            tile_size,
+            tile_spacing,
+            64 * 64,
+        );
+
+        if prim_infos.is_empty() {
+            self.add_radial_gradient_impl(
+                clip_and_scroll,
+                info,
+                start_center,
+                start_radius,
+                end_center,
+                end_radius,
+                ratio_xy,
+                stops,
+                extend_mode,
+                gradient_index,
+            );
+        } else {
+            for prim_info in prim_infos {
+                self.add_radial_gradient_impl(
+                    clip_and_scroll,
+                    &prim_info,
+                    start_center,
+                    start_radius,
+                    end_center,
+                    end_radius,
+                    ratio_xy,
+                    stops,
+                    extend_mode,
+                    gradient_index,
+                );
+            }
+        }
+    }
+
+    pub fn add_text(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        run_offset: LayoutVector2D,
+        info: &LayerPrimitiveInfo,
+        font_instance_key: &FontInstanceKey,
+        text_color: &ColorF,
+        glyph_range: ItemRange<GlyphInstance>,
+        glyph_count: usize,
+        glyph_options: Option<GlyphOptions>,
+    ) {
+        let prim = {
+            let instance_map = self.font_instances.read().unwrap();
+            let font_instance = match instance_map.get(&font_instance_key) {
+                Some(instance) => instance,
+                None => {
+                    warn!("Unknown font instance key");
+                    debug!("key={:?}", font_instance_key);
+                    return;
+                }
+            };
+
+            // Trivial early out checks
+            if font_instance.size.0 <= 0 {
+                return;
+            }
+
+            // Sanity check - anything with glyphs bigger than this
+            // is probably going to consume too much memory to render
+            // efficiently anyway. This is specifically to work around
+            // the font_advance.html reftest, which creates a very large
+            // font as a crash test - the rendering is also ignored
+            // by the azure renderer.
+            if font_instance.size >= Au::from_px(4096) {
+                return;
+            }
+
+            // TODO(gw): Use a proper algorithm to select
+            // whether this item should be rendered with
+            // subpixel AA!
+            let mut render_mode = self.config
+                .default_font_render_mode
+                .limit_by(font_instance.render_mode);
+            let mut flags = font_instance.flags;
+            if let Some(options) = glyph_options {
+                render_mode = render_mode.limit_by(options.render_mode);
+                flags |= options.flags;
+            }
+
+            // There are some conditions under which we can't use
+            // subpixel text rendering, even if enabled.
+            if render_mode == FontRenderMode::Subpixel {
+                // text on a picture that has filters
+                // (e.g. opacity) can't use sub-pixel.
+                // TODO(gw): It's possible we can relax this in
+                //           the future, if we modify the way
+                //           we handle subpixel blending.
+                if let Some(ref stacking_context) = self.sc_stack.last() {
+                    if !stacking_context.allow_subpixel_aa {
+                        render_mode = FontRenderMode::Alpha;
+                    }
+                }
+            }
+
+            let prim_font = FontInstance::new(
+                font_instance.font_key,
+                font_instance.size,
+                *text_color,
+                font_instance.bg_color,
+                render_mode,
+                font_instance.subpx_dir,
+                flags,
+                font_instance.platform_options,
+                font_instance.variations.clone(),
+            );
+            TextRunPrimitiveCpu {
+                font: prim_font,
+                glyph_range,
+                glyph_count,
+                glyph_gpu_blocks: Vec::new(),
+                glyph_keys: Vec::new(),
+                offset: run_offset,
+                shadow: false,
+            }
+        };
+
+        // Text shadows that have a blur radius of 0 need to be rendered as normal
+        // text elements to get pixel perfect results for reftests. It's also a big
+        // performance win to avoid blurs and render target allocations where
+        // possible. For any text shadows that have zero blur, create a normal text
+        // primitive with the shadow's color and offset. These need to be added
+        // *before* the visual text primitive in order to get the correct paint
+        // order. Store them in a Vec first to work around borrowck issues.
+        // TODO(gw): Refactor to avoid having to store them in a Vec first.
+        let mut fast_shadow_prims = Vec::new();
+        for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
+            let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
+            let picture_prim = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
+            match picture_prim.kind {
+                PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
+                    let mut text_prim = prim.clone();
+                    text_prim.font.color = color.into();
+                    text_prim.shadow = true;
+                    text_prim.offset += offset;
+                    fast_shadow_prims.push((idx, text_prim));
+                }
+                _ => {}
+            }
+        }
+
+        for (idx, text_prim) in fast_shadow_prims {
+            let rect = info.rect;
+            let mut info = info.clone();
+            info.rect = rect.translate(&text_prim.offset);
+            info.local_clip =
+              LocalClip::from(info.local_clip.clip_rect().translate(&text_prim.offset));
+            let prim_index = self.create_primitive(
+                &info,
+                Vec::new(),
+                PrimitiveContainer::TextRun(text_prim),
+            );
+            self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
+        }
+
+        // Create (and add to primitive store) the primitive that will be
+        // used for both the visual element and also the shadow(s).
+        let prim_index = self.create_primitive(
+            info,
+            Vec::new(),
+            PrimitiveContainer::TextRun(prim),
+        );
+
+        // Only add a visual element if it can contribute to the scene.
+        if text_color.a > 0.0 {
+            if self.shadow_prim_stack.is_empty() {
+                self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
+            } else {
+                self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
+            }
+        }
+
+        // Now add this primitive index to all the currently active text shadow
+        // primitives. Although we're adding the indices *after* the visual
+        // primitive here, they will still draw before the visual text, since
+        // the shadow primitive itself has been added to the draw cmd
+        // list *before* the visual element, during push_shadow. We need
+        // the primitive index of the visual element here before we can add
+        // the indices as sub-primitives to the shadow primitives.
+        for &(shadow_prim_index, _) in &self.shadow_prim_stack {
+            let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
+            debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Picture);
+            let picture =
+                &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
+
+            match picture.kind {
+                // Only run real blurs here (fast path zero blurs are handled above).
+                PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
+                    picture.add_primitive(
+                        prim_index,
+                        clip_and_scroll,
+                    );
+                }
+                _ => {}
+            }
+        }
+    }
+
+    pub fn add_image(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        stretch_size: LayerSize,
+        mut tile_spacing: LayerSize,
+        sub_rect: Option<TexelRect>,
+        image_key: ImageKey,
+        image_rendering: ImageRendering,
+        alpha_type: AlphaType,
+        tile_offset: Option<TileOffset>,
+    ) {
+        // If the tile spacing is the same as the rect size,
+        // then it is effectively zero. We use this later on
+        // in prim_store to detect if an image can be considered
+        // opaque.
+        if tile_spacing == info.rect.size {
+            tile_spacing = LayerSize::zero();
+        }
+
+        let request = ImageRequest {
+            key: image_key,
+            rendering: image_rendering,
+            tile: tile_offset,
+        };
+
+        // See if conditions are met to run through the new
+        // image brush shader, which supports segments.
+        if tile_spacing == LayerSize::zero() &&
+           stretch_size == info.rect.size &&
+           sub_rect.is_none() &&
+           tile_offset.is_none() {
+            let prim = BrushPrimitive::new(
+                BrushKind::Image {
+                    request,
+                    current_epoch: Epoch::invalid(),
+                    alpha_type,
+                },
+                None,
+            );
+
+            self.add_primitive(
+                clip_and_scroll,
+                info,
+                Vec::new(),
+                PrimitiveContainer::Brush(prim),
+            );
+        } else {
+            let prim_cpu = ImagePrimitiveCpu {
+                tile_spacing,
+                alpha_type,
+                stretch_size,
+                current_epoch: Epoch::invalid(),
+                source: ImageSource::Default,
+                key: ImageCacheKey {
+                    request,
+                    texel_rect: sub_rect.map(|texel_rect| {
+                        DeviceIntRect::new(
+                            DeviceIntPoint::new(
+                                texel_rect.uv0.x as i32,
+                                texel_rect.uv0.y as i32,
+                            ),
+                            DeviceIntSize::new(
+                                (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
+                                (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
+                            ),
+                        )
+                    }),
+                },
+            };
+
+            self.add_primitive(
+                clip_and_scroll,
+                info,
+                Vec::new(),
+                PrimitiveContainer::Image(prim_cpu),
+            );
+        }
+    }
+
+    pub fn add_yuv_image(
+        &mut self,
+        clip_and_scroll: ScrollNodeAndClipChain,
+        info: &LayerPrimitiveInfo,
+        yuv_data: YuvData,
+        color_space: YuvColorSpace,
+        image_rendering: ImageRendering,
+    ) {
+        let format = yuv_data.get_format();
+        let yuv_key = match yuv_data {
+            YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
+            YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2],
+            YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY],
+        };
+
+        let prim = BrushPrimitive::new(
+            BrushKind::YuvImage {
+                yuv_key,
+                format,
+                color_space,
+                image_rendering,
+            },
+            None,
+        );
+
+        self.add_primitive(
+            clip_and_scroll,
+            info,
+            Vec::new(),
+            PrimitiveContainer::Brush(prim),
+        );
+    }
+}
+
+pub fn build_scene(config: &FrameBuilderConfig, request: SceneRequest) -> BuiltScene {
+    // TODO: mutably pass the scene and update its own pipeline epoch map instead of
+    // creating a new one here.
+    let mut pipeline_epoch_map = FastHashMap::default();
+    let mut clip_scroll_tree = ClipScrollTree::new();
+
+    let frame_builder = DisplayListFlattener::create_frame_builder(
+        FrameBuilder::empty(), // WIP, we're not really recycling anything here, clean this up.
+        &request.scene,
+        &mut clip_scroll_tree,
+        request.font_instances,
+        request.tiled_image_map,
+        &request.view,
+        &request.output_pipelines,
+        config,
+        &mut pipeline_epoch_map
+    );
+
+    let mut scene = request.scene;
+    scene.pipeline_epochs = pipeline_epoch_map;
+
+    BuiltScene {
+        scene,
+        frame_builder,
+        clip_scroll_tree,
+        removed_pipelines: request.removed_pipelines,
+    }
+}
+
+trait PrimitiveInfoTiler {
+    fn decompose(
+        &self,
+        tile_size: LayerSize,
+        tile_spacing: LayerSize,
+        max_prims: usize,
+    ) -> Vec<LayerPrimitiveInfo>;
+}
+
+impl PrimitiveInfoTiler for LayerPrimitiveInfo {
+    fn decompose(
+        &self,
+        tile_size: LayerSize,
+        tile_spacing: LayerSize,
+        max_prims: usize,
+    ) -> Vec<LayerPrimitiveInfo> {
+        let mut prims = Vec::new();
+        let tile_repeat = tile_size + tile_spacing;
+
+        if tile_repeat.width <= 0.0 ||
+           tile_repeat.height <= 0.0 {
+            return prims;
+        }
+
+        if tile_repeat.width < self.rect.size.width ||
+           tile_repeat.height < self.rect.size.height {
+            let local_clip = self.local_clip.clip_by(&self.rect);
+            let rect_p0 = self.rect.origin;
+            let rect_p1 = self.rect.bottom_right();
+
+            let mut y0 = rect_p0.y;
+            while y0 < rect_p1.y {
+                let mut x0 = rect_p0.x;
+
+                while x0 < rect_p1.x {
+                    prims.push(LayerPrimitiveInfo {
+                        rect: LayerRect::new(
+                            LayerPoint::new(x0, y0),
+                            tile_size,
+                        ),
+                        local_clip,
+                        is_backface_visible: self.is_backface_visible,
+                        tag: self.tag,
+                    });
+
+                    // Mostly a safety against a crazy number of primitives
+                    // being generated. If we exceed that amount, just bail
+                    // out and only draw the maximum amount.
+                    if prims.len() > max_prims {
+                        warn!("too many prims found due to repeat/tile. dropping extra prims!");
+                        return prims;
+                    }
+
+                    x0 += tile_repeat.width;
+                }
+
+                y0 += tile_repeat.height;
+            }
+        }
+
+        prims
+    }
+}
+
+/// Properties of a stacking context that are maintained
+/// during creation of the scene. These structures are
+/// not persisted after the initial scene build.
+struct FlattenedStackingContext {
+    /// Pipeline this stacking context belongs to.
+    pipeline_id: PipelineId,
+
+    /// Filters / mix-blend-mode effects
+    composite_ops: CompositeOps,
+
+    /// If true, visible when backface is visible.
+    is_backface_visible: bool,
+
+    /// Allow subpixel AA for text runs on this stacking context.
+    /// This is a temporary hack while we don't support subpixel AA
+    /// on transparent stacking contexts.
+    allow_subpixel_aa: bool,
+
+    /// CSS transform-style property.
+    transform_style: TransformStyle,
+
+    /// If Some(..), this stacking context establishes a new
+    /// 3d rendering context, and the value is the primitive
+    // index of the 3d context container.
+    rendering_context_3d_prim_index: Option<PrimitiveIndex>,
+}
+
+#[derive(Debug)]
+pub struct ScrollbarInfo(pub ClipScrollNodeIndex, pub LayerRect);
deleted file mode 100644
--- a/gfx/webrender/src/frame.rs
+++ /dev/null
@@ -1,1210 +0,0 @@
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-use api::{BuiltDisplayListIter, ClipId, ColorF, ComplexClipRegion, DevicePixelScale};
-use api::{DeviceUintRect, DeviceUintSize, DisplayItemRef, DocumentLayer, Epoch, ExternalScrollId};
-use api::{FilterOp, IframeDisplayItem, ImageDisplayItem, ItemRange, LayerPoint};
-use api::{LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D, LayoutSize, PipelineId};
-use api::{ScrollClamping, ScrollEventPhase, ScrollFrameDisplayItem, ScrollLocation};
-use api::{ScrollNodeState, ScrollPolicy, ScrollSensitivity, SpecificDisplayItem, StackingContext};
-use api::{TileOffset, TransformStyle, WorldPoint};
-use clip::ClipRegion;
-use clip_scroll_node::StickyFrameInfo;
-use clip_scroll_tree::{ClipChainIndex, ClipScrollTree, ScrollStates};
-use euclid::rect;
-use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo};
-use gpu_cache::GpuCache;
-use hit_test::HitTester;
-use internal_types::{FastHashMap, FastHashSet, RenderedDocument};
-use prim_store::ScrollNodeAndClipChain;
-use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters};
-use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap};
-use scene::{Scene, StackingContextHelpers, ScenePipeline, SceneProperties};
-use tiling::{CompositeOps, Frame};
-use renderer::PipelineInfo;
-
-#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct FrameId(pub u32);
-
-static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF {
-    r: 0.3,
-    g: 0.3,
-    b: 0.3,
-    a: 0.6,
-};
-
-/// A data structure that keeps track of mapping between API clip ids and the indices
-/// used internally in the ClipScrollTree to avoid having to do HashMap lookups. This
-/// also includes a small LRU cache. Currently the cache is small (1 entry), but in the
-/// future we could use uluru here to do something more involved.
-pub struct ClipIdToIndexMapper {
-    map: FastHashMap<ClipId, ClipChainIndex>,
-    cached_index: Option<(ClipId, ClipChainIndex)>,
-}
-
-impl ClipIdToIndexMapper {
-    fn new() -> ClipIdToIndexMapper {
-        ClipIdToIndexMapper {
-            map: FastHashMap::default(),
-            cached_index: None,
-        }
-    }
-
-    pub fn add(&mut self, id: ClipId, index: ClipChainIndex) {
-        debug_assert!(!self.map.contains_key(&id));
-        self.map.insert(id, index);
-    }
-
-    pub fn map_to_parent_clip_chain(&mut self, id: ClipId, parent_id: &ClipId) {
-        let parent_chain_index = self.map_clip_id(parent_id);
-        self.add(id, parent_chain_index);
-    }
-
-    pub fn map_clip_id(&mut self, id: &ClipId) -> ClipChainIndex {
-        match self.cached_index {
-            Some((cached_id, cached_index)) if cached_id == *id => return cached_index,
-            _ => {}
-        }
-
-        self.map[id]
-    }
-
-    pub fn map_clip_id_and_cache_result(&mut self, id: &ClipId) -> ClipChainIndex {
-        let index = self.map_clip_id(id);
-        self.cached_index = Some((*id, index));
-        index
-    }
-
-    pub fn simple_scroll_and_clip_chain(&mut self, id: &ClipId) -> ScrollNodeAndClipChain {
-        ScrollNodeAndClipChain::new(*id, self.map_clip_id(&id))
-    }
-}
-
-struct FlattenContext<'a> {
-    scene: &'a Scene,
-    builder: FrameBuilder,
-    clip_scroll_tree: &'a mut ClipScrollTree,
-    font_instances: FontInstanceMap,
-    tiled_image_map: TiledImageMap,
-    pipeline_epochs: Vec<(PipelineId, Epoch)>,
-    replacements: Vec<(ClipId, ClipId)>,
-    output_pipelines: &'a FastHashSet<PipelineId>,
-    id_to_index_mapper: ClipIdToIndexMapper,
-}
-
-impl<'a> FlattenContext<'a> {
-    /// Since WebRender still handles fixed position and reference frame content internally
-    /// we need to apply this table of id replacements only to the id that affects the
-    /// position of a node. We can eventually remove this when clients start handling
-    /// reference frames themselves. This method applies these replacements.
-    fn apply_scroll_frame_id_replacement(&self, id: ClipId) -> ClipId {
-        match self.replacements.last() {
-            Some(&(to_replace, replacement)) if to_replace == id => replacement,
-            _ => id,
-        }
-    }
-
-    fn get_complex_clips(
-        &self,
-        pipeline_id: PipelineId,
-        complex_clips: ItemRange<ComplexClipRegion>,
-    ) -> Vec<ComplexClipRegion> {
-        if complex_clips.is_empty() {
-            return vec![];
-        }
-
-        self.scene
-            .pipelines
-            .get(&pipeline_id)
-            .expect("No display list?")
-            .display_list
-            .get(complex_clips)
-            .collect()
-    }
-
-    fn get_clip_chain_items(
-        &self,
-        pipeline_id: PipelineId,
-        items: ItemRange<ClipId>,
-    ) -> Vec<ClipId> {
-        if items.is_empty() {
-            return vec![];
-        }
-
-        self.scene
-            .pipelines
-            .get(&pipeline_id)
-            .expect("No display list?")
-            .display_list
-            .get(items)
-            .collect()
-    }
-
-    fn flatten_root(
-        &mut self,
-        traversal: &mut BuiltDisplayListIter<'a>,
-        pipeline_id: PipelineId,
-        frame_size: &LayoutSize,
-    ) {
-        let root_reference_frame_id = ClipId::root_reference_frame(pipeline_id);
-        let root_scroll_frame_id = ClipId::root_scroll_node(pipeline_id);
-
-        let root_clip_chain_index =
-            self.id_to_index_mapper.map_clip_id_and_cache_result(&root_reference_frame_id);
-        let root_reference_frame_clip_and_scroll = ScrollNodeAndClipChain::new(
-            root_reference_frame_id,
-            root_clip_chain_index,
-        );
-
-        self.builder.push_stacking_context(
-            pipeline_id,
-            CompositeOps::default(),
-            TransformStyle::Flat,
-            true,
-            true,
-            ScrollNodeAndClipChain::new(
-                ClipId::root_scroll_node(pipeline_id),
-                root_clip_chain_index,
-            ),
-            self.output_pipelines,
-        );
-
-        // For the root pipeline, there's no need to add a full screen rectangle
-        // here, as it's handled by the framebuffer clear.
-        if self.scene.root_pipeline_id != Some(pipeline_id) {
-            if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) {
-                if let Some(bg_color) = pipeline.background_color {
-                    let root_bounds = LayerRect::new(LayerPoint::zero(), *frame_size);
-                    let info = LayerPrimitiveInfo::new(root_bounds);
-                    self.builder.add_solid_rectangle(
-                        root_reference_frame_clip_and_scroll,
-                        &info,
-                        bg_color,
-                        None,
-                    );
-                }
-            }
-        }
-
-
-        self.flatten_items(
-            traversal,
-            pipeline_id,
-            LayerVector2D::zero(),
-        );
-
-        if self.builder.config.enable_scrollbars {
-            let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0));
-            let container_rect = LayerRect::new(LayerPoint::zero(), *frame_size);
-            self.builder.add_scroll_bar(
-                root_reference_frame_clip_and_scroll,
-                &LayerPrimitiveInfo::new(scrollbar_rect),
-                DEFAULT_SCROLLBAR_COLOR,
-                ScrollbarInfo(root_scroll_frame_id, container_rect),
-            );
-        }
-
-        self.builder.pop_stacking_context();
-    }
-
-    fn flatten_items(
-        &mut self,
-        traversal: &mut BuiltDisplayListIter<'a>,
-        pipeline_id: PipelineId,
-        reference_frame_relative_offset: LayerVector2D,
-    ) {
-        loop {
-            let subtraversal = {
-                let item = match traversal.next() {
-                    Some(item) => item,
-                    None => break,
-                };
-
-                if SpecificDisplayItem::PopStackingContext == *item.item() {
-                    return;
-                }
-
-                self.flatten_item(
-                    item,
-                    pipeline_id,
-                    reference_frame_relative_offset,
-                )
-            };
-
-            // If flatten_item created a sub-traversal, we need `traversal` to have the
-            // same state as the completed subtraversal, so we reinitialize it here.
-            if let Some(subtraversal) = subtraversal {
-                *traversal = subtraversal;
-            }
-        }
-    }
-
-    fn flatten_clip(&mut self, parent_id: &ClipId, new_clip_id: &ClipId, clip_region: ClipRegion) {
-        self.builder.add_clip_node(
-            *new_clip_id,
-            *parent_id,
-            clip_region,
-            self.clip_scroll_tree,
-            &mut self.id_to_index_mapper,
-        );
-    }
-
-    fn flatten_scroll_frame(
-        &mut self,
-        item: &DisplayItemRef,
-        info: &ScrollFrameDisplayItem,
-        pipeline_id: PipelineId,
-        clip_and_scroll: &ScrollNodeAndClipChain,
-        reference_frame_relative_offset: &LayerVector2D,
-    ) {
-        let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
-        let clip_region = ClipRegion::create_for_clip_node(
-            *item.local_clip().clip_rect(),
-            complex_clips,
-            info.image_mask,
-            &reference_frame_relative_offset,
-        );
-        // Just use clip rectangle as the frame rect for this scroll frame.
-        // This is useful when calculating scroll extents for the
-        // ClipScrollNode::scroll(..) API as well as for properly setting sticky
-        // positioning offsets.
-        let frame_rect = item.local_clip()
-            .clip_rect()
-            .translate(&reference_frame_relative_offset);
-        let content_rect = item.rect().translate(&reference_frame_relative_offset);
-
-        debug_assert!(info.clip_id != info.scroll_frame_id);
-
-        self.builder.add_clip_node(
-            info.clip_id,
-            clip_and_scroll.scroll_node_id,
-            clip_region,
-            self.clip_scroll_tree,
-            &mut self.id_to_index_mapper,
-        );
-
-        self.builder.add_scroll_frame(
-            info.scroll_frame_id,
-            info.clip_id,
-            info.external_id,
-            pipeline_id,
-            &frame_rect,
-            &content_rect.size,
-            info.scroll_sensitivity,
-            self.clip_scroll_tree,
-            &mut self.id_to_index_mapper,
-        );
-    }
-
-    fn flatten_stacking_context(
-        &mut self,
-        traversal: &mut BuiltDisplayListIter<'a>,
-        pipeline_id: PipelineId,
-        unreplaced_scroll_id: ClipId,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        mut reference_frame_relative_offset: LayerVector2D,
-        bounds: &LayerRect,
-        stacking_context: &StackingContext,
-        filters: ItemRange<FilterOp>,
-        is_backface_visible: bool,
-    ) {
-        // Avoid doing unnecessary work for empty stacking contexts.
-        if traversal.current_stacking_context_empty() {
-            traversal.skip_current_stacking_context();
-            return;
-        }
-
-        let composition_operations = {
-            // TODO(optimization?): self.traversal.display_list()
-            let display_list = &self
-                .scene
-                .pipelines
-                .get(&pipeline_id)
-                .expect("No display list?!")
-                .display_list;
-            CompositeOps::new(
-                stacking_context.filter_ops_for_compositing(display_list, filters),
-                stacking_context.mix_blend_mode_for_compositing(),
-            )
-        };
-
-        if stacking_context.scroll_policy == ScrollPolicy::Fixed {
-            self.replacements.push((
-                unreplaced_scroll_id,
-                self.builder.current_reference_frame_id(),
-            ));
-        }
-
-        reference_frame_relative_offset += bounds.origin.to_vector();
-
-        // If we have a transformation or a perspective, we should have been assigned a new
-        // reference frame id. This means this stacking context establishes a new reference frame.
-        // Descendant fixed position content will be positioned relative to us.
-        if let Some(reference_frame_id) = stacking_context.reference_frame_id {
-            debug_assert!(
-                stacking_context.transform.is_some() ||
-                stacking_context.perspective.is_some()
-            );
-
-            let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size);
-            self.builder.push_reference_frame(
-                reference_frame_id,
-                Some(clip_and_scroll.scroll_node_id),
-                pipeline_id,
-                &reference_frame_bounds,
-                stacking_context.transform,
-                stacking_context.perspective,
-                reference_frame_relative_offset,
-                self.clip_scroll_tree,
-                &mut self.id_to_index_mapper,
-            );
-            self.replacements.push((unreplaced_scroll_id, reference_frame_id));
-            reference_frame_relative_offset = LayerVector2D::zero();
-        }
-
-        // We apply the replacements one more time in case we need to set it to a replacement
-        // that we just pushed above.
-        let new_scroll_node = self.apply_scroll_frame_id_replacement(unreplaced_scroll_id);
-        let stacking_context_clip_and_scroll =
-            self.id_to_index_mapper.simple_scroll_and_clip_chain(&new_scroll_node);
-        self.builder.push_stacking_context(
-            pipeline_id,
-            composition_operations,
-            stacking_context.transform_style,
-            is_backface_visible,
-            false,
-            stacking_context_clip_and_scroll,
-            self.output_pipelines,
-        );
-
-        self.flatten_items(
-            traversal,
-            pipeline_id,
-            reference_frame_relative_offset,
-        );
-
-        if stacking_context.scroll_policy == ScrollPolicy::Fixed {
-            self.replacements.pop();
-        }
-
-        if stacking_context.reference_frame_id.is_some() {
-            self.replacements.pop();
-            self.builder.pop_reference_frame();
-        }
-
-        self.builder.pop_stacking_context();
-    }
-
-    fn flatten_iframe(
-        &mut self,
-        item: &DisplayItemRef,
-        info: &IframeDisplayItem,
-        clip_and_scroll: &ScrollNodeAndClipChain,
-        reference_frame_relative_offset: &LayerVector2D,
-    ) {
-        let iframe_pipeline_id = info.pipeline_id;
-        let pipeline = match self.scene.pipelines.get(&iframe_pipeline_id) {
-            Some(pipeline) => pipeline,
-            None => return,
-        };
-
-        self.builder.add_clip_node(
-            info.clip_id,
-            clip_and_scroll.scroll_node_id,
-            ClipRegion::create_for_clip_node_with_local_clip(
-                &item.local_clip(),
-                &reference_frame_relative_offset
-            ),
-            self.clip_scroll_tree,
-            &mut self.id_to_index_mapper,
-        );
-
-        self.pipeline_epochs.push((iframe_pipeline_id, pipeline.epoch));
-
-        let bounds = item.rect();
-        let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size);
-        let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
-        self.builder.push_reference_frame(
-            ClipId::root_reference_frame(iframe_pipeline_id),
-            Some(info.clip_id),
-            iframe_pipeline_id,
-            &iframe_rect,
-            None,
-            None,
-            origin,
-            self.clip_scroll_tree,
-            &mut self.id_to_index_mapper,
-        );
-
-        self.builder.add_scroll_frame(
-            ClipId::root_scroll_node(iframe_pipeline_id),
-            ClipId::root_reference_frame(iframe_pipeline_id),
-            Some(ExternalScrollId(0, iframe_pipeline_id)),
-            iframe_pipeline_id,
-            &iframe_rect,
-            &pipeline.content_size,
-            ScrollSensitivity::ScriptAndInputEvents,
-            self.clip_scroll_tree,
-            &mut self.id_to_index_mapper,
-        );
-
-        self.flatten_root(&mut pipeline.display_list.iter(), iframe_pipeline_id, &iframe_rect.size);
-
-        self.builder.pop_reference_frame();
-    }
-
-    fn flatten_item<'b>(
-        &'b mut self,
-        item: DisplayItemRef<'a, 'b>,
-        pipeline_id: PipelineId,
-        reference_frame_relative_offset: LayerVector2D,
-    ) -> Option<BuiltDisplayListIter<'a>> {
-        let clip_and_scroll = item.clip_and_scroll();
-        let mut clip_and_scroll = ScrollNodeAndClipChain::new(
-            clip_and_scroll.scroll_node_id,
-            self.id_to_index_mapper.map_clip_id_and_cache_result(&clip_and_scroll.clip_node_id()),
-        );
-
-        let unreplaced_scroll_id = clip_and_scroll.scroll_node_id;
-        clip_and_scroll.scroll_node_id =
-            self.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id);
-
-        let prim_info = item.get_layer_primitive_info(&reference_frame_relative_offset);
-        match *item.item() {
-            SpecificDisplayItem::Image(ref info) => {
-                match self.tiled_image_map.get(&info.image_key).cloned() {
-                    Some(tiling) => {
-                        // The image resource is tiled. We have to generate an image primitive
-                        // for each tile.
-                        self.decompose_image(
-                            clip_and_scroll,
-                            &prim_info,
-                            info,
-                            tiling.image_size,
-                            tiling.tile_size as u32,
-                        );
-                    }
-                    None => {
-                        self.builder.add_image(
-                            clip_and_scroll,
-                            &prim_info,
-                            info.stretch_size,
-                            info.tile_spacing,
-                            None,
-                            info.image_key,
-                            info.image_rendering,
-                            info.alpha_type,
-                            None,
-                        );
-                    }
-                }
-            }
-            SpecificDisplayItem::YuvImage(ref info) => {
-                self.builder.add_yuv_image(
-                    clip_and_scroll,
-                    &prim_info,
-                    info.yuv_data,
-                    info.color_space,
-                    info.image_rendering,
-                );
-            }
-            SpecificDisplayItem::Text(ref text_info) => {
-                let instance_map = self.font_instances
-                    .read()
-                    .unwrap();
-                match instance_map.get(&text_info.font_key) {
-                    Some(instance) => {
-                        self.builder.add_text(
-                            clip_and_scroll,
-                            reference_frame_relative_offset,
-                            &prim_info,
-                            instance,
-                            &text_info.color,
-                            item.glyphs(),
-                            item.display_list().get(item.glyphs()).count(),
-                            text_info.glyph_options,
-                        );
-                    }
-                    None => {
-                        warn!("Unknown font instance key");
-                        debug!("key={:?}", text_info.font_key);
-                    }
-                }
-            }
-            SpecificDisplayItem::Rectangle(ref info) => {
-                self.builder.add_solid_rectangle(
-                    clip_and_scroll,
-                    &prim_info,
-                    info.color,
-                    None,
-                );
-            }
-            SpecificDisplayItem::ClearRectangle => {
-                self.builder.add_clear_rectangle(
-                    clip_and_scroll,
-                    &prim_info,
-                );
-            }
-            SpecificDisplayItem::Line(ref info) => {
-                self.builder.add_line(
-                    clip_and_scroll,
-                    &prim_info,
-                    info.wavy_line_thickness,
-                    info.orientation,
-                    &info.color,
-                    info.style,
-                );
-            }
-            SpecificDisplayItem::Gradient(ref info) => {
-                self.builder.add_gradient(
-                    clip_and_scroll,
-                    &prim_info,
-                    info.gradient.start_point,
-                    info.gradient.end_point,
-                    item.gradient_stops(),
-                    item.display_list().get(item.gradient_stops()).count(),
-                    info.gradient.extend_mode,
-                    info.tile_size,
-                    info.tile_spacing,
-                );
-            }
-            SpecificDisplayItem::RadialGradient(ref info) => {
-                self.builder.add_radial_gradient(
-                    clip_and_scroll,
-                    &prim_info,
-                    info.gradient.start_center,
-                    info.gradient.start_radius,
-                    info.gradient.end_center,
-                    info.gradient.end_radius,
-                    info.gradient.ratio_xy,
-                    item.gradient_stops(),
-                    info.gradient.extend_mode,
-                    info.tile_size,
-                    info.tile_spacing,
-                );
-            }
-            SpecificDisplayItem::BoxShadow(ref box_shadow_info) => {
-                let bounds = box_shadow_info
-                    .box_bounds
-                    .translate(&reference_frame_relative_offset);
-                let mut prim_info = prim_info.clone();
-                prim_info.rect = bounds;
-                self.builder.add_box_shadow(
-                    pipeline_id,
-                    clip_and_scroll,
-                    &prim_info,
-                    &box_shadow_info.offset,
-                    &box_shadow_info.color,
-                    box_shadow_info.blur_radius,
-                    box_shadow_info.spread_radius,
-                    box_shadow_info.border_radius,
-                    box_shadow_info.clip_mode,
-                );
-            }
-            SpecificDisplayItem::Border(ref info) => {
-                self.builder.add_border(
-                    clip_and_scroll,
-                    &prim_info,
-                    info,
-                    item.gradient_stops(),
-                    item.display_list().get(item.gradient_stops()).count(),
-                );
-            }
-            SpecificDisplayItem::PushStackingContext(ref info) => {
-                let mut subtraversal = item.sub_iter();
-                self.flatten_stacking_context(
-                    &mut subtraversal,
-                    pipeline_id,
-                    unreplaced_scroll_id,
-                    clip_and_scroll,
-                    reference_frame_relative_offset,
-                    &item.rect(),
-                    &info.stacking_context,
-                    item.filters(),
-                    prim_info.is_backface_visible,
-                );
-                return Some(subtraversal);
-            }
-            SpecificDisplayItem::Iframe(ref info) => {
-                self.flatten_iframe(
-                    &item,
-                    info,
-                    &clip_and_scroll,
-                    &reference_frame_relative_offset
-                );
-            }
-            SpecificDisplayItem::Clip(ref info) => {
-                let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0);
-                let clip_region = ClipRegion::create_for_clip_node(
-                    *item.local_clip().clip_rect(),
-                    complex_clips,
-                    info.image_mask,
-                    &reference_frame_relative_offset,
-                );
-                self.flatten_clip(&clip_and_scroll.scroll_node_id, &info.id, clip_region);
-            }
-            SpecificDisplayItem::ClipChain(ref info) => {
-                let items = self.get_clip_chain_items(pipeline_id, item.clip_chain_items());
-                let parent = info.parent.map(|id|
-                     self.id_to_index_mapper.map_clip_id(&ClipId::ClipChain(id))
-                );
-                let clip_chain_index =
-                    self.clip_scroll_tree.add_clip_chain_descriptor(parent, items);
-                self.id_to_index_mapper.add(ClipId::ClipChain(info.id), clip_chain_index);
-            },
-            SpecificDisplayItem::ScrollFrame(ref info) => {
-                self.flatten_scroll_frame(
-                    &item,
-                    info,
-                    pipeline_id,
-                    &clip_and_scroll,
-                    &reference_frame_relative_offset
-                );
-            }
-            SpecificDisplayItem::StickyFrame(ref info) => {
-                let frame_rect = item.rect().translate(&reference_frame_relative_offset);
-                let sticky_frame_info = StickyFrameInfo::new(
-                    info.margins,
-                    info.vertical_offset_bounds,
-                    info.horizontal_offset_bounds,
-                    info.previously_applied_offset,
-                );
-                let parent_id = clip_and_scroll.scroll_node_id;
-                self.clip_scroll_tree.add_sticky_frame(
-                    info.id,
-                    parent_id,
-                    frame_rect,
-                    sticky_frame_info
-                );
-                self.id_to_index_mapper.map_to_parent_clip_chain(info.id, &parent_id);
-            }
-
-            // Do nothing; these are dummy items for the display list parser
-            SpecificDisplayItem::SetGradientStops => {}
-
-            SpecificDisplayItem::PopStackingContext => {
-                unreachable!("Should have returned in parent method.")
-            }
-            SpecificDisplayItem::PushShadow(shadow) => {
-                let mut prim_info = prim_info.clone();
-                prim_info.rect = LayerRect::zero();
-                self.builder
-                    .push_shadow(shadow, clip_and_scroll, &prim_info);
-            }
-            SpecificDisplayItem::PopAllShadows => {
-                self.builder.pop_all_shadows();
-            }
-        }
-        None
-    }
-
-    /// Decomposes an image display item that is repeated into an image per individual repetition.
-    /// We need to do this when we are unable to perform the repetition in the shader,
-    /// for example if the image is tiled.
-    ///
-    /// In all of the "decompose" methods below, we independently handle horizontal and vertical
-    /// decomposition. This lets us generate the minimum amount of primitives by, for  example,
-    /// decompositing the repetition horizontally while repeating vertically in the shader (for
-    /// an image where the width is too bug but the height is not).
-    ///
-    /// decompose_image and decompose_image_row handle image repetitions while decompose_tiled_image
-    /// takes care of the decomposition required by the internal tiling of the image.
-    fn decompose_image(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        prim_info: &LayerPrimitiveInfo,
-        info: &ImageDisplayItem,
-        image_size: DeviceUintSize,
-        tile_size: u32,
-    ) {
-        let no_vertical_tiling = image_size.height <= tile_size;
-        let no_vertical_spacing = info.tile_spacing.height == 0.0;
-        let item_rect = prim_info.rect;
-        if no_vertical_tiling && no_vertical_spacing {
-            self.decompose_image_row(
-                clip_and_scroll,
-                prim_info,
-                info,
-                image_size,
-                tile_size,
-            );
-            return;
-        }
-
-        // Decompose each vertical repetition into rows.
-        let layout_stride = info.stretch_size.height + info.tile_spacing.height;
-        let num_repetitions = (item_rect.size.height / layout_stride).ceil() as u32;
-        for i in 0 .. num_repetitions {
-            if let Some(row_rect) = rect(
-                item_rect.origin.x,
-                item_rect.origin.y + (i as f32) * layout_stride,
-                item_rect.size.width,
-                info.stretch_size.height,
-            ).intersection(&item_rect)
-            {
-                let mut prim_info = prim_info.clone();
-                prim_info.rect = row_rect;
-                self.decompose_image_row(
-                    clip_and_scroll,
-                    &prim_info,
-                    info,
-                    image_size,
-                    tile_size,
-                );
-            }
-        }
-    }
-
-    fn decompose_image_row(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        prim_info: &LayerPrimitiveInfo,
-        info: &ImageDisplayItem,
-        image_size: DeviceUintSize,
-        tile_size: u32,
-    ) {
-        let no_horizontal_tiling = image_size.width <= tile_size;
-        let no_horizontal_spacing = info.tile_spacing.width == 0.0;
-        if no_horizontal_tiling && no_horizontal_spacing {
-            self.decompose_tiled_image(
-                clip_and_scroll,
-                prim_info,
-                info,
-                image_size,
-                tile_size,
-            );
-            return;
-        }
-
-        // Decompose each horizontal repetition.
-        let item_rect = prim_info.rect;
-        let layout_stride = info.stretch_size.width + info.tile_spacing.width;
-        let num_repetitions = (item_rect.size.width / layout_stride).ceil() as u32;
-        for i in 0 .. num_repetitions {
-            if let Some(decomposed_rect) = rect(
-                item_rect.origin.x + (i as f32) * layout_stride,
-                item_rect.origin.y,
-                info.stretch_size.width,
-                item_rect.size.height,
-            ).intersection(&item_rect)
-            {
-                let mut prim_info = prim_info.clone();
-                prim_info.rect = decomposed_rect;
-                self.decompose_tiled_image(
-                    clip_and_scroll,
-                    &prim_info,
-                    info,
-                    image_size,
-                    tile_size,
-                );
-            }
-        }
-    }
-
-    fn decompose_tiled_image(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        prim_info: &LayerPrimitiveInfo,
-        info: &ImageDisplayItem,
-        image_size: DeviceUintSize,
-        tile_size: u32,
-    ) {
-        // The image resource is tiled. We have to generate an image primitive
-        // for each tile.
-        // We need to do this because the image is broken up into smaller tiles in the texture
-        // cache and the image shader is not able to work with this type of sparse representation.
-
-        // The tiling logic works as follows:
-        //
-        //  ###################-+  -+
-        //  #    |    |    |//# |   | image size
-        //  #    |    |    |//# |   |
-        //  #----+----+----+--#-+   |  -+
-        //  #    |    |    |//# |   |   | regular tile size
-        //  #    |    |    |//# |   |   |
-        //  #----+----+----+--#-+   |  -+-+
-        //  #////|////|////|//# |   |     | "leftover" height
-        //  ################### |  -+  ---+
-        //  #----+----+----+----+
-        //
-        // In the ascii diagram above, a large image is plit into tiles of almost regular size.
-        // The tiles on the right and bottom edges (hatched in the diagram) are smaller than
-        // the regular tiles and are handled separately in the code see leftover_width/height.
-        // each generated image primitive corresponds to a tile in the texture cache, with the
-        // assumption that the smaller tiles with leftover sizes are sized to fit their own
-        // irregular size in the texture cache.
-        //
-        // For the case where we don't tile along an axis, we can still perform the repetition in
-        // the shader (for this particular axis), and it is worth special-casing for this to avoid
-        // generating many primitives.
-        // This can happen with very tall and thin images used as a repeating background.
-        // Apparently web authors do that...
-
-        let item_rect = prim_info.rect;
-        let needs_repeat_x = info.stretch_size.width < item_rect.size.width;
-        let needs_repeat_y = info.stretch_size.height < item_rect.size.height;
-
-        let tiled_in_x = image_size.width > tile_size;
-        let tiled_in_y = image_size.height > tile_size;
-
-        // If we don't actually tile in this dimension, repeating can be done in the shader.
-        let shader_repeat_x = needs_repeat_x && !tiled_in_x;
-        let shader_repeat_y = needs_repeat_y && !tiled_in_y;
-
-        let tile_size_f32 = tile_size as f32;
-
-        // Note: this rounds down so it excludes the partially filled tiles on the right and
-        // bottom edges (we handle them separately below).
-        let num_tiles_x = (image_size.width / tile_size) as u16;
-        let num_tiles_y = (image_size.height / tile_size) as u16;
-
-        // Ratio between (image space) tile size and image size.
-        let img_dw = tile_size_f32 / (image_size.width as f32);
-        let img_dh = tile_size_f32 / (image_size.height as f32);
-
-        // Strected size of the tile in layout space.
-        let stretched_tile_size = LayerSize::new(
-            img_dw * info.stretch_size.width,
-            img_dh * info.stretch_size.height,
-        );
-
-        // The size in pixels of the tiles on the right and bottom edges, smaller
-        // than the regular tile size if the image is not a multiple of the tile size.
-        // Zero means the image size is a multiple of the tile size.
-        let leftover =
-            DeviceUintSize::new(image_size.width % tile_size, image_size.height % tile_size);
-
-        for ty in 0 .. num_tiles_y {
-            for tx in 0 .. num_tiles_x {
-                self.add_tile_primitive(
-                    clip_and_scroll,
-                    prim_info,
-                    info,
-                    TileOffset::new(tx, ty),
-                    stretched_tile_size,
-                    1.0,
-                    1.0,
-                    shader_repeat_x,
-                    shader_repeat_y,
-                );
-            }
-            if leftover.width != 0 {
-                // Tiles on the right edge that are smaller than the tile size.
-                self.add_tile_primitive(
-                    clip_and_scroll,
-                    prim_info,
-                    info,
-                    TileOffset::new(num_tiles_x, ty),
-                    stretched_tile_size,
-                    (leftover.width as f32) / tile_size_f32,
-                    1.0,
-                    shader_repeat_x,
-                    shader_repeat_y,
-                );
-            }
-        }
-
-        if leftover.height != 0 {
-            for tx in 0 .. num_tiles_x {
-                // Tiles on the bottom edge that are smaller than the tile size.
-                self.add_tile_primitive(
-                    clip_and_scroll,
-                    prim_info,
-                    info,
-                    TileOffset::new(tx, num_tiles_y),
-                    stretched_tile_size,
-                    1.0,
-                    (leftover.height as f32) / tile_size_f32,
-                    shader_repeat_x,
-                    shader_repeat_y,
-                );
-            }
-
-            if leftover.width != 0 {
-                // Finally, the bottom-right tile with a "leftover" size.
-                self.add_tile_primitive(
-                    clip_and_scroll,
-                    prim_info,
-                    info,
-                    TileOffset::new(num_tiles_x, num_tiles_y),
-                    stretched_tile_size,
-                    (leftover.width as f32) / tile_size_f32,
-                    (leftover.height as f32) / tile_size_f32,
-                    shader_repeat_x,
-                    shader_repeat_y,
-                );
-            }
-        }
-    }
-
-    fn add_tile_primitive(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        prim_info: &LayerPrimitiveInfo,
-        info: &ImageDisplayItem,
-        tile_offset: TileOffset,
-        stretched_tile_size: LayerSize,
-        tile_ratio_width: f32,
-        tile_ratio_height: f32,
-        shader_repeat_x: bool,
-        shader_repeat_y: bool,
-    ) {
-        // If the the image is tiled along a given axis, we can't have the shader compute
-        // the image repetition pattern. In this case we base the primitive's rectangle size
-        // on the stretched tile size which effectively cancels the repetion (and repetition
-        // has to be emulated by generating more primitives).
-        // If the image is not tiled along this axis, we can perform the repetition in the
-        // shader. in this case we use the item's size in the primitive (on that particular
-        // axis).
-        // See the shader_repeat_x/y code below.
-
-        let stretched_size = LayerSize::new(
-            stretched_tile_size.width * tile_ratio_width,
-            stretched_tile_size.height * tile_ratio_height,
-        );
-
-        let mut prim_rect = LayerRect::new(
-            prim_info.rect.origin +
-                LayerVector2D::new(
-                    tile_offset.x as f32 * stretched_tile_size.width,
-                    tile_offset.y as f32 * stretched_tile_size.height,
-                ),
-            stretched_size,
-        );
-
-        if shader_repeat_x {
-            assert_eq!(tile_offset.x, 0);
-            prim_rect.size.width = prim_info.rect.size.width;
-        }
-
-        if shader_repeat_y {
-            assert_eq!(tile_offset.y, 0);
-            prim_rect.size.height = prim_info.rect.size.height;
-        }
-
-        // Fix up the primitive's rect if it overflows the original item rect.
-        if let Some(prim_rect) = prim_rect.intersection(&prim_info.rect) {
-            let mut prim_info = prim_info.clone();
-            prim_info.rect = prim_rect;
-            self.builder.add_image(
-                clip_and_scroll,
-                &prim_info,
-                stretched_size,
-                info.tile_spacing,
-                None,
-                info.image_key,
-                info.image_rendering,
-                info.alpha_type,
-                Some(tile_offset),
-            );
-        }
-    }
-}
-
-/// Frame context contains the information required to update
-/// (e.g. scroll) a renderer frame builder (`FrameBuilder`).
-pub struct FrameContext {
-    window_size: DeviceUintSize,
-    clip_scroll_tree: ClipScrollTree,
-    pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
-    id: FrameId,
-    pub frame_builder_config: FrameBuilderConfig,
-}
-
-impl FrameContext {
-    pub fn new(config: FrameBuilderConfig) -> Self {
-        FrameContext {
-            window_size: DeviceUintSize::zero(),
-            pipeline_epoch_map: FastHashMap::default(),
-            clip_scroll_tree: ClipScrollTree::new(),
-            id: FrameId(0),
-            frame_builder_config: config,
-        }
-    }
-
-    pub fn reset(&mut self) -> ScrollStates {
-        self.pipeline_epoch_map.clear();
-
-        // Advance to the next frame.
-        self.id.0 += 1;
-
-        self.clip_scroll_tree.drain()
-    }
-
-    #[cfg(feature = "debugger")]
-    pub fn get_clip_scroll_tree(&self) -> &ClipScrollTree {
-        &self.clip_scroll_tree
-    }
-
-    pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
-        self.clip_scroll_tree.get_scroll_node_state()
-    }
-
-    /// Returns true if the node actually changed position or false otherwise.
-    pub fn scroll_node(
-        &mut self,
-        origin: LayerPoint,
-        id: ExternalScrollId,
-        clamp: ScrollClamping
-    ) -> bool {
-        self.clip_scroll_tree.scroll_node(origin, id, clamp)
-    }
-
-    /// Returns true if any nodes actually changed position or false otherwise.
-    pub fn scroll(
-        &mut self,
-        scroll_location: ScrollLocation,
-        cursor: WorldPoint,
-        phase: ScrollEventPhase,
-    ) -> bool {
-        self.clip_scroll_tree.scroll(scroll_location, cursor, phase)
-    }
-
-    pub fn tick_scrolling_bounce_animations(&mut self) {
-        self.clip_scroll_tree.tick_scrolling_bounce_animations();
-    }
-
-    pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) {
-        self.clip_scroll_tree
-            .discard_frame_state_for_pipeline(pipeline_id);
-    }
-
-    pub fn create_frame_builder(
-        &mut self,
-        old_builder: FrameBuilder,
-        scene: &Scene,
-        resource_cache: &mut ResourceCache,
-        window_size: DeviceUintSize,
-        inner_rect: DeviceUintRect,
-        device_pixel_scale: DevicePixelScale,
-        output_pipelines: &FastHashSet<PipelineId>,
-    ) -> FrameBuilder {
-        let root_pipeline_id = match scene.root_pipeline_id {
-            Some(root_pipeline_id) => root_pipeline_id,
-            None => return old_builder,
-        };
-
-        let root_pipeline = match scene.pipelines.get(&root_pipeline_id) {
-            Some(root_pipeline) => root_pipeline,
-            None => return old_builder,
-        };
-
-        if window_size.width == 0 || window_size.height == 0 {
-            error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
-        }
-        self.window_size = window_size;
-
-        let old_scrolling_states = self.reset();
-
-        self.pipeline_epoch_map
-            .insert(root_pipeline_id, root_pipeline.epoch);
-
-        let background_color = root_pipeline
-            .background_color
-            .and_then(|color| if color.a > 0.0 { Some(color) } else { None });
-
-        let frame_builder = {
-            let mut roller = FlattenContext {
-                scene,
-                builder: old_builder.recycle(
-                    inner_rect,
-                    background_color,
-                    self.frame_builder_config,
-                ),
-                clip_scroll_tree: &mut self.clip_scroll_tree,
-                font_instances: resource_cache.get_font_instances(),
-                tiled_image_map: resource_cache.get_tiled_image_map(),
-                pipeline_epochs: Vec::new(),
-                replacements: Vec::new(),
-                output_pipelines,
-                id_to_index_mapper: ClipIdToIndexMapper::new(),
-            };
-
-            roller.builder.push_root(
-                root_pipeline_id,
-                &root_pipeline.viewport_size,
-                &root_pipeline.content_size,
-                roller.clip_scroll_tree,
-                &mut roller.id_to_index_mapper,
-            );
-
-            roller.builder.setup_viewport_offset(
-                inner_rect,
-                device_pixel_scale,
-                roller.clip_scroll_tree,
-            );
-
-            roller.flatten_root(
-                &mut root_pipeline.display_list.iter(),
-                root_pipeline_id,
-                &root_pipeline.viewport_size,
-            );
-
-            debug_assert!(roller.builder.picture_stack.is_empty());
-
-            self.pipeline_epoch_map.extend(roller.pipeline_epochs.drain(..));
-            roller.builder
-        };
-
-        self.clip_scroll_tree
-            .finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
-
-        frame_builder
-    }
-
-    pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
-        self.pipeline_epoch_map.insert(pipeline_id, epoch);
-    }
-
-    pub fn make_rendered_document(&mut self, frame: Frame, removed_pipelines: Vec<PipelineId>) -> RenderedDocument {
-        let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back();
-        RenderedDocument::new(
-            PipelineInfo {
-                epochs: self.pipeline_epoch_map.clone(),
-                removed_pipelines,
-            },
-            nodes_bouncing_back,
-            frame
-        )
-    }
-
-    //TODO: this can probably be simplified if `build()` is called directly by RB.
-    // The only things it needs from the frame context is the CST and frame ID.
-    pub fn build_rendered_document(
-        &mut self,
-        frame_builder: &mut FrameBuilder,
-        resource_cache: &mut ResourceCache,
-        gpu_cache: &mut GpuCache,
-        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
-        device_pixel_scale: DevicePixelScale,
-        layer: DocumentLayer,
-        pan: WorldPoint,
-        texture_cache_profile: &mut TextureCacheProfileCounters,
-        gpu_cache_profile: &mut GpuCacheProfileCounters,
-		scene_properties: &SceneProperties,
-        removed_pipelines: Vec<PipelineId>,
-    ) -> (HitTester, RenderedDocument) {
-        let frame = frame_builder.build(
-            resource_cache,
-            gpu_cache,
-            self.id,
-            &mut self.clip_scroll_tree,
-            pipelines,
-            self.window_size,
-            device_pixel_scale,
-            layer,
-            pan,
-            texture_cache_profile,
-            gpu_cache_profile,
-            scene_properties,
-        );
-
-        let hit_tester = frame_builder.create_hit_tester(&self.clip_scroll_tree);
-
-        (hit_tester, self.make_rendered_document(frame, removed_pipelines))
-    }
-}
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,142 +1,84 @@
 /* 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 api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipId, ColorF};
-use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceUintPoint};
-use api::{DeviceUintRect, DeviceUintSize, DocumentLayer, Epoch, ExtendMode, ExternalScrollId};
-use api::{FontRenderMode, GlyphInstance, GlyphOptions, GradientStop, ImageKey, ImageRendering};
-use api::{ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize, LayerVector2D};
-use api::{LayoutTransform, LayoutVector2D, LineOrientation, LineStyle, LocalClip, PipelineId};
-use api::{PremultipliedColorF, PropertyBinding, RepeatMode, ScrollSensitivity, Shadow, TexelRect};
-use api::{TileOffset, TransformStyle, WorldPoint, YuvColorSpace, YuvData};
-use app_units::Au;
-use border::ImageBorderSegment;
-use clip::{ClipChain, ClipRegion, ClipSource, ClipSources, ClipStore};
-use clip_scroll_node::{ClipScrollNode, NodeType};
-use clip_scroll_tree::{ClipScrollTree, ClipChainIndex};
-use euclid::{SideOffsets2D, vec2};
-use frame::{FrameId, ClipIdToIndexMapper};
-use glyph_rasterizer::FontInstance;
+use api::{BuiltDisplayList, ColorF, DeviceIntPoint, DeviceIntRect, DevicePixelScale};
+use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, FontRenderMode};
+use api::{LayerRect, LayerSize, PipelineId, PremultipliedColorF, WorldPoint};
+use clip::{ClipChain, ClipStore};
+use clip_scroll_node::{ClipScrollNode};
+use clip_scroll_tree::{ClipScrollNodeIndex, ClipScrollTree};
+use display_list_flattener::{DisplayListFlattener};
 use gpu_cache::GpuCache;
 use gpu_types::{ClipChainRectIndex, ClipScrollNodeData, PictureType};
-use hit_test::{HitTester, HitTestingItem, HitTestingRun};
-use internal_types::{FastHashMap, FastHashSet};
-use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, CachedGradient, CachedGradientIndex};
-use prim_store::{ImageCacheKey, ImagePrimitiveCpu, ImageSource, PrimitiveContainer};
-use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveRun, PrimitiveStore};
-use prim_store::{ScrollNodeAndClipChain, TextRunPrimitiveCpu};
+use hit_test::{HitTester, HitTestingRun};
+use internal_types::{FastHashMap};
+use picture::{ContentOrigin, PictureSurface};
+use prim_store::{CachedGradient, PrimitiveIndex, PrimitiveRun, PrimitiveStore};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
+use render_backend::FrameId;
 use render_task::{ClearMode, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
-use resource_cache::{ImageRequest, ResourceCache};
+use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
-use std::{mem, usize, f32};
-use tiling::{CompositeOps, Frame, RenderPass, RenderTargetKind};
-use tiling::{RenderPassKind, RenderTargetContext, ScrollbarPrimitive};
-use util::{self, MaxRect, RectHelpers, WorldToLayerFastTransform, recycle_vec};
-
-#[derive(Debug)]
-pub struct ScrollbarInfo(pub ClipId, pub LayerRect);
-
-/// Properties of a stacking context that are maintained
-/// during creation of the scene. These structures are
-/// not persisted after the initial scene build.
-struct StackingContext {
-    /// Pipeline this stacking context belongs to.
-    pipeline_id: PipelineId,
-
-    /// Filters / mix-blend-mode effects
-    composite_ops: CompositeOps,
-
-    /// If true, visible when backface is visible.
-    is_backface_visible: bool,
-
-    /// Allow subpixel AA for text runs on this stacking context.
-    /// This is a temporary hack while we don't support subpixel AA
-    /// on transparent stacking contexts.
-    allow_subpixel_aa: bool,
-
-    /// CSS transform-style property.
-    transform_style: TransformStyle,
-
-    /// If Some(..), this stacking context establishes a new
-    /// 3d rendering context, and the value is the primitive
-    // index of the 3d context container.
-    rendering_context_3d_prim_index: Option<PrimitiveIndex>,
-}
+use std::{mem, f32};
+use std::sync::Arc;
+use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext, RenderTargetKind};
+use tiling::ScrollbarPrimitive;
+use util::{self, MaxRect, WorldToLayerFastTransform};
 
 #[derive(Clone, Copy)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameBuilderConfig {
     pub enable_scrollbars: bool,
     pub default_font_render_mode: FontRenderMode,
     pub debug: bool,
     pub dual_source_blending_is_supported: bool,
     pub dual_source_blending_is_enabled: bool,
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
-    prim_store: PrimitiveStore,
+    window_size: DeviceUintSize,
+    pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
-    hit_testing_runs: Vec<HitTestingRun>,
+    pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
     pub cached_gradients: Vec<CachedGradient>,
-
-    // A stack of the current shadow primitives.
-    // The sub-Vec stores a buffer of fast-path primitives to be appended on pop.
-    shadow_prim_stack: Vec<(PrimitiveIndex, Vec<(PrimitiveIndex, ScrollNodeAndClipChain)>)>,
-    // If we're doing any fast-path shadows, we buffer the "real"
-    // content here, to be appended when the shadow stack is empty.
-    pending_shadow_contents: Vec<(PrimitiveIndex, ScrollNodeAndClipChain, LayerPrimitiveInfo)>,
-
-    scrollbar_prims: Vec<ScrollbarPrimitive>,
-
-    /// A stack of scroll nodes used during display list processing to properly
-    /// parent new scroll nodes.
-    reference_frame_stack: Vec<ClipId>,
-
-    /// A stack of the current pictures, used during scene building.
-    pub picture_stack: Vec<PrimitiveIndex>,
-
-    /// A temporary stack of stacking context properties, used only
-    /// during scene building.
-    sc_stack: Vec<StackingContext>,
+    pub scrollbar_prims: Vec<ScrollbarPrimitive>,
 }
 
-pub struct FrameContext<'a> {
+pub struct FrameBuildingContext<'a> {
     pub device_pixel_scale: DevicePixelScale,
     pub scene_properties: &'a SceneProperties,
-    pub pipelines: &'a FastHashMap<PipelineId, ScenePipeline>,
+    pub pipelines: &'a FastHashMap<PipelineId, Arc<ScenePipeline>>,
     pub screen_rect: DeviceIntRect,
     pub clip_scroll_tree: &'a ClipScrollTree,
     pub node_data: &'a [ClipScrollNodeData],
 }
 
-pub struct FrameState<'a> {
+pub struct FrameBuildingState<'a> {
     pub render_tasks: &'a mut RenderTaskTree,
     pub profile_counters: &'a mut FrameProfileCounters,
     pub clip_store: &'a mut ClipStore,
     pub local_clip_rects: &'a mut Vec<LayerRect>,
     pub resource_cache: &'a mut ResourceCache,
     pub gpu_cache: &'a mut GpuCache,
     pub cached_gradients: &'a mut [CachedGradient],
 }
 
 pub struct PictureContext<'a> {
     pub pipeline_id: PipelineId,
     pub perform_culling: bool,
     pub prim_runs: Vec<PrimitiveRun>,
-    pub original_reference_frame_id: Option<ClipId>,
+    pub original_reference_frame_index: Option<ClipScrollNodeIndex>,
     pub display_list: &'a BuiltDisplayList,
     pub draw_text_transformed: bool,
     pub inv_world_transform: Option<WorldToLayerFastTransform>,
 }
 
 pub struct PictureState {
     pub tasks: Vec<RenderTaskId>,
 }
@@ -168,1585 +110,106 @@ impl<'a> PrimitiveRunContext<'a> {
         }
     }
 }
 
 impl FrameBuilder {
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
-            shadow_prim_stack: Vec::new(),
             cached_gradients: Vec::new(),
-            pending_shadow_contents: Vec::new(),
             scrollbar_prims: Vec::new(),
-            reference_frame_stack: Vec::new(),
-            picture_stack: Vec::new(),
-            sc_stack: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
+            window_size: DeviceUintSize::zero(),
             background_color: None,
             config: FrameBuilderConfig {
                 enable_scrollbars: false,
                 default_font_render_mode: FontRenderMode::Mono,
                 debug: false,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
             },
         }
     }
 
-    pub fn recycle(
-        self,
+    pub fn with_display_list_flattener(
         screen_rect: DeviceUintRect,
         background_color: Option<ColorF>,
-        config: FrameBuilderConfig,
+        window_size: DeviceUintSize,
+        flattener: DisplayListFlattener,
     ) -> Self {
         FrameBuilder {
-            hit_testing_runs: recycle_vec(self.hit_testing_runs),
-            shadow_prim_stack: recycle_vec(self.shadow_prim_stack),
-            cached_gradients: recycle_vec(self.cached_gradients),
-            pending_shadow_contents: recycle_vec(self.pending_shadow_contents),
-            scrollbar_prims: recycle_vec(self.scrollbar_prims),
-            reference_frame_stack: recycle_vec(self.reference_frame_stack),
-            picture_stack: recycle_vec(self.picture_stack),
-            sc_stack: recycle_vec(self.sc_stack),
-            prim_store: self.prim_store.recycle(),
-            clip_store: self.clip_store.recycle(),
+            hit_testing_runs: flattener.hit_testing_runs,
+            cached_gradients: flattener.cached_gradients,
+            scrollbar_prims: flattener.scrollbar_prims,
+            prim_store: flattener.prim_store,
+            clip_store: flattener.clip_store,
             screen_rect,
             background_color,
-            config,
-        }
-    }
-
-    /// Create a primitive and add it to the prim store. This method doesn't
-    /// add the primitive to the draw list, so can be used for creating
-    /// sub-primitives.
-    pub fn create_primitive(
-        &mut self,
-        info: &LayerPrimitiveInfo,
-        mut clip_sources: Vec<ClipSource>,
-        container: PrimitiveContainer,
-    ) -> PrimitiveIndex {
-        if let &LocalClip::RoundedRect(main, region) = &info.local_clip {
-            clip_sources.push(ClipSource::Rectangle(main));
-
-            clip_sources.push(ClipSource::new_rounded_rect(
-                region.rect,
-                region.radii,
-                region.mode,
-            ));
-        }
-
-        let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
-
-        let clip_sources = self.clip_store.insert(ClipSources::new(clip_sources));
-        let prim_index = self.prim_store.add_primitive(
-            &info.rect,
-            &info.local_clip.clip_rect(),
-            info.is_backface_visible && stacking_context.is_backface_visible,
-            clip_sources,
-            info.tag,
-            container,
-        );
-
-        prim_index
-    }
-
-    pub fn add_primitive_to_hit_testing_list(
-        &mut self,
-        info: &LayerPrimitiveInfo,
-        clip_and_scroll: ScrollNodeAndClipChain
-    ) {
-        let tag = match info.tag {
-            Some(tag) => tag,
-            None => return,
-        };
-
-        let new_item = HitTestingItem::new(tag, info);
-        match self.hit_testing_runs.last_mut() {
-            Some(&mut HitTestingRun(ref mut items, prev_clip_and_scroll))
-                if prev_clip_and_scroll == clip_and_scroll => {
-                items.push(new_item);
-                return;
-            }
-            _ => {}
-        }
-
-        self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
-    }
-
-    /// Add an already created primitive to the draw lists.
-    pub fn add_primitive_to_draw_list(
-        &mut self,
-        prim_index: PrimitiveIndex,
-        clip_and_scroll: ScrollNodeAndClipChain,
-    ) {
-        // Add primitive to the top-most Picture on the stack.
-        // TODO(gw): Let's consider removing the extra indirection
-        //           needed to get a specific primitive index...
-        let pic_prim_index = self.picture_stack.last().unwrap();
-        let metadata = &self.prim_store.cpu_metadata[pic_prim_index.0];
-        let pic = &mut self.prim_store.cpu_pictures[metadata.cpu_prim_index.0];
-        pic.add_primitive(
-            prim_index,
-            clip_and_scroll
-        );
-    }
-
-    /// Convenience interface that creates a primitive entry and adds it
-    /// to the draw list.
-    pub fn add_primitive(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        clip_sources: Vec<ClipSource>,
-        container: PrimitiveContainer,
-    ) -> PrimitiveIndex {
-        self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-        let prim_index = self.create_primitive(info, clip_sources, container);
-
-        self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-        prim_index
-    }
-
-    pub fn push_stacking_context(
-        &mut self,
-        pipeline_id: PipelineId,
-        composite_ops: CompositeOps,
-        transform_style: TransformStyle,
-        is_backface_visible: bool,
-        is_pipeline_root: bool,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        output_pipelines: &FastHashSet<PipelineId>,
-    ) {
-        // Construct the necessary set of Picture primitives
-        // to draw this stacking context.
-        let current_reference_frame_id = self.current_reference_frame_id();
-
-        // An arbitrary large clip rect. For now, we don't
-        // specify a clip specific to the stacking context.
-        // However, now that they are represented as Picture
-        // primitives, we can apply any kind of clip mask
-        // to them, as for a normal primitive. This is needed
-        // to correctly handle some CSS cases (see #1957).
-        let max_clip = LayerRect::max_rect();
-
-        // If there is no root picture, create one for the main framebuffer.
-        if self.sc_stack.is_empty() {
-            // Should be no pictures at all if the stack is empty...
-            debug_assert!(self.prim_store.cpu_pictures.is_empty());
-            debug_assert_eq!(transform_style, TransformStyle::Flat);
-
-            // This picture stores primitive runs for items on the
-            // main framebuffer.
-            let pic = PicturePrimitive::new_image(
-                None,
-                false,
-                pipeline_id,
-                current_reference_frame_id,
-                None,
-            );
-
-            // No clip sources needed for the main framebuffer.
-            let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-
-            // Add root picture primitive. The provided layer rect
-            // is zero, because we don't yet know the size of the
-            // picture. Instead, this is calculated recursively
-            // when we cull primitives.
-            let prim_index = self.prim_store.add_primitive(
-                &LayerRect::zero(),
-                &max_clip,
-                true,
-                clip_sources,
-                None,
-                PrimitiveContainer::Picture(pic),
-            );
-
-            self.picture_stack.push(prim_index);
-        } else if composite_ops.mix_blend_mode.is_some() && self.sc_stack.len() > 2 {
-            // If we have a mix-blend-mode, and we aren't the primary framebuffer,
-            // the stacking context needs to be isolated to blend correctly as per
-            // the CSS spec.
-            // TODO(gw): The way we detect not being the primary framebuffer (len > 2)
-            //           is hacky and depends on how we create a root stacking context
-            //           during flattening.
-            let current_pic_prim_index = self.picture_stack.last().unwrap();
-            let pic_cpu_prim_index = self.prim_store.cpu_metadata[current_pic_prim_index.0].cpu_prim_index;
-            let parent_pic = &mut self.prim_store.cpu_pictures[pic_cpu_prim_index.0];
-
-            match parent_pic.kind {
-                PictureKind::Image { ref mut composite_mode, .. } => {
-                    // If not already isolated for some other reason,
-                    // make this picture as isolated.
-                    if composite_mode.is_none() {
-                        *composite_mode = Some(PictureCompositeMode::Blit);
-                    }
-                }
-                PictureKind::TextShadow { .. } |
-                PictureKind::BoxShadow { .. } => {
-                    panic!("bug: text/box pictures invalid here");
-                }
-            }
-        }
-
-        // Get the transform-style of the parent stacking context,
-        // which determines if we *might* need to draw this on
-        // an intermediate surface for plane splitting purposes.
-        let parent_transform_style = match self.sc_stack.last() {
-            Some(sc) => sc.transform_style,
-            None => TransformStyle::Flat,
-        };
-
-        // If this is preserve-3d *or* the parent is, then this stacking
-        // context is participating in the 3d rendering context. In that
-        // case, hoist the picture up to the 3d rendering context
-        // container, so that it's rendered as a sibling with other
-        // elements in this context.
-        let participating_in_3d_context =
-            composite_ops.count() == 0 &&
-            (parent_transform_style == TransformStyle::Preserve3D ||
-             transform_style == TransformStyle::Preserve3D);
-
-        // If this is participating in a 3d context *and* the
-        // parent was not a 3d context, then this must be the
-        // element that establishes a new 3d context.
-        let establishes_3d_context =
-            participating_in_3d_context &&
-            parent_transform_style == TransformStyle::Flat;
-
-        let rendering_context_3d_prim_index = if establishes_3d_context {
-            // If establishing a 3d context, we need to add a picture
-            // that will be the container for all the planes and any
-            // un-transformed content.
-            let container = PicturePrimitive::new_image(
-                None,
-                false,
-                pipeline_id,
-                current_reference_frame_id,
-                None,
-            );
-
-            let clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-
-            let prim_index = self.prim_store.add_primitive(
-                &LayerRect::zero(),
-                &max_clip,
-                is_backface_visible,
-                clip_sources,
-                None,
-                PrimitiveContainer::Picture(container),
-            );
-
-            let parent_pic_prim_index = *self.picture_stack.last().unwrap();
-            let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
-            let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
-            pic.add_primitive(
-                prim_index,
-                clip_and_scroll,
-            );
-
-            self.picture_stack.push(prim_index);
-
-            Some(prim_index)
-        } else {
-            None
-        };
-
-        let mut parent_pic_prim_index = if !establishes_3d_context && participating_in_3d_context {
-            // If we're in a 3D context, we will parent the picture
-            // to the first stacking context we find that is a
-            // 3D rendering context container. This follows the spec
-            // by hoisting these items out into the same 3D context
-            // for plane splitting.
-            self.sc_stack
-                .iter()
-                .rev()
-                .find(|sc| sc.rendering_context_3d_prim_index.is_some())
-                .map(|sc| sc.rendering_context_3d_prim_index.unwrap())
-                .unwrap()
-        } else {
-            *self.picture_stack.last().unwrap()
-        };
-
-        // For each filter, create a new image with that composite mode.
-        for filter in composite_ops.filters.iter().rev() {
-            let src_prim = PicturePrimitive::new_image(
-                Some(PictureCompositeMode::Filter(*filter)),
-                false,
-                pipeline_id,
-                current_reference_frame_id,
-                None,
-            );
-            let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-
-            let src_prim_index = self.prim_store.add_primitive(
-                &LayerRect::zero(),
-                &max_clip,
-                is_backface_visible,
-                src_clip_sources,
-                None,
-                PrimitiveContainer::Picture(src_prim),
-            );
-
-            let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
-            parent_pic_prim_index = src_prim_index;
-            let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
-            pic.add_primitive(
-                src_prim_index,
-                clip_and_scroll,
-            );
-
-            self.picture_stack.push(src_prim_index);
-        }
-
-        // Same for mix-blend-mode.
-        if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
-            let src_prim = PicturePrimitive::new_image(
-                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
-                false,
-                pipeline_id,
-                current_reference_frame_id,
-                None,
-            );
-            let src_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-
-            let src_prim_index = self.prim_store.add_primitive(
-                &LayerRect::zero(),
-                &max_clip,
-                is_backface_visible,
-                src_clip_sources,
-                None,
-                PrimitiveContainer::Picture(src_prim),
-            );
-
-            let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
-            parent_pic_prim_index = src_prim_index;
-            let pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
-            pic.add_primitive(
-                src_prim_index,
-                clip_and_scroll,
-            );
-
-            self.picture_stack.push(src_prim_index);
-        }
-
-        // By default, this picture will be collapsed into
-        // the owning target.
-        let mut composite_mode = None;
-        let mut frame_output_pipeline_id = None;
-
-        // If this stacking context if the root of a pipeline, and the caller
-        // has requested it as an output frame, create a render task to isolate it.
-        if is_pipeline_root && output_pipelines.contains(&pipeline_id) {
-            composite_mode = Some(PictureCompositeMode::Blit);
-            frame_output_pipeline_id = Some(pipeline_id);
-        }
-
-        if participating_in_3d_context {
-            // TODO(gw): For now, as soon as this picture is in
-            //           a 3D context, we draw it to an intermediate
-            //           surface and apply plane splitting. However,
-            //           there is a large optimization opportunity here.
-            //           During culling, we can check if there is actually
-            //           perspective present, and skip the plane splitting
-            //           completely when that is not the case.
-            composite_mode = Some(PictureCompositeMode::Blit);
-        }
-
-        // Add picture for this actual stacking context contents to render into.
-        let sc_prim = PicturePrimitive::new_image(
-            composite_mode,
-            participating_in_3d_context,
-            pipeline_id,
-            current_reference_frame_id,
-            frame_output_pipeline_id,
-        );
-
-        let sc_clip_sources = self.clip_store.insert(ClipSources::new(Vec::new()));
-        let sc_prim_index = self.prim_store.add_primitive(
-            &LayerRect::zero(),
-            &max_clip,
-            is_backface_visible,
-            sc_clip_sources,
-            None,
-            PrimitiveContainer::Picture(sc_prim),
-        );
-
-        let pic_prim_index = self.prim_store.cpu_metadata[parent_pic_prim_index.0].cpu_prim_index;
-        let sc_pic = &mut self.prim_store.cpu_pictures[pic_prim_index.0];
-        sc_pic.add_primitive(
-            sc_prim_index,
-            clip_and_scroll,
-        );
-
-        // Add this as the top-most picture for primitives to be added to.
-        self.picture_stack.push(sc_prim_index);
-
-        // TODO(gw): This is super conservative. We can expand on this a lot
-        //           once all the picture code is in place and landed.
-        let allow_subpixel_aa = composite_ops.count() == 0 &&
-                                transform_style == TransformStyle::Flat;
-
-        // Push the SC onto the stack, so we know how to handle things in
-        // pop_stacking_context.
-        let sc = StackingContext {
-            composite_ops,
-            is_backface_visible,
-            pipeline_id,
-            allow_subpixel_aa,
-            transform_style,
-            rendering_context_3d_prim_index,
-        };
-
-        self.sc_stack.push(sc);
-    }
-
-    pub fn pop_stacking_context(&mut self) {
-        let sc = self.sc_stack.pop().unwrap();
-
-        // Always pop at least the main picture for this stacking context.
-        let mut pop_count = 1;
-
-        // Remove the picture for any filter/mix-blend-mode effects.
-        pop_count += sc.composite_ops.count();
-
-        // Remove the 3d context container if created
-        if sc.rendering_context_3d_prim_index.is_some() {
-            pop_count += 1;
-        }
-
-        for _ in 0 .. pop_count {
-            self.picture_stack.pop().expect("bug: mismatched picture stack");
-        }
-
-        // By the time the stacking context stack is empty, we should
-        // also have cleared the picture stack.
-        if self.sc_stack.is_empty() {
-            self.picture_stack.pop().expect("bug: picture stack invalid");
-            debug_assert!(self.picture_stack.is_empty());
-        }
-
-        assert!(
-            self.shadow_prim_stack.is_empty(),
-            "Found unpopped text shadows when popping stacking context!"
-        );
-    }
-
-    pub fn push_reference_frame(
-        &mut self,
-        reference_frame_id: ClipId,
-        parent_id: Option<ClipId>,
-        pipeline_id: PipelineId,
-        rect: &LayerRect,
-        source_transform: Option<PropertyBinding<LayoutTransform>>,
-        source_perspective: Option<LayoutTransform>,
-        origin_in_parent_reference_frame: LayerVector2D,
-        clip_scroll_tree: &mut ClipScrollTree,
-        id_to_index_mapper: &mut ClipIdToIndexMapper,
-    ) {
-        let node = ClipScrollNode::new_reference_frame(
-            parent_id,
-            rect,
-            source_transform,
-            source_perspective,
-            origin_in_parent_reference_frame,
-            pipeline_id,
-        );
-        clip_scroll_tree.add_node(node, reference_frame_id);
-        self.reference_frame_stack.push(reference_frame_id);
-
-        match parent_id {
-            Some(ref parent_id) =>
-                id_to_index_mapper.map_to_parent_clip_chain(reference_frame_id, parent_id),
-            _ => id_to_index_mapper.add(reference_frame_id, ClipChainIndex(0)),
-        }
-    }
-
-    pub fn current_reference_frame_id(&self) -> ClipId {
-        *self.reference_frame_stack.last().unwrap()
-    }
-
-    pub fn setup_viewport_offset(
-        &mut self,
-        inner_rect: DeviceUintRect,
-        device_pixel_scale: DevicePixelScale,
-        clip_scroll_tree: &mut ClipScrollTree,
-    ) {
-        let viewport_offset = (inner_rect.origin.to_vector().to_f32() / device_pixel_scale).round();
-        let root_id = clip_scroll_tree.root_reference_frame_id();
-        if let Some(root_node) = clip_scroll_tree.nodes.get_mut(&root_id) {
-            if let NodeType::ReferenceFrame(ref mut info) = root_node.node_type {
-                info.resolved_transform =
-                    LayerVector2D::new(viewport_offset.x, viewport_offset.y).into();
-            }
-        }
-    }
-
-    pub fn push_root(
-        &mut self,
-        pipeline_id: PipelineId,
-        viewport_size: &LayerSize,
-        content_size: &LayerSize,
-        clip_scroll_tree: &mut ClipScrollTree,
-        id_to_index_mapper: &mut ClipIdToIndexMapper,
-    ) -> ClipId {
-        let viewport_rect = LayerRect::new(LayerPoint::zero(), *viewport_size);
-        self.push_reference_frame(
-            ClipId::root_reference_frame(pipeline_id),
-            None,
-            pipeline_id,
-            &viewport_rect,
-            None,
-            None,
-            LayerVector2D::zero(),
-            clip_scroll_tree,
-            id_to_index_mapper,
-        );
-
-        let topmost_scrolling_node_id = ClipId::root_scroll_node(pipeline_id);
-        clip_scroll_tree.topmost_scrolling_node_id = topmost_scrolling_node_id;
-
-        self.add_scroll_frame(
-            topmost_scrolling_node_id,
-            clip_scroll_tree.root_reference_frame_id,
-            Some(ExternalScrollId(0, pipeline_id)),
-            pipeline_id,
-            &viewport_rect,
-            content_size,
-            ScrollSensitivity::ScriptAndInputEvents,
-            clip_scroll_tree,
-            id_to_index_mapper,
-        );
-
-        topmost_scrolling_node_id
-    }
-
-    pub fn add_clip_node(
-        &mut self,
-        new_node_id: ClipId,
-        parent_id: ClipId,
-        clip_region: ClipRegion,
-        clip_scroll_tree: &mut ClipScrollTree,
-        id_to_index_mapper: &mut ClipIdToIndexMapper,
-    ) {
-        let clip_rect = clip_region.main;
-        let clip_sources = ClipSources::from(clip_region);
-
-        debug_assert!(clip_sources.has_clips());
-        let handle = self.clip_store.insert(clip_sources);
-
-        let clip_chain_index = clip_scroll_tree.add_clip_node(
-            new_node_id,
-            parent_id,
-            handle,
-            clip_rect
-        );
-        id_to_index_mapper.add(new_node_id, clip_chain_index);
-    }
-
-    pub fn add_scroll_frame(
-        &mut self,
-        new_node_id: ClipId,
-        parent_id: ClipId,
-        external_id: Option<ExternalScrollId>,
-        pipeline_id: PipelineId,
-        frame_rect: &LayerRect,
-        content_size: &LayerSize,
-        scroll_sensitivity: ScrollSensitivity,
-        clip_scroll_tree: &mut ClipScrollTree,
-        id_to_index_mapper: &mut ClipIdToIndexMapper,
-    ) {
-        let node = ClipScrollNode::new_scroll_frame(
-            pipeline_id,
-            parent_id,
-            external_id,
-            frame_rect,
-            content_size,
-            scroll_sensitivity,
-        );
-
-        clip_scroll_tree.add_node(node, new_node_id);
-        id_to_index_mapper.map_to_parent_clip_chain(new_node_id, &parent_id);
-    }
-
-    pub fn pop_reference_frame(&mut self) {
-        self.reference_frame_stack.pop();
-    }
-
-    pub fn push_shadow(
-        &mut self,
-        shadow: Shadow,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-    ) {
-        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
-        let prim = PicturePrimitive::new_text_shadow(shadow, pipeline_id);
-
-        // Create an empty shadow primitive. Insert it into
-        // the draw lists immediately so that it will be drawn
-        // before any visual text elements that are added as
-        // part of this shadow context.
-        let prim_index = self.create_primitive(
-            info,
-            Vec::new(),
-            PrimitiveContainer::Picture(prim),
-        );
-
-        let pending = vec![(prim_index, clip_and_scroll)];
-        self.shadow_prim_stack.push((prim_index, pending));
-    }
-
-    pub fn pop_all_shadows(&mut self) {
-        assert!(self.shadow_prim_stack.len() > 0, "popped shadows, but none were present");
-
-        // Borrowcheck dance
-        let mut shadows = mem::replace(&mut self.shadow_prim_stack, Vec::new());
-        for (_, pending_primitives) in shadows.drain(..) {
-            // Push any fast-path shadows now
-            for (prim_index, clip_and_scroll) in pending_primitives {
-                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-            }
-        }
-
-        let mut pending_primitives = mem::replace(&mut self.pending_shadow_contents, Vec::new());
-        for (prim_index, clip_and_scroll, info) in pending_primitives.drain(..) {
-            self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
-            self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-        }
-
-        mem::replace(&mut self.pending_shadow_contents, pending_primitives);
-        mem::replace(&mut self.shadow_prim_stack, shadows);
-    }
-
-    pub fn add_solid_rectangle(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        color: ColorF,
-        segments: Option<BrushSegmentDescriptor>,
-    ) {
-        if color.a == 0.0 {
-            // Don't add transparent rectangles to the draw list, but do consider them for hit
-            // testing. This allows specifying invisible hit testing areas.
-            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-            return;
-        }
-
-        let prim = BrushPrimitive::new(
-            BrushKind::Solid {
-                color,
-            },
-            segments,
-        );
-
-        self.add_primitive(
-            clip_and_scroll,
-            info,
-            Vec::new(),
-            PrimitiveContainer::Brush(prim),
-        );
-    }
-
-    pub fn add_clear_rectangle(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-    ) {
-        let prim = BrushPrimitive::new(
-            BrushKind::Clear,
-            None,
-        );
-
-        self.add_primitive(
-            clip_and_scroll,
-            info,
-            Vec::new(),
-            PrimitiveContainer::Brush(prim),
-        );
-    }
-
-    pub fn add_scroll_bar(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        color: ColorF,
-        scrollbar_info: ScrollbarInfo,
-    ) {
-        if color.a == 0.0 {
-            return;
-        }
-
-        let prim = BrushPrimitive::new(
-            BrushKind::Solid {
-                color,
-            },
-            None,
-        );
-
-        let prim_index = self.add_primitive(
-            clip_and_scroll,
-            info,
-            Vec::new(),
-            PrimitiveContainer::Brush(prim),
-        );
-
-        self.scrollbar_prims.push(ScrollbarPrimitive {
-            prim_index,
-            clip_id: scrollbar_info.0,
-            frame_rect: scrollbar_info.1,
-        });
-    }
-
-    pub fn add_line(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        wavy_line_thickness: f32,
-        orientation: LineOrientation,
-        line_color: &ColorF,
-        style: LineStyle,
-    ) {
-        let line = BrushPrimitive::new(
-            BrushKind::Line {
-                wavy_line_thickness,
-                color: line_color.premultiplied(),
-                style,
-                orientation,
-            },
-            None,
-        );
-
-        let mut fast_shadow_prims = Vec::new();
-        for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
-            let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
-            let picture = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
-            match picture.kind {
-                PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
-                    fast_shadow_prims.push((idx, offset, color));
-                }
-                _ => {}
-            }
-        }
-
-        for (idx, shadow_offset, shadow_color) in fast_shadow_prims {
-            let line = BrushPrimitive::new(
-                BrushKind::Line {
-                    wavy_line_thickness,
-                    color: shadow_color.premultiplied(),
-                    style,
-                    orientation,
-                },
-                None,
-            );
-            let mut info = info.clone();
-            info.rect = info.rect.translate(&shadow_offset);
-            info.local_clip =
-              LocalClip::from(info.local_clip.clip_rect().translate(&shadow_offset));
-            let prim_index = self.create_primitive(
-                &info,
-                Vec::new(),
-                PrimitiveContainer::Brush(line),
-            );
-            self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
+            window_size,
+            config: flattener.config,
         }
-
-        let prim_index = self.create_primitive(
-            &info,
-            Vec::new(),
-            PrimitiveContainer::Brush(line),
-        );
-
-        if line_color.a > 0.0 {
-            if self.shadow_prim_stack.is_empty() {
-                self.add_primitive_to_hit_testing_list(&info, clip_and_scroll);
-                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-            } else {
-                self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
-            }
-        }
-
-        for &(shadow_prim_index, _) in &self.shadow_prim_stack {
-            let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
-            debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Picture);
-            let picture =
-                &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
-
-            match picture.kind {
-                // Only run real blurs here (fast path zero blurs are handled above).
-                PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
-                    picture.add_primitive(
-                        prim_index,
-                        clip_and_scroll,
-                    );
-                }
-                _ => {}
-            }
-        }
-    }
-
-    pub fn add_border(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        border_item: &BorderDisplayItem,
-        gradient_stops: ItemRange<GradientStop>,
-        gradient_stops_count: usize,
-    ) {
-        let rect = info.rect;
-        let create_segments = |outset: SideOffsets2D<f32>| {
-            // Calculate the modified rect as specific by border-image-outset
-            let origin = LayerPoint::new(rect.origin.x - outset.left, rect.origin.y - outset.top);
-            let size = LayerSize::new(
-                rect.size.width + outset.left + outset.right,
-                rect.size.height + outset.top + outset.bottom,
-            );
-            let rect = LayerRect::new(origin, size);
-
-            let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
-            let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
-
-            let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
-            let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
-
-            let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
-            let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
-
-            let br_outer = LayerPoint::new(
-                rect.origin.x + rect.size.width,
-                rect.origin.y + rect.size.height,
-            );
-            let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
-
-            // Build the list of gradient segments
-            vec![
-                // Top left
-                LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
-                // Top right
-                LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
-                // Bottom right
-                LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
-                // Bottom left
-                LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
-                // Top
-                LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
-                // Bottom
-                LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
-                // Left
-                LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
-                // Right
-                LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
-            ]
-        };
-
-        match border_item.details {
-            BorderDetails::Image(ref border) => {
-                // Calculate the modified rect as specific by border-image-outset
-                let origin = LayerPoint::new(
-                    rect.origin.x - border.outset.left,
-                    rect.origin.y - border.outset.top,
-                );
-                let size = LayerSize::new(
-                    rect.size.width + border.outset.left + border.outset.right,
-                    rect.size.height + border.outset.top + border.outset.bottom,
-                );
-                let rect = LayerRect::new(origin, size);
-
-                // Calculate the local texel coords of the slices.
-                let px0 = 0.0;
-                let px1 = border.patch.slice.left as f32;
-                let px2 = border.patch.width as f32 - border.patch.slice.right as f32;
-                let px3 = border.patch.width as f32;
-
-                let py0 = 0.0;
-                let py1 = border.patch.slice.top as f32;
-                let py2 = border.patch.height as f32 - border.patch.slice.bottom as f32;
-                let py3 = border.patch.height as f32;
-
-                let tl_outer = LayerPoint::new(rect.origin.x, rect.origin.y);
-                let tl_inner = tl_outer + vec2(border_item.widths.left, border_item.widths.top);
-
-                let tr_outer = LayerPoint::new(rect.origin.x + rect.size.width, rect.origin.y);
-                let tr_inner = tr_outer + vec2(-border_item.widths.right, border_item.widths.top);
-
-                let bl_outer = LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height);
-                let bl_inner = bl_outer + vec2(border_item.widths.left, -border_item.widths.bottom);
-
-                let br_outer = LayerPoint::new(
-                    rect.origin.x + rect.size.width,
-                    rect.origin.y + rect.size.height,
-                );
-                let br_inner = br_outer - vec2(border_item.widths.right, border_item.widths.bottom);
-
-                fn add_segment(
-                    segments: &mut Vec<ImageBorderSegment>,
-                    rect: LayerRect,
-                    uv_rect: TexelRect,
-                    repeat_horizontal: RepeatMode,
-                    repeat_vertical: RepeatMode) {
-                    if uv_rect.uv1.x > uv_rect.uv0.x &&
-                       uv_rect.uv1.y > uv_rect.uv0.y {
-                        segments.push(ImageBorderSegment::new(
-                            rect,
-                            uv_rect,
-                            repeat_horizontal,
-                            repeat_vertical,
-                        ));
-                    }
-                }
-
-                // Build the list of image segments
-                let mut segments = vec![];
-
-                // Top left
-                add_segment(
-                    &mut segments,
-                    LayerRect::from_floats(tl_outer.x, tl_outer.y, tl_inner.x, tl_inner.y),
-                    TexelRect::new(px0, py0, px1, py1),
-                    RepeatMode::Stretch,
-                    RepeatMode::Stretch
-                );
-                // Top right
-                add_segment(
-                    &mut segments,
-                    LayerRect::from_floats(tr_inner.x, tr_outer.y, tr_outer.x, tr_inner.y),
-                    TexelRect::new(px2, py0, px3, py1),
-                    RepeatMode::Stretch,
-                    RepeatMode::Stretch
-                );
-                // Bottom right
-                add_segment(
-                    &mut segments,
-                    LayerRect::from_floats(br_inner.x, br_inner.y, br_outer.x, br_outer.y),
-                    TexelRect::new(px2, py2, px3, py3),
-                    RepeatMode::Stretch,
-                    RepeatMode::Stretch
-                );
-                // Bottom left
-                add_segment(
-                    &mut segments,
-                    LayerRect::from_floats(bl_outer.x, bl_inner.y, bl_inner.x, bl_outer.y),
-                    TexelRect::new(px0, py2, px1, py3),
-                    RepeatMode::Stretch,
-                    RepeatMode::Stretch
-                );
-
-                // Center
-                if border.fill {
-                    add_segment(
-                        &mut segments,
-                        LayerRect::from_floats(tl_inner.x, tl_inner.y, tr_inner.x, bl_inner.y),
-                        TexelRect::new(px1, py1, px2, py2),
-                        border.repeat_horizontal,
-                        border.repeat_vertical
-                    );
-                }
-
-                // Add edge segments.
-
-                // Top
-                add_segment(
-                    &mut segments,
-                    LayerRect::from_floats(tl_inner.x, tl_outer.y, tr_inner.x, tl_inner.y),
-                    TexelRect::new(px1, py0, px2, py1),
-                    border.repeat_horizontal,
-                    RepeatMode::Stretch,
-                );
-                // Bottom
-                add_segment(
-                    &mut segments,
-                    LayerRect::from_floats(bl_inner.x, bl_inner.y, br_inner.x, bl_outer.y),
-                    TexelRect::new(px1, py2, px2, py3),
-                    border.repeat_horizontal,
-                    RepeatMode::Stretch,
-                );
-                // Left
-                add_segment(
-                    &mut segments,
-                    LayerRect::from_floats(tl_outer.x, tl_inner.y, tl_inner.x, bl_inner.y),
-                    TexelRect::new(px0, py1, px1, py2),
-                    RepeatMode::Stretch,
-                    border.repeat_vertical,
-                );
-                // Right
-                add_segment(
-                    &mut segments,
-                    LayerRect::from_floats(tr_inner.x, tr_inner.y, br_outer.x, br_inner.y),
-                    TexelRect::new(px2, py1, px3, py2),
-                    RepeatMode::Stretch,
-                    border.repeat_vertical,
-                );
-
-                for segment in segments {
-                    let mut info = info.clone();
-                    info.rect = segment.geom_rect;
-                    self.add_image(
-                        clip_and_scroll,
-                        &info,
-                        segment.stretch_size,
-                        segment.tile_spacing,
-                        Some(segment.sub_rect),
-                        border.image_key,
-                        ImageRendering::Auto,
-                        AlphaType::PremultipliedAlpha,
-                        None,
-                    );
-                }
-            }
-            BorderDetails::Normal(ref border) => {
-                self.add_normal_border(info, border, &border_item.widths, clip_and_scroll);
-            }
-            BorderDetails::Gradient(ref border) => for segment in create_segments(border.outset) {
-                let segment_rel = segment.origin - rect.origin;
-                let mut info = info.clone();
-                info.rect = segment;
-
-                self.add_gradient(
-                    clip_and_scroll,
-                    &info,
-                    border.gradient.start_point - segment_rel,
-                    border.gradient.end_point - segment_rel,
-                    gradient_stops,
-                    gradient_stops_count,
-                    border.gradient.extend_mode,
-                    segment.size,
-                    LayerSize::zero(),
-                );
-            },
-            BorderDetails::RadialGradient(ref border) => {
-                for segment in create_segments(border.outset) {
-                    let segment_rel = segment.origin - rect.origin;
-                    let mut info = info.clone();
-                    info.rect = segment;
-
-                    self.add_radial_gradient(
-                        clip_and_scroll,
-                        &info,
-                        border.gradient.start_center - segment_rel,
-                        border.gradient.start_radius,
-                        border.gradient.end_center - segment_rel,
-                        border.gradient.end_radius,
-                        border.gradient.ratio_xy,
-                        gradient_stops,
-                        border.gradient.extend_mode,
-                        segment.size,
-                        LayerSize::zero(),
-                    );
-                }
-            }
-        }
-    }
-
-    fn add_gradient_impl(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        start_point: LayerPoint,
-        end_point: LayerPoint,
-        stops: ItemRange<GradientStop>,
-        stops_count: usize,
-        extend_mode: ExtendMode,
-        gradient_index: CachedGradientIndex,
-    ) {
-        // Try to ensure that if the gradient is specified in reverse, then so long as the stops
-        // are also supplied in reverse that the rendered result will be equivalent. To do this,
-        // a reference orientation for the gradient line must be chosen, somewhat arbitrarily, so
-        // just designate the reference orientation as start < end. Aligned gradient rendering
-        // manages to produce the same result regardless of orientation, so don't worry about
-        // reversing in that case.
-        let reverse_stops = start_point.x > end_point.x ||
-            (start_point.x == end_point.x && start_point.y > end_point.y);
-
-        // To get reftests exactly matching with reverse start/end
-        // points, it's necessary to reverse the gradient
-        // line in some cases.
-        let (sp, ep) = if reverse_stops {
-            (end_point, start_point)
-        } else {
-            (start_point, end_point)
-        };
-
-        let prim = BrushPrimitive::new(
-            BrushKind::LinearGradient {
-                stops_range: stops,
-                stops_count,
-                extend_mode,
-                reverse_stops,
-                start_point: sp,
-                end_point: ep,
-                gradient_index,
-            },
-            None,
-        );
-
-        let prim = PrimitiveContainer::Brush(prim);
-
-        self.add_primitive(clip_and_scroll, info, Vec::new(), prim);
-    }
-
-    pub fn add_gradient(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        start_point: LayerPoint,
-        end_point: LayerPoint,
-        stops: ItemRange<GradientStop>,
-        stops_count: usize,
-        extend_mode: ExtendMode,
-        tile_size: LayerSize,
-        tile_spacing: LayerSize,
-    ) {
-        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
-        self.cached_gradients.push(CachedGradient::new());
-
-        let prim_infos = info.decompose(
-            tile_size,
-            tile_spacing,
-            64 * 64,
-        );
-
-        if prim_infos.is_empty() {
-            self.add_gradient_impl(
-                clip_and_scroll,
-                info,
-                start_point,
-                end_point,
-                stops,
-                stops_count,
-                extend_mode,
-                gradient_index,
-            );
-        } else {
-            for prim_info in prim_infos {
-                self.add_gradient_impl(
-                    clip_and_scroll,
-                    &prim_info,
-                    start_point,
-                    end_point,
-                    stops,
-                    stops_count,
-                    extend_mode,
-                    gradient_index,
-                );
-            }
-        }
-    }
-
-    fn add_radial_gradient_impl(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        start_center: LayerPoint,
-        start_radius: f32,
-        end_center: LayerPoint,
-        end_radius: f32,
-        ratio_xy: f32,
-        stops: ItemRange<GradientStop>,
-        extend_mode: ExtendMode,
-        gradient_index: CachedGradientIndex,
-    ) {
-        let prim = BrushPrimitive::new(
-            BrushKind::RadialGradient {
-                stops_range: stops,
-                extend_mode,
-                start_center,
-                end_center,
-                start_radius,
-                end_radius,
-                ratio_xy,
-                gradient_index,
-            },
-            None,
-        );
-
-        self.add_primitive(
-            clip_and_scroll,
-            info,
-            Vec::new(),
-            PrimitiveContainer::Brush(prim),
-        );
-    }
-
-    pub fn add_radial_gradient(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        start_center: LayerPoint,
-        start_radius: f32,
-        end_center: LayerPoint,
-        end_radius: f32,
-        ratio_xy: f32,
-        stops: ItemRange<GradientStop>,
-        extend_mode: ExtendMode,
-        tile_size: LayerSize,
-        tile_spacing: LayerSize,
-    ) {
-        let gradient_index = CachedGradientIndex(self.cached_gradients.len());
-        self.cached_gradients.push(CachedGradient::new());
-
-        let prim_infos = info.decompose(
-            tile_size,
-            tile_spacing,
-            64 * 64,
-        );
-
-        if prim_infos.is_empty() {
-            self.add_radial_gradient_impl(
-                clip_and_scroll,
-                info,
-                start_center,
-                start_radius,
-                end_center,
-                end_radius,
-                ratio_xy,
-                stops,
-                extend_mode,
-                gradient_index,
-            );
-        } else {
-            for prim_info in prim_infos {
-                self.add_radial_gradient_impl(
-                    clip_and_scroll,
-                    &prim_info,
-                    start_center,
-                    start_radius,
-                    end_center,
-                    end_radius,
-                    ratio_xy,
-                    stops,
-                    extend_mode,
-                    gradient_index,
-                );
-            }
-        }
-    }
-
-    pub fn add_text(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        run_offset: LayoutVector2D,
-        info: &LayerPrimitiveInfo,
-        font: &FontInstance,
-        text_color: &ColorF,
-        glyph_range: ItemRange<GlyphInstance>,
-        glyph_count: usize,
-        glyph_options: Option<GlyphOptions>,
-    ) {
-        // Trivial early out checks
-        if font.size.0 <= 0 {
-            return;
-        }
-
-        // Sanity check - anything with glyphs bigger than this
-        // is probably going to consume too much memory to render
-        // efficiently anyway. This is specifically to work around
-        // the font_advance.html reftest, which creates a very large
-        // font as a crash test - the rendering is also ignored
-        // by the azure renderer.
-        if font.size >= Au::from_px(4096) {
-            return;
-        }
-
-        // TODO(gw): Use a proper algorithm to select
-        // whether this item should be rendered with
-        // subpixel AA!
-        let mut render_mode = self.config
-            .default_font_render_mode
-            .limit_by(font.render_mode);
-        let mut flags = font.flags;
-        if let Some(options) = glyph_options {
-            render_mode = render_mode.limit_by(options.render_mode);
-            flags |= options.flags;
-        }
-
-        // There are some conditions under which we can't use
-        // subpixel text rendering, even if enabled.
-        if render_mode == FontRenderMode::Subpixel {
-            // text on a picture that has filters
-            // (e.g. opacity) can't use sub-pixel.
-            // TODO(gw): It's possible we can relax this in
-            //           the future, if we modify the way
-            //           we handle subpixel blending.
-            if let Some(ref stacking_context) = self.sc_stack.last() {
-                if !stacking_context.allow_subpixel_aa {
-                    render_mode = FontRenderMode::Alpha;
-                }
-            }
-        }
-
-        let prim_font = FontInstance::new(
-            font.font_key,
-            font.size,
-            *text_color,
-            font.bg_color,
-            render_mode,
-            font.subpx_dir,
-            flags,
-            font.platform_options,
-            font.variations.clone(),
-        );
-        let prim = TextRunPrimitiveCpu {
-            font: prim_font,
-            glyph_range,
-            glyph_count,
-            glyph_gpu_blocks: Vec::new(),
-            glyph_keys: Vec::new(),
-            offset: run_offset,
-            shadow: false,
-        };
-
-        // Text shadows that have a blur radius of 0 need to be rendered as normal
-        // text elements to get pixel perfect results for reftests. It's also a big
-        // performance win to avoid blurs and render target allocations where
-        // possible. For any text shadows that have zero blur, create a normal text
-        // primitive with the shadow's color and offset. These need to be added
-        // *before* the visual text primitive in order to get the correct paint
-        // order. Store them in a Vec first to work around borrowck issues.
-        // TODO(gw): Refactor to avoid having to store them in a Vec first.
-        let mut fast_shadow_prims = Vec::new();
-        for (idx, &(shadow_prim_index, _)) in self.shadow_prim_stack.iter().enumerate() {
-            let shadow_metadata = &self.prim_store.cpu_metadata[shadow_prim_index.0];
-            let picture_prim = &self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
-            match picture_prim.kind {
-                PictureKind::TextShadow { offset, color, blur_radius, .. } if blur_radius == 0.0 => {
-                    let mut text_prim = prim.clone();
-                    text_prim.font.color = color.into();
-                    text_prim.shadow = true;
-                    text_prim.offset += offset;
-                    fast_shadow_prims.push((idx, text_prim));
-                }
-                _ => {}
-            }
-        }
-
-        for (idx, text_prim) in fast_shadow_prims {
-            let rect = info.rect;
-            let mut info = info.clone();
-            info.rect = rect.translate(&text_prim.offset);
-            info.local_clip =
-              LocalClip::from(info.local_clip.clip_rect().translate(&text_prim.offset));
-            let prim_index = self.create_primitive(
-                &info,
-                Vec::new(),
-                PrimitiveContainer::TextRun(text_prim),
-            );
-            self.shadow_prim_stack[idx].1.push((prim_index, clip_and_scroll));
-        }
-
-        // Create (and add to primitive store) the primitive that will be
-        // used for both the visual element and also the shadow(s).
-        let prim_index = self.create_primitive(
-            info,
-            Vec::new(),
-            PrimitiveContainer::TextRun(prim),
-        );
-
-        // Only add a visual element if it can contribute to the scene.
-        if text_color.a > 0.0 {
-            if self.shadow_prim_stack.is_empty() {
-                self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-                self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
-            } else {
-                self.pending_shadow_contents.push((prim_index, clip_and_scroll, *info));
-            }
-        }
-
-        // Now add this primitive index to all the currently active text shadow
-        // primitives. Although we're adding the indices *after* the visual
-        // primitive here, they will still draw before the visual text, since
-        // the shadow primitive itself has been added to the draw cmd
-        // list *before* the visual element, during push_shadow. We need
-        // the primitive index of the visual element here before we can add
-        // the indices as sub-primitives to the shadow primitives.
-        for &(shadow_prim_index, _) in &self.shadow_prim_stack {
-            let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
-            debug_assert_eq!(shadow_metadata.prim_kind, PrimitiveKind::Picture);
-            let picture =
-                &mut self.prim_store.cpu_pictures[shadow_metadata.cpu_prim_index.0];
-
-            match picture.kind {
-                // Only run real blurs here (fast path zero blurs are handled above).
-                PictureKind::TextShadow { blur_radius, .. } if blur_radius > 0.0 => {
-                    picture.add_primitive(
-                        prim_index,
-                        clip_and_scroll,
-                    );
-                }
-                _ => {}
-            }
-        }
-    }
-
-    pub fn add_image(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        stretch_size: LayerSize,
-        mut tile_spacing: LayerSize,
-        sub_rect: Option<TexelRect>,
-        image_key: ImageKey,
-        image_rendering: ImageRendering,
-        alpha_type: AlphaType,
-        tile_offset: Option<TileOffset>,
-    ) {
-        // If the tile spacing is the same as the rect size,
-        // then it is effectively zero. We use this later on
-        // in prim_store to detect if an image can be considered
-        // opaque.
-        if tile_spacing == info.rect.size {
-            tile_spacing = LayerSize::zero();
-        }
-
-        let request = ImageRequest {
-            key: image_key,
-            rendering: image_rendering,
-            tile: tile_offset,
-        };
-
-        // See if conditions are met to run through the new
-        // image brush shader, which supports segments.
-        if tile_spacing == LayerSize::zero() &&
-           stretch_size == info.rect.size &&
-           sub_rect.is_none() &&
-           tile_offset.is_none() {
-            let prim = BrushPrimitive::new(
-                BrushKind::Image {
-                    request,
-                    current_epoch: Epoch::invalid(),
-                    alpha_type,
-                },
-                None,
-            );
-
-            self.add_primitive(
-                clip_and_scroll,
-                info,
-                Vec::new(),
-                PrimitiveContainer::Brush(prim),
-            );
-        } else {
-            let prim_cpu = ImagePrimitiveCpu {
-                tile_spacing,
-                alpha_type,
-                stretch_size,
-                current_epoch: Epoch::invalid(),
-                source: ImageSource::Default,
-                key: ImageCacheKey {
-                    request,
-                    texel_rect: sub_rect.map(|texel_rect| {
-                        DeviceIntRect::new(
-                            DeviceIntPoint::new(
-                                texel_rect.uv0.x as i32,
-                                texel_rect.uv0.y as i32,
-                            ),
-                            DeviceIntSize::new(
-                                (texel_rect.uv1.x - texel_rect.uv0.x) as i32,
-                                (texel_rect.uv1.y - texel_rect.uv0.y) as i32,
-                            ),
-                        )
-                    }),
-                },
-            };
-
-            self.add_primitive(
-                clip_and_scroll,
-                info,
-                Vec::new(),
-                PrimitiveContainer::Image(prim_cpu),
-            );
-        }
-    }
-
-    pub fn add_yuv_image(
-        &mut self,
-        clip_and_scroll: ScrollNodeAndClipChain,
-        info: &LayerPrimitiveInfo,
-        yuv_data: YuvData,
-        color_space: YuvColorSpace,
-        image_rendering: ImageRendering,
-    ) {
-        let format = yuv_data.get_format();
-        let yuv_key = match yuv_data {
-            YuvData::NV12(plane_0, plane_1) => [plane_0, plane_1, ImageKey::DUMMY],
-            YuvData::PlanarYCbCr(plane_0, plane_1, plane_2) => [plane_0, plane_1, plane_2],
-            YuvData::InterleavedYCbCr(plane_0) => [plane_0, ImageKey::DUMMY, ImageKey::DUMMY],
-        };
-
-        let prim = BrushPrimitive::new(
-            BrushKind::YuvImage {
-                yuv_key,
-                format,
-                color_space,
-                image_rendering,
-            },
-            None,
-        );
-
-        self.add_primitive(
-            clip_and_scroll,
-            info,
-            Vec::new(),
-            PrimitiveContainer::Brush(prim),
-        );
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
-        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
+        pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         profile_counters: &mut FrameProfileCounters,
         device_pixel_scale: DevicePixelScale,
         scene_properties: &SceneProperties,
         local_clip_rects: &mut Vec<LayerRect>,
         node_data: &[ClipScrollNodeData],
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.cpu_pictures.is_empty() {
             return None
         }
 
         // The root picture is always the first one added.
-        let root_clip_scroll_node = &clip_scroll_tree.nodes[&clip_scroll_tree.root_reference_frame_id()];
+        let root_clip_scroll_node =
+            &clip_scroll_tree.nodes[clip_scroll_tree.root_reference_frame_index().0];
 
         let display_list = &pipelines
             .get(&root_clip_scroll_node.pipeline_id)
             .expect("No display list?")
             .display_list;
 
-        let frame_context = FrameContext {
+        let frame_context = FrameBuildingContext {
             device_pixel_scale,
             scene_properties,
             pipelines,
             screen_rect: self.screen_rect.to_i32(),
             clip_scroll_tree,
             node_data,
         };
 
-        let mut frame_state = FrameState {
+        let mut frame_state = FrameBuildingState {
             render_tasks,
             profile_counters,
             clip_store: &mut self.clip_store,
             local_clip_rects,
             resource_cache,
             gpu_cache,
             cached_gradients: &mut self.cached_gradients,
         };
 
         let pic_context = PictureContext {
             pipeline_id: root_clip_scroll_node.pipeline_id,
             perform_culling: true,
             prim_runs: mem::replace(&mut self.prim_store.cpu_pictures[0].runs, Vec::new()),
-            original_reference_frame_id: None,
+            original_reference_frame_index: None,
             display_list,
             draw_text_transformed: true,
             inv_world_transform: None,
         };
 
         let mut pic_state = PictureState::new();
 
         self.prim_store.reset_prim_visibility();
@@ -1776,17 +239,17 @@ impl FrameBuilder {
         Some(render_task_id)
     }
 
     fn update_scroll_bars(&mut self, clip_scroll_tree: &ClipScrollTree, gpu_cache: &mut GpuCache) {
         static SCROLLBAR_PADDING: f32 = 8.0;
 
         for scrollbar_prim in &self.scrollbar_prims {
             let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0];
-            let scroll_frame = &clip_scroll_tree.nodes[&scrollbar_prim.clip_id];
+            let scroll_frame = &clip_scroll_tree.nodes[scrollbar_prim.scroll_frame_index.0];
 
             // Invalidate what's in the cache so it will get rebuilt.
             gpu_cache.invalidate(&metadata.gpu_location);
 
             let scrollable_distance = scroll_frame.scrollable_size().height;
             if scrollable_distance <= 0.0 {
                 metadata.local_clip_rect.size = LayerSize::zero();
                 continue;
@@ -1806,28 +269,27 @@ impl FrameBuilder {
     }
 
     pub fn build(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         frame_id: FrameId,
         clip_scroll_tree: &mut ClipScrollTree,
-        pipelines: &FastHashMap<PipelineId, ScenePipeline>,
-        window_size: DeviceUintSize,
+        pipelines: &FastHashMap<PipelineId, Arc<ScenePipeline>>,
         device_pixel_scale: DevicePixelScale,
         layer: DocumentLayer,
         pan: WorldPoint,
         texture_cache_profile: &mut TextureCacheProfileCounters,
         gpu_cache_profile: &mut GpuCacheProfileCounters,
         scene_properties: &SceneProperties,
     ) -> Frame {
         profile_scope!("build");
         debug_assert!(
-            DeviceUintRect::new(DeviceUintPoint::zero(), window_size)
+            DeviceUintRect::new(DeviceUintPoint::zero(), self.window_size)
                 .contains_rect(&self.screen_rect)
         );
 
         let mut profile_counters = FrameProfileCounters::new();
         profile_counters
             .total_primitives
             .set(self.prim_store.prim_count());
 
@@ -1921,17 +383,17 @@ impl FrameBuilder {
 
         let gpu_cache_frame_id = gpu_cache.end_frame(gpu_cache_profile);
 
         render_tasks.build();
 
         resource_cache.end_frame();
 
         Frame {
-            window_size,
+            window_size: self.window_size,
             inner_rect: self.screen_rect,
             device_pixel_ratio: device_pixel_scale.0,
             background_color: self.background_color,
             layer,
             profile_counters,
             passes,
             node_data,
             clip_chain_local_clip_rects,
@@ -1947,71 +409,8 @@ impl FrameBuilder {
         HitTester::new(
             &self.hit_testing_runs,
             &clip_scroll_tree,
             &self.clip_store
         )
     }
 }
 
-trait PrimitiveInfoTiler {
-    fn decompose(
-        &self,
-        tile_size: LayerSize,
-        tile_spacing: LayerSize,
-        max_prims: usize,
-    ) -> Vec<LayerPrimitiveInfo>;
-}
-
-impl PrimitiveInfoTiler for LayerPrimitiveInfo {
-    fn decompose(
-        &self,
-        tile_size: LayerSize,
-        tile_spacing: LayerSize,
-        max_prims: usize,
-    ) -> Vec<LayerPrimitiveInfo> {
-        let mut prims = Vec::new();
-        let tile_repeat = tile_size + tile_spacing;
-
-        if tile_repeat.width <= 0.0 ||
-           tile_repeat.height <= 0.0 {
-            return prims;
-        }
-
-        if tile_repeat.width < self.rect.size.width ||
-           tile_repeat.height < self.rect.size.height {
-            let local_clip = self.local_clip.clip_by(&self.rect);
-            let rect_p0 = self.rect.origin;
-            let rect_p1 = self.rect.bottom_right();
-
-            let mut y0 = rect_p0.y;
-            while y0 < rect_p1.y {
-                let mut x0 = rect_p0.x;
-
-                while x0 < rect_p1.x {
-                    prims.push(LayerPrimitiveInfo {
-                        rect: LayerRect::new(
-                            LayerPoint::new(x0, y0),
-                            tile_size,
-                        ),
-                        local_clip,
-                        is_backface_visible: self.is_backface_visible,
-                        tag: self.tag,
-                    });
-
-                    // Mostly a safety against a crazy number of primitives
-                    // being generated. If we exceed that amount, just bail
-                    // out and only draw the maximum amount.
-                    if prims.len() > max_prims {
-                        warn!("too many prims found due to repeat/tile. dropping extra prims!");
-                        return prims;
-                    }
-
-                    x0 += tile_repeat.width;
-                }
-
-                y0 += tile_repeat.height;
-            }
-        }
-
-        prims
-    }
-}
--- a/gfx/webrender/src/glyph_rasterizer.rs
+++ b/gfx/webrender/src/glyph_rasterizer.rs
@@ -397,17 +397,17 @@ impl GlyphRasterizer {
 
         // select glyphs that have not been requested yet.
         for key in glyph_keys {
             match glyph_key_cache.entry(key.clone()) {
                 Entry::Occupied(mut entry) => {
                     if let Ok(Some(ref mut glyph_info)) = *entry.get_mut() {
                         if texture_cache.request(&mut glyph_info.texture_cache_handle, gpu_cache) {
                             // This case gets hit when we have already rasterized
-                            // the glyph and stored it in CPU memory, the the glyph
+                            // the glyph and stored it in CPU memory, but the glyph
                             // has been evicted from the texture cache. In which case
                             // we need to re-upload it to the GPU.
                             texture_cache.update(
                                 &mut glyph_info.texture_cache_handle,
                                 ImageDescriptor {
                                     width: glyph_info.size.width,
                                     height: glyph_info.size.height,
                                     stride: None,
--- a/gfx/webrender/src/gpu_types.rs
+++ b/gfx/webrender/src/gpu_types.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{LayerToWorldTransform};
-use gpu_cache::GpuCacheAddress;
+use api::{DevicePoint, LayerToWorldTransform, PremultipliedColorF};
+use gpu_cache::{GpuCacheAddress, GpuDataRequest};
 use prim_store::EdgeAaSegmentMask;
 use render_task::RenderTaskAddress;
 
 // Contains type that must exactly match the same structures declared in GLSL.
 
 #[repr(i32)]
 #[derive(Debug, Copy, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -198,17 +198,16 @@ impl From<BrushInstance> for PrimitiveIn
 // Defines how a brush image is stretched onto the primitive.
 // In the future, we may draw with segments for each portion
 // of the primitive, in which case this will be redundant.
 #[repr(C)]
 #[derive(Debug, Copy, Clone)]
 pub enum BrushImageKind {
     Simple = 0,     // A normal rect
     NinePatch = 1,  // A nine-patch image (stretch inside segments)
-    Mirror = 2,     // A top left corner only (mirror across x/y axes)
 }
 
 #[derive(Copy, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub struct ClipScrollNodeIndex(pub u32);
 
@@ -240,8 +239,38 @@ pub struct ClipChainRectIndex(pub usize)
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[repr(C)]
 pub enum PictureType {
     Image = 1,
     TextShadow = 2,
     BoxShadow = 3,
 }
+
+#[derive(Debug, Copy, Clone)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+pub struct ImageSource {
+    pub p0: DevicePoint,
+    pub p1: DevicePoint,
+    pub texture_layer: f32,
+    pub user_data: [f32; 3],
+    pub color: PremultipliedColorF,
+}
+
+impl ImageSource {
+    pub fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
+        request.push([
+            self.p0.x,
+            self.p0.y,
+            self.p1.x,
+            self.p1.y,
+        ]);
+        request.push([
+            self.texture_layer,
+            self.user_data[0],
+            self.user_data[1],
+            self.user_data[2],
+        ]);
+        request.push(self.color);
+    }
+}
\ No newline at end of file
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -1,25 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderRadius, ClipId, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag};
-use api::{LayerPoint, LayerPrimitiveInfo, LayerRect, LocalClip, PipelineId, WorldPoint};
+use api::{BorderRadius, ClipMode, HitTestFlags, HitTestItem, HitTestResult, ItemTag, LayerPoint};
+use api::{LayerPrimitiveInfo, LayerRect, LocalClip, PipelineId, WorldPoint};
 use clip::{ClipSource, ClipStore, Contains, rounded_rectangle_contains_point};
 use clip_scroll_node::{ClipScrollNode, NodeType};
-use clip_scroll_tree::{ClipChainIndex, ClipScrollTree};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, ClipScrollTree};
 use internal_types::FastHashMap;
 use prim_store::ScrollNodeAndClipChain;
 use util::LayerToWorldFastTransform;
 
 /// A copy of important clip scroll node data to use during hit testing. This a copy of
 /// data from the ClipScrollTree that will persist as a new frame is under construction,
 /// allowing hit tests consistent with the currently rendered frame.
 pub struct HitTestClipScrollNode {
+    /// The pipeline id of this node.
+    pipeline_id: PipelineId,
+
     /// A particular point must be inside all of these regions to be considered clipped in
     /// for the purposes of a hit test.
     regions: Vec<HitTestRegion>,
 
     /// World transform for content transformed by this node.
     world_content_transform: LayerToWorldFastTransform,
 
     /// World viewport transform for content transformed by this node.
@@ -32,17 +35,17 @@ pub struct HitTestClipScrollNode {
 /// A description of a clip chain in the HitTester. This is used to describe
 /// hierarchical clip scroll nodes as well as ClipChains, so that they can be
 /// handled the same way during hit testing. Once we represent all ClipChains
 /// using ClipChainDescriptors, we can get rid of this and just use the
 /// ClipChainDescriptor here.
 #[derive(Clone)]
 struct HitTestClipChainDescriptor {
     parent: Option<ClipChainIndex>,
-    clips: Vec<ClipId>,
+    clips: Vec<ClipScrollNodeIndex>,
 }
 
 impl HitTestClipChainDescriptor {
     fn empty() -> HitTestClipChainDescriptor {
         HitTestClipChainDescriptor {
             parent: None,
             clips: Vec::new(),
         }
@@ -83,30 +86,32 @@ impl HitTestRegion {
             &HitTestRegion::RoundedRectangle(rect, radii, ClipMode::ClipOut) =>
                 !rounded_rectangle_contains_point(point, &rect, &radii),
         }
     }
 }
 
 pub struct HitTester {
     runs: Vec<HitTestingRun>,
-    nodes: FastHashMap<ClipId, HitTestClipScrollNode>,
+    nodes: Vec<HitTestClipScrollNode>,
     clip_chains: Vec<HitTestClipChainDescriptor>,
+    pipeline_root_nodes: FastHashMap<PipelineId, ClipScrollNodeIndex>,
 }
 
 impl HitTester {
     pub fn new(
         runs: &Vec<HitTestingRun>,
         clip_scroll_tree: &ClipScrollTree,
         clip_store: &ClipStore
     ) -> HitTester {
         let mut hit_tester = HitTester {
             runs: runs.clone(),
-            nodes: FastHashMap::default(),
+            nodes: Vec::new(),
             clip_chains: Vec::new(),
+            pipeline_root_nodes: FastHashMap::default(),
         };
         hit_tester.read_clip_scroll_tree(clip_scroll_tree, clip_store);
         hit_tester
     }
 
     fn read_clip_scroll_tree(
         &mut self,
         clip_scroll_tree: &ClipScrollTree,
@@ -114,29 +119,36 @@ impl HitTester {
     ) {
         self.nodes.clear();
         self.clip_chains.clear();
         self.clip_chains.resize(
             clip_scroll_tree.clip_chains.len(),
             HitTestClipChainDescriptor::empty()
         );
 
-        for (id, node) in &clip_scroll_tree.nodes {
-            self.nodes.insert(*id, HitTestClipScrollNode {
+        for (index, node) in clip_scroll_tree.nodes.iter().enumerate() {
+            let index = ClipScrollNodeIndex(index);
+
+            // If we haven't already seen a node for this pipeline, record this one as the root
+            // node.
+            self.pipeline_root_nodes.entry(node.pipeline_id).or_insert(index);
+
+            self.nodes.push(HitTestClipScrollNode {
+                pipeline_id: node.pipeline_id,
                 regions: get_regions_for_clip_scroll_node(node, clip_store),
                 world_content_transform: node.world_content_transform,
                 world_viewport_transform: node.world_viewport_transform,
                 node_origin: node.local_viewport_rect.origin,
             });
 
             if let NodeType::Clip { clip_chain_index, .. } = node.node_type {
               let clip_chain = self.clip_chains.get_mut(clip_chain_index.0).unwrap();
               clip_chain.parent =
                   clip_scroll_tree.get_clip_chain(clip_chain_index).parent_index;
-              clip_chain.clips = vec![*id];
+              clip_chain.clips = vec![index];
             }
         }
 
         for descriptor in &clip_scroll_tree.clip_chains_descriptors {
             let clip_chain = self.clip_chains.get_mut(descriptor.index.0).unwrap();
             clip_chain.parent = clip_scroll_tree.get_clip_chain(descriptor.index).parent_index;
             clip_chain.clips = descriptor.clips.clone();
         }
@@ -158,68 +170,68 @@ impl HitTester {
             Some(parent) => self.is_point_clipped_in_for_clip_chain(point, parent, test),
         };
 
         if !parent_clipped_in {
             test.set_in_clip_chain_cache(clip_chain_index, false);
             return false;
         }
 
-        for clip_node in &descriptor.clips {
-            if !self.is_point_clipped_in_for_node(point, clip_node, test) {
+        for clip_node_index in &descriptor.clips {
+            if !self.is_point_clipped_in_for_node(point, *clip_node_index, test) {
                 test.set_in_clip_chain_cache(clip_chain_index, false);
                 return false;
             }
         }
 
         test.set_in_clip_chain_cache(clip_chain_index, true);
         true
     }
 
     fn is_point_clipped_in_for_node(
         &self,
         point: WorldPoint,
-        node_id: &ClipId,
+        node_index: ClipScrollNodeIndex,
         test: &mut HitTest
     ) -> bool {
-        if let Some(point) = test.node_cache.get(node_id) {
+        if let Some(point) = test.node_cache.get(&node_index) {
             return point.is_some();
         }
 
-        let node = self.nodes.get(node_id).unwrap();
+        let node = &self.nodes[node_index.0];
         let transform = node.world_viewport_transform;
         let transformed_point = match transform.inverse() {
             Some(inverted) => inverted.transform_point2d(&point),
             None => {
-                test.node_cache.insert(*node_id, None);
+                test.node_cache.insert(node_index, None);
                 return false;
             }
         };
 
         let point_in_layer = transformed_point - node.node_origin.to_vector();
         for region in &node.regions {
             if !region.contains(&transformed_point) {
-                test.node_cache.insert(*node_id, None);
+                test.node_cache.insert(node_index, None);
                 return false;
             }
         }
 
-        test.node_cache.insert(*node_id, Some(point_in_layer));
+        test.node_cache.insert(node_index, Some(point_in_layer));
         true
     }
 
     pub fn hit_test(&self, mut test: HitTest) -> HitTestResult {
         let point = test.get_absolute_point(self);
 
         let mut result = HitTestResult::default();
         for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
             let scroll_node_id = clip_and_scroll.scroll_node_id;
-            let scroll_node = &self.nodes[&scroll_node_id];
-            let pipeline_id = scroll_node_id.pipeline_id();
-            match (test.pipeline_id, clip_and_scroll.scroll_node_id.pipeline_id()) {
+            let scroll_node = &self.nodes[scroll_node_id.0];
+            let pipeline_id = scroll_node.pipeline_id;
+            match (test.pipeline_id, pipeline_id) {
                 (Some(id), node_id) if node_id != id => continue,
                 _ => {},
             }
 
             let transform = scroll_node.world_content_transform;
             let point_in_layer = match transform.inverse() {
                 Some(inverted) => inverted.transform_point2d(&point),
                 None => continue,
@@ -238,21 +250,21 @@ impl HitTester {
                     break;
                 }
 
                 // We need to trigger a lookup against the root reference frame here, because
                 // items that are clipped by clip chains won't test against that part of the
                 // hierarchy. If we don't have a valid point for this test, we are likely
                 // in a situation where the reference frame has an univertible transform, but the
                 // item's clip does not.
-                let root_reference_frame = ClipId::root_reference_frame(pipeline_id);
-                if !self.is_point_clipped_in_for_node(point, &root_reference_frame, &mut test) {
+                let root_node_index = self.pipeline_root_nodes[&pipeline_id];
+                if !self.is_point_clipped_in_for_node(point, root_node_index, &mut test) {
                     continue;
                 }
-                let point_in_viewport = match test.node_cache[&root_reference_frame] {
+                let point_in_viewport = match test.node_cache[&root_node_index] {
                     Some(point) => point,
                     None => continue,
                 };
 
                 result.items.push(HitTestItem {
                     pipeline: pipeline_id,
                     tag: item.tag,
                     point_in_viewport,
@@ -262,16 +274,20 @@ impl HitTester {
                     return result;
                 }
             }
         }
 
         result.items.dedup();
         result
     }
+
+    pub fn get_pipeline_root(&self, pipeline_id: PipelineId) -> &HitTestClipScrollNode {
+        &self.nodes[self.pipeline_root_nodes[&pipeline_id].0]
+    }
 }
 
 fn get_regions_for_clip_scroll_node(
     node: &ClipScrollNode,
     clip_store: &ClipStore
 ) -> Vec<HitTestRegion> {
     let clips = match node.node_type {
         NodeType::Clip{ ref handle, .. } => clip_store.get(handle).clips(),
@@ -289,17 +305,17 @@ fn get_regions_for_clip_scroll_node(
         }
     }).collect()
 }
 
 pub struct HitTest {
     pipeline_id: Option<PipelineId>,
     point: WorldPoint,
     flags: HitTestFlags,
-    node_cache: FastHashMap<ClipId, Option<LayerPoint>>,
+    node_cache: FastHashMap<ClipScrollNodeIndex, Option<LayerPoint>>,
     clip_chain_cache: Vec<Option<bool>>,
 }
 
 impl HitTest {
     pub fn new(
         pipeline_id: Option<PipelineId>,
         point: WorldPoint,
         flags: HitTestFlags,
@@ -329,13 +345,13 @@ impl HitTest {
     }
 
     pub fn get_absolute_point(&self, hit_tester: &HitTester) -> WorldPoint {
         if !self.flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
             return self.point;
         }
 
         let point =  &LayerPoint::new(self.point.x, self.point.y);
-        self.pipeline_id.and_then(|id| hit_tester.nodes.get(&ClipId::root_reference_frame(id)))
-                   .map(|node| node.world_viewport_transform.transform_point2d(&point))
-                   .unwrap_or_else(|| WorldPoint::new(self.point.x, self.point.y))
+        self.pipeline_id.map(|id|
+            hit_tester.get_pipeline_root(id).world_viewport_transform.transform_point2d(&point)
+        ).unwrap_or_else(|| WorldPoint::new(self.point.x, self.point.y))
     }
 }
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -1,16 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ClipId, DeviceUintRect, DocumentId};
-use api::{ExternalImageData, ExternalImageId};
+use api::{DebugCommand, DeviceUintRect, DocumentId, ExternalImageData, ExternalImageId};
 use api::ImageFormat;
-use api::DebugCommand;
+use clip_scroll_tree::ClipScrollNodeIndex;
 use device::TextureFilter;
 use renderer::PipelineInfo;
 use gpu_cache::GpuCacheUpdateList;
 use fxhash::FxHasher;
 use profiler::BackendProfileCounters;
 use std::{usize, i32};
 use std::collections::{HashMap, HashSet};
 use std::f32;
@@ -135,25 +134,25 @@ impl TextureUpdateList {
 pub struct RenderedDocument {
     /// The pipeline info contains:
     /// - The last rendered epoch for each pipeline present in the frame.
     /// This information is used to know if a certain transformation on the layout has
     /// been rendered, which is necessary for reftests.
     /// - Pipelines that were removed from the scene.
     pub pipeline_info: PipelineInfo,
     /// The layers that are currently affected by the over-scrolling animation.
-    pub layers_bouncing_back: FastHashSet<ClipId>,
+    pub layers_bouncing_back: FastHashSet<ClipScrollNodeIndex>,
 
     pub frame: tiling::Frame,
 }
 
 impl RenderedDocument {
     pub fn new(
         pipeline_info: PipelineInfo,
-        layers_bouncing_back: FastHashSet<ClipId>,
+        layers_bouncing_back: FastHashSet<ClipScrollNodeIndex>,
         frame: tiling::Frame,
     ) -> Self {
         RenderedDocument {
             pipeline_info,
             layers_bouncing_back,
             frame,
         }
     }
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -61,18 +61,18 @@ mod clip;
 mod clip_scroll_node;
 mod clip_scroll_tree;
 mod debug_colors;
 mod debug_font_data;
 mod debug_render;
 #[cfg(feature = "debugger")]
 mod debug_server;
 mod device;
+mod display_list_flattener;
 mod ellipse;
-mod frame;
 mod frame_builder;
 mod freelist;
 #[cfg(any(target_os = "macos", target_os = "windows"))]
 mod gamma_lut;
 mod geometry;
 mod glyph_cache;
 mod glyph_rasterizer;
 mod gpu_cache;
@@ -85,16 +85,17 @@ mod print_tree;
 mod profiler;
 mod query;
 mod record;
 mod render_backend;
 mod render_task;
 mod renderer;
 mod resource_cache;
 mod scene;
+mod scene_builder;
 mod segment;
 mod spring;
 mod texture_allocator;
 mod texture_cache;
 mod tiling;
 mod util;
 
 mod shader_source {
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -1,17 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BoxShadowClipMode, ClipId, ColorF, DeviceIntPoint, DeviceIntRect, FilterOp, LayerPoint};
+use api::{BoxShadowClipMode, ColorF, DeviceIntPoint, DeviceIntRect, FilterOp, LayerPoint};
 use api::{LayerRect, LayerToWorldScale, LayerVector2D, MixBlendMode, PipelineId};
 use api::{PremultipliedColorF, Shadow};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
-use frame_builder::{FrameContext, FrameState, PictureState};
+use clip_scroll_tree::ClipScrollNodeIndex;
+use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use gpu_cache::{GpuCacheHandle, GpuDataRequest};
 use gpu_types::{BrushImageKind, PictureType};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use prim_store::ScrollNodeAndClipChain;
 use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::CacheItem;
 use scene::{FilterOpHelpers, SceneProperties};
@@ -81,17 +82,17 @@ pub enum PictureKind {
         is_in_3d_context: bool,
         // If requested as a frame output (for rendering
         // pages to a texture), this is the pipeline this
         // picture is the root of.
         frame_output_pipeline_id: Option<PipelineId>,
         // The original reference frame ID for this picture.
         // It is only different if this is part of a 3D
         // rendering context.
-        reference_frame_id: ClipId,
+        reference_frame_index: ClipScrollNodeIndex,
         real_local_rect: LayerRect,
         // An optional cache handle for storing extra data
         // in the GPU cache, depending on the type of
         // picture.
         extra_gpu_data_handle: GpuCacheHandle,
     },
 }
 
@@ -203,28 +204,28 @@ impl PicturePrimitive {
             ),
         }
     }
 
     pub fn new_image(
         composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
-        reference_frame_id: ClipId,
+        reference_frame_index: ClipScrollNodeIndex,
         frame_output_pipeline_id: Option<PipelineId>,
     ) -> Self {
         PicturePrimitive {
             runs: Vec::new(),
             surface: None,
             kind: PictureKind::Image {
                 secondary_render_task_id: None,
                 composite_mode,
                 is_in_3d_context,
                 frame_output_pipeline_id,
-                reference_frame_id,
+                reference_frame_index,
                 real_local_rect: LayerRect::zero(),
                 extra_gpu_data_handle: GpuCacheHandle::new(),
             },
             pipeline_id,
             cull_children: true,
             brush: BrushPrimitive::new(
                 BrushKind::Picture,
                 None,
@@ -282,41 +283,27 @@ impl PicturePrimitive {
 
                 *content_rect = local_content_rect.inflate(
                     blur_offset,
                     blur_offset,
                 );
 
                 content_rect.translate(&offset)
             }
-            PictureKind::BoxShadow { blur_radius, clip_mode, image_kind, ref mut content_rect, .. } => {
+            PictureKind::BoxShadow { blur_radius, clip_mode, ref mut content_rect, .. } => {
                 // We need to inflate the content rect if outset.
                 *content_rect = match clip_mode {
                     BoxShadowClipMode::Outset => {
-                        match image_kind {
-                            BrushImageKind::Mirror => {
-                                let half_offset = 0.5 * blur_radius * BLUR_SAMPLE_SCALE;
-                                // If the radii are uniform, we can render just the top
-                                // left corner and mirror it across the primitive. In
-                                // this case, shift the content rect to leave room
-                                // for the blur to take effect.
-                                local_content_rect
-                                    .translate(&-LayerVector2D::new(half_offset, half_offset))
-                                    .inflate(half_offset, half_offset)
-                            }
-                            BrushImageKind::NinePatch | BrushImageKind::Simple => {
-                                let full_offset = blur_radius * BLUR_SAMPLE_SCALE;
-                                // For a non-uniform radii, we need to expand
-                                // the content rect on all sides for the blur.
-                                local_content_rect.inflate(
-                                    full_offset,
-                                    full_offset,
-                                )
-                            }
-                        }
+                        let full_offset = blur_radius * BLUR_SAMPLE_SCALE;
+                        // For a non-uniform radii, we need to expand
+                        // the content rect on all sides for the blur.
+                        local_content_rect.inflate(
+                            full_offset,
+                            full_offset,
+                        )
                     }
                     BoxShadowClipMode::Inset => {
                         local_content_rect
                     }
                 };
 
                 prim_local_rect
             }
@@ -325,18 +312,18 @@ impl PicturePrimitive {
 
     pub fn prepare_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_screen_rect: &DeviceIntRect,
         prim_local_rect: &LayerRect,
         pic_state_for_children: PictureState,
         pic_state: &mut PictureState,
-        frame_context: &FrameContext,
-        frame_state: &mut FrameState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
     ) {
         let content_scale = LayerToWorldScale::new(1.0) * frame_context.device_pixel_scale;
 
         match self.kind {
             PictureKind::Image {
                 ref mut secondary_render_task_id,
                 ref mut extra_gpu_data_handle,
                 composite_mode,
@@ -637,17 +624,9 @@ impl PicturePrimitive {
                     }
                 }
             }
             PictureKind::BoxShadow { color, .. } => {
                 request.push(color.premultiplied());
             }
         }
     }
-
-    pub fn target_kind(&self) -> RenderTargetKind {
-        match self.kind {
-            PictureKind::TextShadow { .. } => RenderTargetKind::Color,
-            PictureKind::BoxShadow { .. } => RenderTargetKind::Alpha,
-            PictureKind::Image { .. } => RenderTargetKind::Color,
-        }
-    }
 }
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,49 +1,53 @@
 /* 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 api::{AlphaType, BorderRadius, BuiltDisplayList, ClipId, ClipMode, ColorF, ComplexClipRegion};
+use api::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, ComplexClipRegion};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch, ExtendMode, FontRenderMode};
 use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
 use api::{LineStyle, PremultipliedColorF, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderEdgeKind};
-use clip_scroll_tree::{ClipChainIndex, CoordinateSystemId};
+use clip_scroll_tree::{ClipChainIndex, ClipScrollNodeIndex, CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipSource};
 use clip::{ClipSourcesHandle, ClipWorkItem};
-use frame_builder::{FrameContext, FrameState, PictureContext, PictureState, PrimitiveRunContext};
+use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
+use frame_builder::PrimitiveRunContext;
 use glyph_rasterizer::{FontInstance, FontTransform};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::{ClipChainRectIndex};
 use picture::{PictureKind, PicturePrimitive};
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind};
 use render_task::RenderTaskId;
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{CacheItem, ImageProperties, ImageRequest, ResourceCache};
 use segment::SegmentBuilder;
 use std::{mem, usize};
-use std::rc::Rc;
+use std::sync::Arc;
 use util::{MatrixHelpers, WorldToLayerFastTransform, calculate_screen_bounding_rect};
 use util::{pack_as_float, recycle_vec};
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ScrollNodeAndClipChain {
-    pub scroll_node_id: ClipId,
+    pub scroll_node_id: ClipScrollNodeIndex,
     pub clip_chain_index: ClipChainIndex,
 }
 
 impl ScrollNodeAndClipChain {
-    pub fn new(scroll_node_id: ClipId, clip_chain_index: ClipChainIndex) -> ScrollNodeAndClipChain {
+    pub fn new(
+        scroll_node_id: ClipScrollNodeIndex,
+        clip_chain_index: ClipChainIndex
+    ) -> ScrollNodeAndClipChain {
         ScrollNodeAndClipChain { scroll_node_id, clip_chain_index }
     }
 }
 
 #[derive(Debug)]
 pub struct PrimitiveRun {
     pub base_prim_index: PrimitiveIndex,
     pub count: usize,
@@ -180,27 +184,21 @@ pub struct PrimitiveMetadata {
     pub screen_rect: Option<ScreenRect>,
 
     /// A tag used to identify this primitive outside of WebRender. This is
     /// used for returning useful data during hit testing.
     pub tag: Option<ItemTag>,
 }
 
 #[derive(Debug)]
-pub enum BrushMaskKind {
-    //Rect,         // TODO(gw): Optimization opportunity for masks with 0 border radii.
-    Corner(LayerSize),
-    RoundedRect(LayerRect, BorderRadius),
-}
-
-#[derive(Debug)]
 pub enum BrushKind {
     Mask {
         clip_mode: ClipMode,
-        kind: BrushMaskKind,
+        rect: LayerRect,
+        radii: BorderRadius,
     },
     Solid {
         color: ColorF,
     },
     Clear,
     Line {
         color: PremultipliedColorF,
         wavy_line_thickness: f32,
@@ -325,35 +323,29 @@ impl BrushPrimitive {
             segment_desc,
         }
     }
 
     fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
         // has to match VECS_PER_SPECIFIC_BRUSH
         match self.kind {
             BrushKind::Picture |
-            BrushKind::Image { .. } |
             BrushKind::YuvImage { .. } => {
             }
+            BrushKind::Image { .. } => {
+                request.push([0.0; 4]);
+            }
             BrushKind::Solid { color } => {
                 request.push(color.premultiplied());
             }
             BrushKind::Clear => {
                 // Opaque black with operator dest out
                 request.push(PremultipliedColorF::BLACK);
             }
-            BrushKind::Mask { clip_mode, kind: BrushMaskKind::Corner(radius) } => {
-                request.push([
-                    radius.width,
-                    radius.height,
-                    clip_mode as u32 as f32,
-                    0.0,
-                ]);
-            }
-            BrushKind::Mask { clip_mode, kind: BrushMaskKind::RoundedRect(rect, radii) } => {
+            BrushKind::Mask { clip_mode, rect, radii } => {
                 request.push([
                     clip_mode as u32 as f32,
                     0.0,
                     0.0,
                     0.0
                 ]);
                 request.push(rect);
                 request.push([
@@ -1083,18 +1075,18 @@ impl PrimitiveStore {
 
     fn prepare_prim_for_render_inner(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         pic_state_for_children: PictureState,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
-        frame_context: &FrameContext,
-        frame_state: &mut FrameState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
     ) {
         let metadata = &mut self.cpu_metadata[prim_index.0];
         match metadata.prim_kind {
             PrimitiveKind::Border => {}
             PrimitiveKind::Picture => {
                 self.cpu_pictures[metadata.cpu_prim_index.0]
                     .prepare_for_render(
                         prim_index,
@@ -1362,18 +1354,18 @@ impl PrimitiveStore {
     }
 
     fn write_brush_segment_description(
         brush: &mut BrushPrimitive,
         metadata: &PrimitiveMetadata,
         prim_run_context: &PrimitiveRunContext,
         clips: &Vec<ClipWorkItem>,
         has_clips_from_other_coordinate_systems: bool,
-        frame_context: &FrameContext,
-        frame_state: &mut FrameState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
     ) {
         match brush.segment_desc {
             Some(ref segment_desc) => {
                 // If we already have a segment descriptor, only run through the
                 // clips list if we haven't already determined the mask kind.
                 if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown {
                     return;
                 }
@@ -1484,18 +1476,18 @@ impl PrimitiveStore {
     fn update_clip_task_for_brush(
         &mut self,
         prim_run_context: &PrimitiveRunContext,
         prim_index: PrimitiveIndex,
         clips: &Vec<ClipWorkItem>,
         combined_outer_rect: &DeviceIntRect,
         has_clips_from_other_coordinate_systems: bool,
         pic_state: &mut PictureState,
-        frame_context: &FrameContext,
-        frame_state: &mut FrameState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
     ) -> bool {
         let metadata = &self.cpu_metadata[prim_index.0];
         let brush = match metadata.prim_kind {
             PrimitiveKind::Brush => {
                 &mut self.cpu_brushes[metadata.cpu_prim_index.0]
             }
             PrimitiveKind::Picture => {
                 &mut self.cpu_pictures[metadata.cpu_prim_index.0].brush
@@ -1552,18 +1544,18 @@ impl PrimitiveStore {
     }
 
     fn update_clip_task(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         prim_screen_rect: &DeviceIntRect,
         pic_state: &mut PictureState,
-        frame_context: &FrameContext,
-        frame_state: &mut FrameState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
     ) -> bool {
         self.cpu_metadata[prim_index.0].clip_task_id = None;
 
         let prim_screen_rect = match prim_screen_rect.intersection(&frame_context.screen_rect) {
             Some(rect) => rect,
             None => {
                 self.cpu_metadata[prim_index.0].screen_rect = None;
                 return false;
@@ -1586,17 +1578,17 @@ impl PrimitiveStore {
                 );
                 let (screen_inner_rect, screen_outer_rect) =
                     prim_clips.get_screen_bounds(transform, frame_context.device_pixel_scale);
 
                 if let Some(outer) = screen_outer_rect {
                     combined_outer_rect = combined_outer_rect.and_then(|r| r.intersection(&outer));
                 }
 
-                Some(Rc::new(ClipChainNode {
+                Some(Arc::new(ClipChainNode {
                     work_item: ClipWorkItem {
                         scroll_node_data_index: prim_run_context.scroll_node.node_data_index,
                         clip_sources: metadata.clip_sources.weak(),
                         coordinate_system_id: prim_coordinate_system_id,
                     },
                     // The local_clip_rect a property of ClipChain nodes that are ClipScrollNodes.
                     // It's used to calculate a local clipping rectangle before we reach this
                     // point, so we can set it to zero here. It should be unused from this point
@@ -1683,18 +1675,18 @@ impl PrimitiveStore {
     }
 
     pub fn prepare_prim_for_render(
         &mut self,
         prim_index: PrimitiveIndex,
         prim_run_context: &PrimitiveRunContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
-        frame_context: &FrameContext,
-        frame_state: &mut FrameState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
     ) -> Option<LayerRect> {
         let mut may_need_clip_mask = true;
         let mut pic_state_for_children = PictureState::new();
 
         // Do some basic checks first, that can early out
         // without even knowing the local rect.
         let (prim_kind, cpu_prim_index) = {
             let metadata = &self.cpu_metadata[prim_index.0];
@@ -1716,20 +1708,20 @@ impl PrimitiveStore {
         if let PrimitiveKind::Picture = prim_kind {
             let pic_context_for_children = {
                 let pic = &mut self.cpu_pictures[cpu_prim_index.0];
 
                 if !pic.resolve_scene_properties(frame_context.scene_properties) {
                     return None;
                 }
 
-                let (draw_text_transformed, original_reference_frame_id) = match pic.kind {
-                    PictureKind::Image { reference_frame_id, .. } => {
-                        may_need_clip_mask = false;
-                        (true, Some(reference_frame_id))
+                let (draw_text_transformed, original_reference_frame_index) = match pic.kind {
+                    PictureKind::Image { reference_frame_index, composite_mode, .. } => {
+                        may_need_clip_mask = composite_mode.is_some();
+                        (true, Some(reference_frame_index))
                     }
                     PictureKind::BoxShadow { .. } |
                     PictureKind::TextShadow { .. } => {
                         (false, None)
                     }
                 };
 
                 let display_list = &frame_context
@@ -1742,17 +1734,17 @@ impl PrimitiveStore {
                     .scroll_node
                     .world_content_transform
                     .inverse();
 
                 PictureContext {
                     pipeline_id: pic.pipeline_id,
                     perform_culling: pic.cull_children,
                     prim_runs: mem::replace(&mut pic.runs, Vec::new()),
-                    original_reference_frame_id,
+                    original_reference_frame_index,
                     display_list,
                     draw_text_transformed,
                     inv_world_transform,
                 }
             };
 
             let result = self.prepare_prim_runs(
                 &pic_context_for_children,
@@ -1842,31 +1834,31 @@ impl PrimitiveStore {
             md.screen_rect = None;
         }
     }
 
     pub fn prepare_prim_runs(
         &mut self,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
-        frame_context: &FrameContext,
-        frame_state: &mut FrameState,
+        frame_context: &FrameBuildingContext,
+        frame_state: &mut FrameBuildingState,
     ) -> PrimitiveRunLocalRect {
         let mut result = PrimitiveRunLocalRect {
             local_rect_in_actual_parent_space: LayerRect::zero(),
             local_rect_in_original_parent_space: LayerRect::zero(),
         };
 
         for run in &pic_context.prim_runs {
             // TODO(gw): Perhaps we can restructure this to not need to create
             //           a new primitive context for every run (if the hash
             //           lookups ever show up in a profile).
             let scroll_node = &frame_context
                 .clip_scroll_tree
-                .nodes[&run.clip_and_scroll.scroll_node_id];
+                .nodes[run.clip_and_scroll.scroll_node_id.0];
             let clip_chain = frame_context
                 .clip_scroll_tree
                 .get_clip_chain(run.clip_and_scroll.clip_chain_index);
 
             if pic_context.perform_culling {
                 if !scroll_node.invertible {
                     debug!("{:?} {:?}: position not invertible", run.base_prim_index, pic_context.pipeline_id);
                     continue;
@@ -1879,21 +1871,21 @@ impl PrimitiveStore {
             }
 
             let parent_relative_transform = pic_context
                 .inv_world_transform
                 .map(|inv_parent| {
                     inv_parent.pre_mul(&scroll_node.world_content_transform)
                 });
 
-            let original_relative_transform = pic_context.original_reference_frame_id
-                .and_then(|original_reference_frame_id| {
+            let original_relative_transform = pic_context.original_reference_frame_index
+                .and_then(|original_reference_frame_index| {
                     let parent = frame_context
                         .clip_scroll_tree
-                        .nodes[&original_reference_frame_id]
+                        .nodes[original_reference_frame_index.0]
                         .world_content_transform;
                     parent.inverse()
                         .map(|inv_parent| {
                             inv_parent.pre_mul(&scroll_node.world_content_transform)
                         })
                 });
 
             let clip_chain_rect = match pic_context.perform_culling {
--- a/gfx/webrender/src/record.rs
+++ b/gfx/webrender/src/record.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ApiMsg, DocumentMsg};
+use api::{ApiMsg, FrameMsg};
 use bincode::{serialize, Infinite};
 use byteorder::{LittleEndian, WriteBytesExt};
 use std::any::TypeId;
 use std::fmt::Debug;
 use std::fs::File;
 use std::io::Write;
 use std::mem;
 use std::path::PathBuf;
@@ -62,20 +62,21 @@ impl ApiRecordingReceiver for BinaryReco
 }
 
 pub fn should_record_msg(msg: &ApiMsg) -> bool {
     match *msg {
         ApiMsg::UpdateResources(..) |
         ApiMsg::AddDocument { .. } |
         ApiMsg::DeleteDocument(..) => true,
         ApiMsg::UpdateDocument(_, ref msgs) => {
-            for msg in msgs {
+            for msg in &msgs.frame_ops {
                 match *msg {
-                    DocumentMsg::GetScrollNodeState(..) |
-                    DocumentMsg::HitTest(..) => {}
+                    FrameMsg::GetScrollNodeState(..) |
+                    FrameMsg::HitTest(..) => {}
                     _ => { return true; }
                 }
             }
+
             false
         }
         _ => false,
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -1,179 +1,363 @@
 /* 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 api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
-use api::{DocumentId, DocumentLayer, DocumentMsg, HitTestResult, IdNamespace, PipelineId};
-use api::RenderNotifier;
-use api::channel::{MsgReceiver, PayloadReceiver, PayloadReceiverHelperMethods};
-use api::channel::{PayloadSender, PayloadSenderHelperMethods};
+use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestResult};
+use api::{IdNamespace, LayerPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
+use api::{ScrollEventPhase, ScrollLocation, ScrollNodeState, TransactionMsg, WorldPoint};
+use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
+use clip_scroll_tree::ClipScrollTree;
 #[cfg(feature = "debugger")]
 use debug_server;
-use frame::FrameContext;
+use display_list_flattener::DisplayListFlattener;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
+use renderer::PipelineInfo;
 use resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use resource_cache::PlainResources;
-use scene::Scene;
+use scene::{Scene, SceneProperties};
+use scene_builder::*;
 #[cfg(feature = "serialize")]
 use serde::{Serialize, Deserialize};
 #[cfg(feature = "debugger")]
 use serde_json;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
-use std::sync::mpsc::Sender;
 use std::mem::replace;
+use std::sync::mpsc::{Sender, Receiver};
 use std::u32;
+use tiling::Frame;
 use time::precise_time_ns;
 
-#[cfg_attr(feature = "capture", derive(Clone, Serialize))]
+#[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-struct DocumentView {
-    window_size: DeviceUintSize,
-    inner_rect: DeviceUintRect,
-    layer: DocumentLayer,
-    pan: DeviceIntPoint,
-    device_pixel_ratio: f32,
-    page_zoom_factor: f32,
-    pinch_zoom_factor: f32,
+#[derive(Clone)]
+pub struct DocumentView {
+    pub window_size: DeviceUintSize,
+    pub inner_rect: DeviceUintRect,
+    pub layer: DocumentLayer,
+    pub pan: DeviceIntPoint,
+    pub device_pixel_ratio: f32,
+    pub page_zoom_factor: f32,
+    pub pinch_zoom_factor: f32,
 }
 
 impl DocumentView {
-    fn accumulated_scale_factor(&self) -> DevicePixelScale {
+    pub fn accumulated_scale_factor(&self) -> DevicePixelScale {
         DevicePixelScale::new(
             self.device_pixel_ratio *
             self.page_zoom_factor *
             self.pinch_zoom_factor
         )
     }
 }
 
-struct Document {
+struct SceneData {
     scene: Scene,
+    removed_pipelines: Vec<PipelineId>,
+}
+
+#[derive(Copy, Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FrameId(pub u32);
+
+struct Document {
+    // The latest built scene, usable to build frames.
+    // received from the scene builder thread.
+    current: SceneData,
+    // The scene with the latest transactions applied, not necessarily built yet.
+    // what we will send to the scene builder.
+    pending: SceneData,
+
     view: DocumentView,
-    frame_ctx: FrameContext,
+
+    /// The ClipScrollTree for this document which tracks both ClipScrollNodes and ClipChains.
+    /// This is stored here so that we are able to preserve scrolling positions between
+    /// rendered frames.
+    clip_scroll_tree: ClipScrollTree,
+
+    /// The id of the current frame.
+    frame_id: FrameId,
+
+    /// A configuration object for the FrameBuilder that we produce.
+    frame_builder_config: FrameBuilderConfig,
+
     // the `Option` here is only to deal with borrow checker
     frame_builder: Option<FrameBuilder>,
     // A set of pipelines that the caller has requested be
     // made available as output textures.
     output_pipelines: FastHashSet<PipelineId>,
-    // The pipeline removal notifications that will be sent in the next frame.
-    // Because of async scene building, removed pipelines should not land here
-    // as soon as the render backend receives a DocumentMsg::RemovePipeline.
-    // Instead, the notification should be added to this list when the first
-    // scene that does not contain the pipeline becomes current.
-    removed_pipelines: Vec<PipelineId>,
     // A helper switch to prevent any frames rendering triggered by scrolling
     // messages between `SetDisplayList` and `GenerateFrame`.
     // If we allow them, then a reftest that scrolls a few layers before generating
     // the first frame would produce inconsistent rendering results, because
     // scroll events are not necessarily received in deterministic order.
     render_on_scroll: Option<bool>,
     // A helper flag to prevent any hit-tests from happening between calls
     // to build_scene and rendering the document. In between these two calls,
     // hit-tests produce inconsistent results because the clip_scroll_tree
     // is out of sync with the display list.
     render_on_hittest: bool,
 
     /// A data structure to allow hit testing against rendered frames. This is updated
     /// every time we produce a fully rendered frame.
     hit_tester: Option<HitTester>,
+
+    /// Properties that are resolved during frame building and can be changed at any time
+    /// without requiring the scene to be re-built.
+    dynamic_properties: SceneProperties,
 }
 
 impl Document {
     pub fn new(
-        config: FrameBuilderConfig,
+        frame_builder_config: FrameBuilderConfig,
         window_size: DeviceUintSize,
         layer: DocumentLayer,
         enable_render_on_scroll: bool,
         default_device_pixel_ratio: f32,
     ) -> Self {
         let render_on_scroll = if enable_render_on_scroll {
             Some(false)
         } else {
             None
         };
         Document {
-            scene: Scene::new(),
-            removed_pipelines: Vec::new(),
+            current: SceneData {
+                scene: Scene::new(),
+                removed_pipelines: Vec::new(),
+            },
+            pending: SceneData {
+                scene: Scene::new(),
+                removed_pipelines: Vec::new(),
+            },
             view: DocumentView {
                 window_size,
                 inner_rect: DeviceUintRect::new(DeviceUintPoint::zero(), window_size),
                 layer,
                 pan: DeviceIntPoint::zero(),
                 page_zoom_factor: 1.0,
                 pinch_zoom_factor: 1.0,
                 device_pixel_ratio: default_device_pixel_ratio,
             },
-            frame_ctx: FrameContext::new(config),
-            frame_builder: Some(FrameBuilder::empty()),
+            clip_scroll_tree: ClipScrollTree::new(),
+            frame_id: FrameId(0),
+            frame_builder_config,
+            frame_builder: None,
             output_pipelines: FastHashSet::default(),
             render_on_scroll,
             render_on_hittest: false,
             hit_tester: None,
+            dynamic_properties: SceneProperties::new(),
         }
     }
 
+    fn can_render(&self) -> bool { self.frame_builder.is_some() }
+
+    // TODO: We will probably get rid of this soon and always forward to the scene building thread.
     fn build_scene(&mut self, resource_cache: &mut ResourceCache) {
-        // this code is why we have `Option`, which is never `None`
-        let frame_builder = self.frame_ctx.create_frame_builder(
-            self.frame_builder.take().unwrap(),
-            &self.scene,
-            resource_cache,
-            self.view.window_size,
-            self.view.inner_rect,
-            self.view.accumulated_scale_factor(),
-            &self.output_pipelines,
-        );
-        self.removed_pipelines.extend(self.scene.removed_pipelines.drain(..));
+        let frame_builder = self.create_frame_builder(resource_cache);
+        if !self.current.removed_pipelines.is_empty() {
+            warn!("Built the scene several times without rendering it.");
+        }
+        self.current.removed_pipelines.extend(self.pending.removed_pipelines.drain(..));
         self.frame_builder = Some(frame_builder);
+        self.current.scene = self.pending.scene.clone();
+    }
+
+    fn forward_transaction_to_scene_builder(
+        &mut self,
+        transaction_msg: TransactionMsg,
+        document_ops: &DocumentOps,
+        document_id: DocumentId,
+        resource_cache: &ResourceCache,
+        scene_tx: &Sender<SceneBuilderRequest>,
+    ) {
+        // Do as much of the error handling as possible here before dispatching to
+        // the scene builder thread.
+        let build_scene: bool = document_ops.build
+            && self.pending.scene.root_pipeline_id.map(
+                |id| { self.pending.scene.pipelines.contains_key(&id) }
+            ).unwrap_or(false);
+
+        let scene_request = if build_scene {
+            if self.view.window_size.width == 0 || self.view.window_size.height == 0 {
+                error!("ERROR: Invalid window dimensions! Please call api.set_window_size()");
+            }
+
+            Some(SceneRequest {
+                scene: self.pending.scene.clone(),
+                removed_pipelines: replace(&mut self.pending.removed_pipelines, Vec::new()),
+                view: self.view.clone(),
+                font_instances: resource_cache.get_font_instances(),
+