Bug 1436058 - Update webrender to 342bc314db94aa439b2001249c5f24ccfcbccc22. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 12 Feb 2018 11:28:01 -0500
changeset 753900 bdd65999d58000216a4fa2a4c5434f1ac5a8c167
parent 753815 3ee38289dac8838fe848f7234d75f3cef5d3dbc7
child 753901 111a4e0ad8d56fb5ec4b688972f6d8a1730f86ab
push id98716
push userkgupta@mozilla.com
push dateMon, 12 Feb 2018 16:43:47 +0000
reviewersjrmuizel
bugs1436058
milestone60.0a1
Bug 1436058 - Update webrender to 342bc314db94aa439b2001249c5f24ccfcbccc22. r?jrmuizel MozReview-Commit-ID: DoRq53eXv1Q
gfx/webrender/Cargo.toml
gfx/webrender/examples/alpha_perf.rs
gfx/webrender/examples/animation.rs
gfx/webrender/examples/basic.rs
gfx/webrender/examples/common/boilerplate.rs
gfx/webrender/examples/image_resize.rs
gfx/webrender/examples/multiwindow.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/examples/texture_cache_stress.rs
gfx/webrender/examples/yuv.rs
gfx/webrender/res/brush_line.glsl
gfx/webrender/res/prim_shared.glsl
gfx/webrender/src/batch.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/debug_server.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/hit_test.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/picture.rs
gfx/webrender/src/platform/macos/font.rs
gfx/webrender/src/prim_store.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/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_api/src/api.rs
gfx/webrender_bindings/Cargo.toml
gfx/webrender_bindings/revision.txt
gfx/wrench/Cargo.toml
gfx/wrench/README.md
gfx/wrench/src/main.rs
gfx/wrench/src/rawtest.rs
gfx/wrench/src/wrench.rs
gfx/wrench/src/yaml_frame_reader.rs
gfx/wrench/src/yaml_frame_writer.rs
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -17,17 +17,17 @@ replay = ["webrender_api/deserialize", "
 [dependencies]
 app_units = "0.6"
 bincode = "0.9"
 byteorder = "1.0"
 euclid = "0.16"
 fxhash = "0.2.1"
 gleam = "0.4.20"
 lazy_static = "1"
-log = "0.3"
+log = "0.4"
 num-traits = "0.1.32"
 time = "0.1"
 rayon = "0.8"
 webrender_api = {path = "../webrender_api"}
 bitflags = "1.0"
 thread_profiler = "0.1.1"
 plane-split = "0.7"
 png = { optional = true, version = "0.11" }
@@ -36,22 +36,22 @@ 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" }
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
-env_logger = "0.4"
+env_logger = "0.5"
 rand = "0.3"                # for the benchmarks
-servo-glutin = "0.14"     # for the example apps
+glutin = "0.12"             # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.3", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-foundation = "0.5"
 core-graphics = "0.13"
-core-text = { version = "9.0", default-features = false }
+core-text = { version = "9.2.0", default-features = false }
--- a/gfx/webrender/examples/alpha_perf.rs
+++ b/gfx/webrender/examples/alpha_perf.rs
@@ -48,22 +48,29 @@ impl Example for App {
             builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 0.05));
         }
 
         builder.pop_stacking_context();
     }
 
     fn on_event(
         &mut self,
-        event: glutin::Event,
+        event: glutin::WindowEvent,
         _api: &RenderApi,
         _document_id: DocumentId
     ) -> bool {
         match event {
-            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+            glutin::WindowEvent::KeyboardInput {
+                input: glutin::KeyboardInput {
+                    state: glutin::ElementState::Pressed,
+                    virtual_keycode: Some(key),
+                    ..
+                },
+                ..
+            } => {
                 match key {
                     glutin::VirtualKeyCode::Right => {
                         self.rect_count += 1;
                         println!("rects = {}", self.rect_count);
                     }
                     glutin::VirtualKeyCode::Left => {
                         self.rect_count = cmp::max(self.rect_count, 1) - 1;
                         println!("rects = {}", self.rect_count);
--- a/gfx/webrender/examples/animation.rs
+++ b/gfx/webrender/examples/animation.rs
@@ -66,19 +66,26 @@ impl Example for App {
         );
 
         // Fill it with a white rect
         builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
         builder.pop_stacking_context();
     }
 
-    fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
-        match event {
-            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+    fn on_event(&mut self, win_event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
+        match win_event {
+            glutin::WindowEvent::KeyboardInput {
+                input: glutin::KeyboardInput {
+                    state: glutin::ElementState::Pressed,
+                    virtual_keycode: Some(key),
+                    ..
+                },
+                ..
+            } => {
                 let (offset_x, offset_y, angle, delta_opacity) = match key {
                     glutin::VirtualKeyCode::Down => (0.0, 10.0, 0.0, 0.0),
                     glutin::VirtualKeyCode::Up => (0.0, -10.0, 0.0, 0.0),
                     glutin::VirtualKeyCode::Right => (10.0, 0.0, 0.0, 0.0),
                     glutin::VirtualKeyCode::Left => (-10.0, 0.0, 0.0, 0.0),
                     glutin::VirtualKeyCode::Comma => (0.0, 0.0, 0.1, 0.0),
                     glutin::VirtualKeyCode::Period => (0.0, 0.0, -0.1, 0.0),
                     glutin::VirtualKeyCode::Z => (0.0, 0.0, 0.0, -0.1),
--- a/gfx/webrender/examples/basic.rs
+++ b/gfx/webrender/examples/basic.rs
@@ -268,20 +268,20 @@ impl Example for App {
                 BorderRadius::uniform(simple_border_radius),
                 box_shadow_type,
             );
         }
 
         builder.pop_stacking_context();
     }
 
-    fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
+    fn on_event(&mut self, event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
         let mut txn = Transaction::new();
         match event {
-            glutin::Event::Touch(touch) => match self.touch_state.handle_event(touch) {
+            glutin::WindowEvent::Touch(touch) => match self.touch_state.handle_event(touch) {
                 TouchResult::Pan(pan) => {
                     txn.set_pan(pan);
                 }
                 TouchResult::Zoom(zoom) => {
                     txn.set_pinch_zoom(ZoomFactor::new(zoom));
                 }
                 TouchResult::None => {}
             },
--- a/gfx/webrender/examples/common/boilerplate.rs
+++ b/gfx/webrender/examples/common/boilerplate.rs
@@ -1,42 +1,42 @@
 /* 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/. */
 
 extern crate env_logger;
 extern crate euclid;
 
 use gleam::gl;
-use glutin;
+use glutin::{self, GlContext};
 use std::env;
 use std::path::PathBuf;
 use webrender;
 use webrender::api::*;
 
 struct Notifier {
-    window_proxy: glutin::WindowProxy,
+    events_proxy: glutin::EventsLoopProxy,
 }
 
 impl Notifier {
-    fn new(window_proxy: glutin::WindowProxy) -> Notifier {
-        Notifier { window_proxy }
+    fn new(events_proxy: glutin::EventsLoopProxy) -> Notifier {
+        Notifier { events_proxy }
     }
 }
 
 impl RenderNotifier for Notifier {
     fn clone(&self) -> Box<RenderNotifier> {
         Box::new(Notifier {
-            window_proxy: self.window_proxy.clone(),
+            events_proxy: self.events_proxy.clone(),
         })
     }
 
     fn wake_up(&self) {
         #[cfg(not(target_os = "android"))]
-        self.window_proxy.wakeup_event_loop();
+        let _ = self.events_proxy.wakeup();
     }
 
     fn new_document_ready(&self, _: DocumentId, _scrolled: bool, _composite_needed: bool) {
         self.wake_up();
     }
 }
 
 pub trait HandyDandyRectBuilder {
@@ -71,17 +71,17 @@ pub trait Example {
         &mut self,
         api: &RenderApi,
         builder: &mut DisplayListBuilder,
         resources: &mut ResourceUpdates,
         framebuffer_size: DeviceUintSize,
         pipeline_id: PipelineId,
         document_id: DocumentId,
     );
-    fn on_event(&mut self, glutin::Event, &RenderApi, DocumentId) -> bool {
+    fn on_event(&mut self, glutin::WindowEvent, &RenderApi, DocumentId) -> bool {
         false
     }
     fn get_image_handlers(
         &mut self,
         _gl: &gl::Gl,
     ) -> (Option<Box<webrender::ExternalImageHandler>>,
           Option<Box<webrender::OutputImageHandler>>) {
         (None, None)
@@ -89,34 +89,36 @@ pub trait Example {
     fn draw_custom(&self, _gl: &gl::Gl) {
     }
 }
 
 pub fn main_wrapper<E: Example>(
     example: &mut E,
     options: Option<webrender::RendererOptions>,
 ) {
-    env_logger::init().unwrap();
+    env_logger::init();
 
     let args: Vec<String> = env::args().collect();
     let res_path = if args.len() > 1 {
         Some(PathBuf::from(&args[1]))
     } else {
         None
     };
 
-    let window = glutin::WindowBuilder::new()
-        .with_title(E::TITLE)
-        .with_multitouch()
-        .with_dimensions(E::WIDTH, E::HEIGHT)
+    let mut events_loop = glutin::EventsLoop::new();
+    let context_builder = glutin::ContextBuilder::new()
         .with_gl(glutin::GlRequest::GlThenGles {
             opengl_version: (3, 2),
             opengles_version: (3, 0),
-        })
-        .build()
+        });
+    let window_builder = glutin::WindowBuilder::new()
+        .with_title(E::TITLE)
+        .with_multitouch()
+        .with_dimensions(E::WIDTH, E::HEIGHT);
+    let window = glutin::GlWindow::new(window_builder, context_builder, &events_loop)
         .unwrap();
 
     unsafe {
         window.make_current().ok();
     }
 
     let gl = match window.get_api() {
         glutin::Api::OpenGl => unsafe {
@@ -140,20 +142,20 @@ pub fn main_wrapper<E: Example>(
         precache_shaders: E::PRECACHE_SHADERS,
         device_pixel_ratio,
         clear_color: Some(ColorF::new(0.3, 0.0, 0.0, 1.0)),
         //scatter_gpu_cache_updates: false,
         ..options.unwrap_or(webrender::RendererOptions::default())
     };
 
     let framebuffer_size = {
-        let (width, height) = window.get_inner_size_pixels().unwrap();
+        let (width, height) = window.get_inner_size().unwrap();
         DeviceUintSize::new(width, height)
     };
-    let notifier = Box::new(Notifier::new(window.create_window_proxy()));
+    let notifier = Box::new(Notifier::new(events_loop.create_proxy()));
     let (mut renderer, sender) = webrender::Renderer::new(gl.clone(), notifier, opts).unwrap();
     let api = sender.create_api();
     let document_id = api.add_document(framebuffer_size, 0);
 
     let (external, output) = example.get_image_handlers(&*gl);
 
     if let Some(output_image_handler) = output {
         renderer.set_output_image_handler(output_image_handler);
@@ -186,137 +188,97 @@ pub fn main_wrapper<E: Example>(
         true,
     );
     txn.update_resources(resources);
     txn.set_root_pipeline(pipeline_id);
     txn.generate_frame();
     api.send_transaction(document_id, txn);
 
     println!("Entering event loop");
-    'outer: for event in window.wait_events() {
-        let mut events = Vec::new();
-        events.push(event);
-        events.extend(window.poll_events());
-
+    events_loop.run_forever(|global_event| {
         let mut txn = Transaction::new();
-        for event in events {
-            match event {
-                glutin::Event::Closed |
-                glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) => break 'outer,
+        let mut custom_event = true;
 
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::P),
-                ) => {
-                    renderer.toggle_debug_flags(webrender::DebugFlags::PROFILER_DBG);
-                }
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::O),
-                ) => {
-                    renderer.toggle_debug_flags(webrender::DebugFlags::RENDER_TARGET_DBG);
-                }
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::I),
-                ) => {
-                    renderer.toggle_debug_flags(webrender::DebugFlags::TEXTURE_CACHE_DBG);
-                }
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::B),
-                ) => {
-                    renderer.toggle_debug_flags(webrender::DebugFlags::ALPHA_PRIM_DBG);
-                }
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::S),
-                ) => {
-                    renderer.toggle_debug_flags(webrender::DebugFlags::COMPACT_PROFILER);
-                }
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::Q),
-                ) => {
-                    renderer.toggle_debug_flags(webrender::DebugFlags::GPU_TIME_QUERIES
-                        | webrender::DebugFlags::GPU_SAMPLE_QUERIES);
-                }
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::Key1),
-                ) => {
-                    txn.set_window_parameters(
-                        framebuffer_size,
-                        DeviceUintRect::new(DeviceUintPoint::zero(), framebuffer_size),
-                        1.0
-                    );
-                }
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::Key2),
-                ) => {
-                    txn.set_window_parameters(
-                        framebuffer_size,
-                        DeviceUintRect::new(DeviceUintPoint::zero(), framebuffer_size),
-                        2.0
-                    );
-                }
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::M),
-                ) => {
-                    api.notify_memory_pressure();
-                }
+        match global_event {
+            glutin::Event::WindowEvent { event: glutin::WindowEvent::Closed, .. } => return glutin::ControlFlow::Break,
+            glutin::Event::WindowEvent {
+                event: glutin::WindowEvent::KeyboardInput {
+                    input: glutin::KeyboardInput {
+                        state: glutin::ElementState::Pressed,
+                        virtual_keycode: Some(key),
+                        ..
+                    },
+                    ..
+                },
+                ..
+            } => match key {
+                glutin::VirtualKeyCode::Escape => return glutin::ControlFlow::Break,
+                glutin::VirtualKeyCode::P => renderer.toggle_debug_flags(webrender::DebugFlags::PROFILER_DBG),
+                glutin::VirtualKeyCode::O => renderer.toggle_debug_flags(webrender::DebugFlags::RENDER_TARGET_DBG),
+                glutin::VirtualKeyCode::I => renderer.toggle_debug_flags(webrender::DebugFlags::TEXTURE_CACHE_DBG),
+                glutin::VirtualKeyCode::S => renderer.toggle_debug_flags(webrender::DebugFlags::COMPACT_PROFILER),
+                glutin::VirtualKeyCode::Q => renderer.toggle_debug_flags(webrender::DebugFlags::GPU_TIME_QUERIES | webrender::DebugFlags::GPU_SAMPLE_QUERIES),
+                glutin::VirtualKeyCode::Key1 => txn.set_window_parameters(
+                    framebuffer_size,
+                    DeviceUintRect::new(DeviceUintPoint::zero(), framebuffer_size),
+                    1.0
+                ),
+                glutin::VirtualKeyCode::Key2 => txn.set_window_parameters(
+                    framebuffer_size,
+                    DeviceUintRect::new(DeviceUintPoint::zero(), framebuffer_size),
+                    2.0
+                ),
+                glutin::VirtualKeyCode::M => api.notify_memory_pressure(),
                 #[cfg(feature = "capture")]
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::C),
-                ) => {
+                glutin::VirtualKeyCode::C => {
                     let path: PathBuf = "../captures/example".into();
                     //TODO: switch between SCENE/FRAME capture types
                     // based on "shift" modifier, when `glutin` is updated.
                     let bits = CaptureBits::all();
                     api.save_capture(path, bits);
-                }
-                _ => if example.on_event(event, &api, document_id) {
-                    let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
-                    let mut resources = ResourceUpdates::new();
+                },
+                _ => {
+                    let win_event = match global_event {
+                        glutin::Event::WindowEvent { event, .. } => event,
+                        _ => unreachable!()
+                    };
+                    custom_event = example.on_event(win_event, &api, document_id)
+                },
+            },
+            glutin::Event::WindowEvent { event, .. } => custom_event = example.on_event(event, &api, document_id),
+            _ => return glutin::ControlFlow::Continue,
+        };
+
+        if custom_event {
+            let mut builder = DisplayListBuilder::new(pipeline_id, layout_size);
+            let mut resources = ResourceUpdates::new();
 
-                    example.render(
-                        &api,
-                        &mut builder,
-                        &mut resources,
-                        framebuffer_size,
-                        pipeline_id,
-                        document_id,
-                    );
-                    txn.set_display_list(
-                        epoch,
-                        None,
-                        layout_size,
-                        builder.finalize(),
-                        true,
-                    );
-                    txn.update_resources(resources);
-                    txn.generate_frame();
-                }
-            }
+            example.render(
+                &api,
+                &mut builder,
+                &mut resources,
+                framebuffer_size,
+                pipeline_id,
+                document_id,
+            );
+            txn.set_display_list(
+                epoch,
+                None,
+                layout_size,
+                builder.finalize(),
+                true,
+            );
+            txn.update_resources(resources);
+            txn.generate_frame();
         }
         api.send_transaction(document_id, txn);
 
         renderer.update();
         renderer.render(framebuffer_size).unwrap();
+        let _ = renderer.flush_pipeline_info();
         example.draw_custom(&*gl);
         window.swap_buffers().ok();
-    }
+
+        glutin::ControlFlow::Continue
+    });
 
     renderer.deinit();
 }
--- a/gfx/webrender/examples/image_resize.rs
+++ b/gfx/webrender/examples/image_resize.rs
@@ -74,44 +74,46 @@ impl Example for App {
             ImageRendering::Pixelated,
             AlphaType::PremultipliedAlpha,
             self.image_key,
         );
 
         builder.pop_stacking_context();
     }
 
-    fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
+    fn on_event(&mut self, event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
         match event {
-            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
-                match key {
-                    glutin::VirtualKeyCode::Space => {
-                        let mut image_data = Vec::new();
-                        for y in 0 .. 64 {
-                            for x in 0 .. 64 {
-                                let r = 255 * ((y & 32) == 0) as u8;
-                                let g = 255 * ((x & 32) == 0) as u8;
-                                image_data.extend_from_slice(&[0, g, r, 0xff]);
-                            }
-                        }
+            glutin::WindowEvent::KeyboardInput {
+                input: glutin::KeyboardInput {
+                    state: glutin::ElementState::Pressed,
+                    virtual_keycode: Some(glutin::VirtualKeyCode::Space),
+                    ..
+                },
+                ..
+            } => {
+                let mut image_data = Vec::new();
+                for y in 0 .. 64 {
+                    for x in 0 .. 64 {
+                        let r = 255 * ((y & 32) == 0) as u8;
+                        let g = 255 * ((x & 32) == 0) as u8;
+                        image_data.extend_from_slice(&[0, g, r, 0xff]);
+                    }
+                }
 
-                        let mut updates = ResourceUpdates::new();
-                        updates.update_image(
-                            self.image_key,
-                            ImageDescriptor::new(64, 64, ImageFormat::BGRA8, true),
-                            ImageData::new(image_data),
-                            None,
-                        );
-                        let mut txn = Transaction::new();
-                        txn.update_resources(updates);
-                        txn.generate_frame();
-                        api.send_transaction(document_id, txn);
-                    }
-                    _ => {}
-                }
+                let mut updates = ResourceUpdates::new();
+                updates.update_image(
+                    self.image_key,
+                    ImageDescriptor::new(64, 64, ImageFormat::BGRA8, true),
+                    ImageData::new(image_data),
+                    None,
+                );
+                let mut txn = Transaction::new();
+                txn.update_resources(updates);
+                txn.generate_frame();
+                api.send_transaction(document_id, txn);
             }
             _ => {}
         }
 
         false
     }
 }
 
--- a/gfx/webrender/examples/multiwindow.rs
+++ b/gfx/webrender/examples/multiwindow.rs
@@ -3,71 +3,75 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 extern crate app_units;
 extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 extern crate webrender;
 
-use app_units::Au;
 use std::fs::File;
 use std::io::Read;
 use webrender::api::*;
+use app_units::Au;
 use gleam::gl;
+use glutin::GlContext;
 
 struct Notifier {
-    window_proxy: glutin::WindowProxy,
+    events_proxy: glutin::EventsLoopProxy,
 }
 
 impl Notifier {
-    fn new(window_proxy: glutin::WindowProxy) -> Notifier {
-        Notifier { window_proxy }
+    fn new(events_proxy: glutin::EventsLoopProxy) -> Notifier {
+        Notifier { events_proxy }
     }
 }
 
 impl RenderNotifier for Notifier {
     fn clone(&self) -> Box<RenderNotifier> {
         Box::new(Notifier {
-            window_proxy: self.window_proxy.clone(),
+            events_proxy: self.events_proxy.clone(),
         })
     }
 
     fn wake_up(&self) {
         #[cfg(not(target_os = "android"))]
-        self.window_proxy.wakeup_event_loop();
+        let _ = self.events_proxy.wakeup();
     }
 
     fn new_document_ready(&self, _: DocumentId, _scrolled: bool, _composite_needed: bool) {
         self.wake_up();
     }
 }
 
 struct Window {
-    window: glutin::Window,
+    events_loop: glutin::EventsLoop, //TODO: share events loop?
+    window: glutin::GlWindow,
     renderer: webrender::Renderer,
     name: &'static str,
     pipeline_id: PipelineId,
     document_id: DocumentId,
     epoch: Epoch,
     api: RenderApi,
     font_instance_key: FontInstanceKey,
 }
 
 impl Window {
-    fn new(name: &'static str, clear_color: ColorF) -> Window {
-        let window = glutin::WindowBuilder::new()
-            .with_title(name)
-            .with_multitouch()
-            .with_dimensions(800, 600)
+    fn new(name: &'static str, clear_color: ColorF) -> Self {
+        let events_loop = glutin::EventsLoop::new();
+        let context_builder = glutin::ContextBuilder::new()
             .with_gl(glutin::GlRequest::GlThenGles {
                 opengl_version: (3, 2),
                 opengles_version: (3, 0),
-            })
-            .build()
+            });
+        let window_builder = glutin::WindowBuilder::new()
+            .with_title(name)
+            .with_multitouch()
+            .with_dimensions(800, 600);
+        let window = glutin::GlWindow::new(window_builder, context_builder, &events_loop)
             .unwrap();
 
         unsafe {
             window.make_current().ok();
         }
 
         let gl = match window.get_api() {
             glutin::Api::OpenGl => unsafe {
@@ -84,20 +88,20 @@ impl Window {
         let opts = webrender::RendererOptions {
             debug: true,
             device_pixel_ratio,
             clear_color: Some(clear_color),
             ..webrender::RendererOptions::default()
         };
 
         let framebuffer_size = {
-            let (width, height) = window.get_inner_size_pixels().unwrap();
+            let (width, height) = window.get_inner_size().unwrap();
             DeviceUintSize::new(width, height)
         };
-        let notifier = Box::new(Notifier::new(window.create_window_proxy()));
+        let notifier = Box::new(Notifier::new(events_loop.create_proxy()));
         let (renderer, sender) = webrender::Renderer::new(gl.clone(), notifier, opts).unwrap();
         let api = sender.create_api();
         let document_id = api.add_document(framebuffer_size, 0);
 
         let epoch = Epoch(0);
         let pipeline_id = PipelineId(0, 0);
         let mut resources = ResourceUpdates::new();
 
@@ -108,52 +112,69 @@ impl Window {
         let font_instance_key = api.generate_font_instance_key();
         resources.add_font_instance(font_instance_key, font_key, Au::from_px(32), None, None, Vec::new());
 
         let mut txn = Transaction::new();
         txn.update_resources(resources);
         api.send_transaction(document_id, txn);
 
         Window {
+            events_loop,
             window,
             renderer,
             name,
             epoch,
             pipeline_id,
             document_id,
             api,
             font_instance_key,
         }
     }
 
     fn tick(&mut self) -> bool {
         unsafe {
             self.window.make_current().ok();
         }
-
-        for event in self.window.poll_events() {
-            match event {
-                glutin::Event::Closed |
-                glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) => return true,
+        let mut do_exit = false;
+        let my_name = &self.name;
+        let renderer = &mut self.renderer;
 
-                glutin::Event::KeyboardInput(
-                    glutin::ElementState::Pressed,
-                    _,
-                    Some(glutin::VirtualKeyCode::P),
-                ) => {
-                    println!("toggle flags {}", self.name);
-                    self.renderer.toggle_debug_flags(webrender::DebugFlags::PROFILER_DBG);
+        self.events_loop.poll_events(|global_event| match global_event {
+            glutin::Event::WindowEvent { event, .. } => match event {
+                glutin::WindowEvent::Closed |
+                glutin::WindowEvent::KeyboardInput {
+                    input: glutin::KeyboardInput {
+                        virtual_keycode: Some(glutin::VirtualKeyCode::Escape),
+                        ..
+                    },
+                    ..
+                } => {
+                    do_exit = true
                 }
-
+                glutin::WindowEvent::KeyboardInput {
+                    input: glutin::KeyboardInput {
+                        state: glutin::ElementState::Pressed,
+                        virtual_keycode: Some(glutin::VirtualKeyCode::P),
+                        ..
+                    },
+                    ..
+                } => {
+                    println!("toggle flags {}", my_name);
+                    renderer.toggle_debug_flags(webrender::DebugFlags::PROFILER_DBG);
+                }
                 _ => {}
             }
+            _ => {}
+        });
+        if do_exit {
+            return true
         }
 
         let framebuffer_size = {
-            let (width, height) = self.window.get_inner_size_pixels().unwrap();
+            let (width, height) = self.window.get_inner_size().unwrap();
             DeviceUintSize::new(width, height)
         };
         let device_pixel_ratio = self.window.hidpi_factor();
         let layout_size = framebuffer_size.to_f32() / euclid::TypedScale::new(device_pixel_ratio);
         let mut txn = Transaction::new();
         let mut builder = DisplayListBuilder::new(self.pipeline_id, layout_size);
 
         let bounds = LayoutRect::new(LayoutPoint::zero(), builder.content_size());
@@ -240,18 +261,18 @@ impl Window {
             layout_size,
             builder.finalize(),
             true,
         );
         txn.set_root_pipeline(self.pipeline_id);
         txn.generate_frame();
         self.api.send_transaction(self.document_id, txn);
 
-        self.renderer.update();
-        self.renderer.render(framebuffer_size).unwrap();
+        renderer.update();
+        renderer.render(framebuffer_size).unwrap();
         self.window.swap_buffers().ok();
 
         false
     }
 
     fn deinit(self) {
         self.renderer.deinit();
     }
--- a/gfx/webrender/examples/scrolling.rs
+++ b/gfx/webrender/examples/scrolling.rs
@@ -40,19 +40,18 @@ impl Example for App {
             MixBlendMode::Normal,
             Vec::new(),
         );
 
         if true {
             // scrolling and clips stuff
             // let's make a scrollbox
             let scrollbox = (0, 0).to(300, 400);
-            let info = LayoutPrimitiveInfo::new((10, 10).by(0, 0));
             builder.push_stacking_context(
-                &info,
+                &LayoutPrimitiveInfo::new((10, 10).by(0, 0)),
                 ScrollPolicy::Scrollable,
                 None,
                 TransformStyle::Flat,
                 None,
                 MixBlendMode::Normal,
                 Vec::new(),
             );
             // set the scrolling clip
@@ -63,27 +62,30 @@ impl Example for App {
                 vec![],
                 None,
                 ScrollSensitivity::ScriptAndInputEvents,
             );
             builder.push_clip_id(clip_id);
 
             // now put some content into it.
             // start with a white background
-            let info = LayoutPrimitiveInfo::new((0, 0).to(1000, 1000));
+            let mut info = LayoutPrimitiveInfo::new((0, 0).to(1000, 1000));
+            info.tag = Some((0, 1));
             builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
             // let's make a 50x50 blue square as a visual reference
-            let info = LayoutPrimitiveInfo::new((0, 0).to(50, 50));
+            let mut info = LayoutPrimitiveInfo::new((0, 0).to(50, 50));
+            info.tag = Some((0, 2));
             builder.push_rect(&info, ColorF::new(0.0, 0.0, 1.0, 1.0));
 
             // and a 50x50 green square next to it with an offset clip
             // to see what that looks like
-            let info =
+            let mut info =
                 LayoutPrimitiveInfo::with_clip_rect((50, 0).to(100, 50), (60, 10).to(110, 60));
+            info.tag = Some((0, 3));
             builder.push_rect(&info, ColorF::new(0.0, 1.0, 0.0, 1.0));
 
             // Below the above rectangles, set up a nested scrollbox. It's still in
             // the same stacking context, so note that the rects passed in need to
             // be relative to the stacking context.
             let nested_clip_id = builder.define_scroll_frame(
                 None,
                 (0, 100).to(300, 1000),
@@ -91,93 +93,114 @@ impl Example for App {
                 vec![],
                 None,
                 ScrollSensitivity::ScriptAndInputEvents,
             );
             builder.push_clip_id(nested_clip_id);
 
             // give it a giant gray background just to distinguish it and to easily
             // visually identify the nested scrollbox
-            let info = LayoutPrimitiveInfo::new((-1000, -1000).to(5000, 5000));
+            let mut info = LayoutPrimitiveInfo::new((-1000, -1000).to(5000, 5000));
+            info.tag = Some((0, 4));
             builder.push_rect(&info, ColorF::new(0.5, 0.5, 0.5, 1.0));
 
             // add a teal square to visualize the scrolling/clipping behaviour
             // as you scroll the nested scrollbox
-            let info = LayoutPrimitiveInfo::new((0, 200).to(50, 250));
+            let mut info = LayoutPrimitiveInfo::new((0, 200).to(50, 250));
+            info.tag = Some((0, 5));
             builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
             // Add a sticky frame. It will "stick" twice while scrolling, once
             // at a margin of 10px from the bottom, for 40 pixels of scrolling,
             // and once at a margin of 10px from the top, for 60 pixels of
             // scrolling.
             let sticky_id = builder.define_sticky_frame(
                 (50, 350).by(50, 50),
                 SideOffsets2D::new(Some(10.0), None, Some(10.0), None),
                 StickyOffsetBounds::new(-40.0, 60.0),
                 StickyOffsetBounds::new(0.0, 0.0),
                 LayoutVector2D::new(0.0, 0.0)
             );
 
             builder.push_clip_id(sticky_id);
-            let info = LayoutPrimitiveInfo::new((50, 350).by(50, 50));
+            let mut info = LayoutPrimitiveInfo::new((50, 350).by(50, 50));
+            info.tag = Some((0, 6));
             builder.push_rect(&info, ColorF::new(0.5, 0.5, 1.0, 1.0));
             builder.pop_clip_id(); // sticky_id
 
             // just for good measure add another teal square further down and to
             // the right, which can be scrolled into view by the user
-            let info = LayoutPrimitiveInfo::new((250, 350).to(300, 400));
+            let mut info = LayoutPrimitiveInfo::new((250, 350).to(300, 400));
+            info.tag = Some((0, 7));
             builder.push_rect(&info, ColorF::new(0.0, 1.0, 1.0, 1.0));
 
             builder.pop_clip_id(); // nested_clip_id
 
             builder.pop_clip_id(); // clip_id
             builder.pop_stacking_context();
         }
 
         builder.pop_stacking_context();
     }
 
-    fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
+    fn on_event(&mut self, event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
         let mut txn = Transaction::new();
         match event {
-            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+            glutin::WindowEvent::KeyboardInput {
+                input: glutin::KeyboardInput {
+                    state: glutin::ElementState::Pressed,
+                    virtual_keycode: Some(key),
+                    ..
+                },
+                ..
+            } => {
                 let offset = match key {
                     glutin::VirtualKeyCode::Down => (0.0, -10.0),
                     glutin::VirtualKeyCode::Up => (0.0, 10.0),
                     glutin::VirtualKeyCode::Right => (-10.0, 0.0),
                     glutin::VirtualKeyCode::Left => (10.0, 0.0),
                     _ => return false,
                 };
 
                 txn.scroll(
                     ScrollLocation::Delta(LayoutVector2D::new(offset.0, offset.1)),
                     self.cursor_position,
                     ScrollEventPhase::Start,
                 );
             }
-            glutin::Event::MouseMoved(x, y) => {
+            glutin::WindowEvent::CursorMoved { position: (x, y), .. } => {
                 self.cursor_position = WorldPoint::new(x as f32, y as f32);
             }
-            glutin::Event::MouseWheel(delta, _, event_cursor_position) => {
-                if let Some((x, y)) = event_cursor_position {
-                    self.cursor_position = WorldPoint::new(x as f32, y as f32);
-                }
-
+            glutin::WindowEvent::MouseWheel { delta, .. } => {
                 const LINE_HEIGHT: f32 = 38.0;
                 let (dx, dy) = match delta {
                     glutin::MouseScrollDelta::LineDelta(dx, dy) => (dx, dy * LINE_HEIGHT),
                     glutin::MouseScrollDelta::PixelDelta(dx, dy) => (dx, dy),
                 };
 
                 txn.scroll(
                     ScrollLocation::Delta(LayoutVector2D::new(dx, dy)),
                     self.cursor_position,
                     ScrollEventPhase::Start,
                 );
             }
+            glutin::WindowEvent::MouseInput { .. } => {
+                let results = api.hit_test(
+                    document_id,
+                    None,
+                    self.cursor_position,
+                    HitTestFlags::FIND_ALL
+                );
+
+                println!("Hit test results:");
+                for item in &results.items {
+                    println!("  • {:?}", item);
+                }
+                println!("");
+            }
             _ => (),
         }
 
         api.send_transaction(document_id, txn);
 
         false
     }
 }
--- a/gfx/webrender/examples/texture_cache_stress.rs
+++ b/gfx/webrender/examples/texture_cache_stress.rs
@@ -181,22 +181,29 @@ impl Example for App {
         );
         self.swap_index = 1 - self.swap_index;
 
         builder.pop_stacking_context();
     }
 
     fn on_event(
         &mut self,
-        event: glutin::Event,
+        event: glutin::WindowEvent,
         api: &RenderApi,
         _document_id: DocumentId,
     ) -> bool {
         match event {
-            glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, Some(key)) => {
+            glutin::WindowEvent::KeyboardInput {
+                input: glutin::KeyboardInput {
+                    state: glutin::ElementState::Pressed,
+                    virtual_keycode: Some(key),
+                    ..
+                },
+                ..
+            } => {
                 let mut updates = ResourceUpdates::new();
 
                 match key {
                     glutin::VirtualKeyCode::S => {
                         self.stress_keys.clear();
 
                         for _ in 0 .. 16 {
                             for _ in 0 .. 16 {
--- a/gfx/webrender/examples/yuv.rs
+++ b/gfx/webrender/examples/yuv.rs
@@ -235,20 +235,20 @@ impl Example for App {
             YuvData::PlanarYCbCr(yuv_chanel1, yuv_chanel2_1, yuv_chanel3),
             YuvColorSpace::Rec601,
             ImageRendering::Auto,
         );
 
         builder.pop_stacking_context();
     }
 
-    fn on_event(&mut self, event: glutin::Event, api: &RenderApi, document_id: DocumentId) -> bool {
+    fn on_event(&mut self, event: glutin::WindowEvent, api: &RenderApi, document_id: DocumentId) -> bool {
         let mut txn = Transaction::new();
         match event {
-            glutin::Event::Touch(touch) => match self.touch_state.handle_event(touch) {
+            glutin::WindowEvent::Touch(touch) => match self.touch_state.handle_event(touch) {
                 TouchResult::Pan(pan) => {
                     txn.set_pan(pan);
                 }
                 TouchResult::Zoom(zoom) => {
                     txn.set_pinch_zoom(ZoomFactor::new(zoom));
                 }
                 TouchResult::None => {}
             },
--- a/gfx/webrender/res/brush_line.glsl
+++ b/gfx/webrender/res/brush_line.glsl
@@ -35,44 +35,45 @@ void brush_vs(
     int prim_address,
     vec2 local_pos,
     RectWithSize local_rect,
     ivec2 user_data,
     PictureTask pic_task
 ) {
     vLocalPos = local_pos;
 
-    Line line = fetch_line(prim_address);
+    // Note: `line` name is reserved in HLSL
+    Line line_prim = fetch_line(prim_address);
 
     switch (int(abs(pic_task.pic_kind_and_raster_mode))) {
         case PIC_TYPE_TEXT_SHADOW:
             vColor = pic_task.color;
             break;
         default:
-            vColor = line.color;
+            vColor = line_prim.color;
             break;
     }
 
     vec2 pos, size;
 
-    switch (int(line.orientation)) {
+    switch (int(line_prim.orientation)) {
         case LINE_ORIENTATION_HORIZONTAL:
             vAxisSelect = 0.0;
             pos = local_rect.p0;
             size = local_rect.size;
             break;
         case LINE_ORIENTATION_VERTICAL:
             vAxisSelect = 1.0;
             pos = local_rect.p0.yx;
             size = local_rect.size.yx;
             break;
     }
 
     vLocalOrigin = pos;
-    vStyle = int(line.style);
+    vStyle = int(line_prim.style);
 
     switch (vStyle) {
         case LINE_STYLE_SOLID: {
             break;
         }
         case LINE_STYLE_DASHED: {
             float dash_length = size.y * 3.0;
             vParams = vec4(2.0 * dash_length, // period
@@ -89,17 +90,17 @@ void brush_vs(
             vParams = vec4(period,
                            diameter / 2.0, // radius
                            center_line,
                            max_x);
             break;
         }
         case LINE_STYLE_WAVY: {
             // This logic copied from gecko to get the same results
-            float line_thickness = max(line.wavyLineThickness, 1.0);
+            float line_thickness = max(line_prim.wavyLineThickness, 1.0);
             // Difference in height between peaks and troughs
             // (and since slopes are 45 degrees, the length of each slope)
             float slope_length = size.y - line_thickness;
             // Length of flat runs
             float flat_length = max((line_thickness - 1.0) * 2.0, 1.0);
 
             vParams = vec4(line_thickness / 2.0,
                            slope_length,
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -19,18 +19,18 @@
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
 
 // An A8 target for standalone tasks that is available to all passes.
 uniform sampler2DArray sSharedCacheA8;
 
 uniform sampler2D sGradients;
 
-vec2 clamp_rect(vec2 point, RectWithSize rect) {
-    return clamp(point, rect.p0, rect.p0 + rect.size);
+vec2 clamp_rect(vec2 pt, RectWithSize rect) {
+    return clamp(pt, rect.p0, rect.p0 + rect.size);
 }
 
 float distance_to_line(vec2 p0, vec2 perp_dir, vec2 p) {
     vec2 dir_to_p0 = p0 - p;
     return dot(normalize(perp_dir), dir_to_p0);
 }
 
 // TODO: convert back to RectWithEndPoint if driver issues are resolved, if ever.
@@ -476,21 +476,21 @@ Primitive load_primitive() {
     prim.z = float(pi.z);
 
     return prim;
 }
 
 // Return the intersection of the plane (set up by "normal" and "point")
 // with the ray (set up by "ray_origin" and "ray_dir"),
 // writing the resulting scaler into "t".
-bool ray_plane(vec3 normal, vec3 point, vec3 ray_origin, vec3 ray_dir, out float t)
+bool ray_plane(vec3 normal, vec3 pt, vec3 ray_origin, vec3 ray_dir, out float t)
 {
     float denom = dot(normal, ray_dir);
     if (abs(denom) > 1e-6) {
-        vec3 d = point - ray_origin;
+        vec3 d = pt - ray_origin;
         t = dot(d, normal) / denom;
         return t >= 0.0;
     }
 
     return false;
 }
 
 // Apply the inverse transform "inv_transform"
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -1,36 +1,36 @@
 /* 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, DeviceIntRect, DeviceIntSize, ImageKey, LayerToWorldScale};
+use api::{AlphaType, DeviceIntRect, DeviceIntSize, LayerToWorldScale};
 use api::{DeviceUintRect, DeviceUintPoint, DeviceUintSize, ExternalImageType, FilterOp, ImageRendering, LayerRect};
-use api::{SubpixelDirection, TileOffset, YuvColorSpace, YuvFormat};
+use api::{DeviceIntPoint, LayerPoint, SubpixelDirection, YuvColorSpace, YuvFormat};
 use api::{LayerToWorldTransform, WorldPixel};
 use border::{BorderCornerInstance, BorderCornerSide, BorderEdgeKind};
 use clip::{ClipSource, ClipStore};
 use clip_scroll_tree::{CoordinateSystemId};
 use euclid::{TypedTransform3D, vec3};
 use glyph_rasterizer::GlyphFormat;
 use gpu_cache::{GpuCache, GpuCacheAddress};
 use gpu_types::{BrushImageKind, BrushInstance, ClipChainRectIndex};
-use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex, PictureType};
+use gpu_types::{ClipMaskInstance, ClipScrollNodeIndex};
 use gpu_types::{CompositePrimitiveInstance, PrimitiveInstance, SimplePrimitiveInstance};
 use internal_types::{FastHashMap, SourceTexture};
-use picture::{PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
+use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Polygon, Splitter};
 use prim_store::{ImageSource, PrimitiveIndex, PrimitiveKind, PrimitiveMetadata, PrimitiveStore};
 use prim_store::{BrushPrimitive, BrushKind, DeferredResolve, EdgeAaSegmentMask, PrimitiveRun};
 use render_task::{ClipWorkItem};
-use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind};
-use render_task::{RenderTaskTree};
+use render_task::{RenderTaskAddress, RenderTaskId};
+use render_task::{RenderTaskKind, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind};
 use renderer::BLOCKS_PER_UV_RECT;
-use resource_cache::{CacheItem, GlyphFetchResult, ResourceCache};
+use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache};
 use std::{usize, f32, i32};
 use tiling::{RenderTargetContext, RenderTargetKind};
 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(i32::MAX as u32);
 
@@ -119,52 +119,16 @@ impl BatchTextures {
 
     pub fn color(texture: SourceTexture) -> Self {
         BatchTextures {
             colors: [texture, texture, SourceTexture::Invalid],
         }
     }
 }
 
-#[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct AlphaPrimitiveBatch {
-    pub key: BatchKey,
-    pub instances: Vec<PrimitiveInstance>,
-    pub item_rects: Vec<DeviceIntRect>,
-}
-
-impl AlphaPrimitiveBatch {
-    pub fn new(key: BatchKey) -> AlphaPrimitiveBatch {
-        AlphaPrimitiveBatch {
-            key,
-            instances: Vec::new(),
-            item_rects: Vec::new(),
-        }
-    }
-}
-
-#[derive(Debug)]
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct OpaquePrimitiveBatch {
-    pub key: BatchKey,
-    pub instances: Vec<PrimitiveInstance>,
-}
-
-impl OpaquePrimitiveBatch {
-    pub fn new(key: BatchKey) -> OpaquePrimitiveBatch {
-        OpaquePrimitiveBatch {
-            key,
-            instances: Vec::new(),
-        }
-    }
-}
-
 #[derive(Copy, Clone, Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BatchKey {
     pub kind: BatchKind,
     pub blend_mode: BlendMode,
     pub textures: BatchTextures,
 }
@@ -186,51 +150,51 @@ impl BatchKey {
     }
 }
 
 #[inline]
 fn textures_compatible(t1: SourceTexture, t2: SourceTexture) -> bool {
     t1 == SourceTexture::Invalid || t2 == SourceTexture::Invalid || t1 == t2
 }
 
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaBatchList {
-    pub batches: Vec<AlphaPrimitiveBatch>,
+    pub batches: Vec<PrimitiveBatch>,
+    pub item_rects: Vec<Vec<DeviceIntRect>>,
 }
 
 impl AlphaBatchList {
     fn new() -> Self {
         AlphaBatchList {
             batches: Vec::new(),
+            item_rects: Vec::new(),
         }
     }
 
     pub fn get_suitable_batch(
         &mut self,
         key: BatchKey,
-        item_bounding_rect: &DeviceIntRect,
+        task_relative_bounding_rect: &DeviceIntRect,
     ) -> &mut Vec<PrimitiveInstance> {
         let mut selected_batch_index = None;
 
         match (key.kind, key.blend_mode) {
             (BatchKind::Composite { .. }, _) => {
                 // Composites always get added to their own batch.
                 // This is because the result of a composite can affect
                 // the input to the next composite. Perhaps we can
                 // optimize this in the future.
             }
             (BatchKind::Transformable(_, TransformBatchKind::TextRun(_)), BlendMode::SubpixelWithBgColor) |
             (BatchKind::Transformable(_, TransformBatchKind::TextRun(_)), BlendMode::SubpixelVariableTextColor) => {
                 'outer_text: for (batch_index, batch) in self.batches.iter().enumerate().rev().take(10) {
                     // Subpixel text is drawn in two passes. Because of this, we need
                     // to check for overlaps with every batch (which is a bit different
                     // than the normal batching below).
-                    for item_rect in &batch.item_rects {
-                        if item_rect.intersects(item_bounding_rect) {
+                    for item_rect in &self.item_rects[batch_index] {
+                        if item_rect.intersects(task_relative_bounding_rect) {
                             break 'outer_text;
                         }
                     }
 
                     if batch.key.is_compatible_with(&key) {
                         selected_batch_index = Some(batch_index);
                         break;
                     }
@@ -243,60 +207,58 @@ impl AlphaBatchList {
                     // is compatible, then we know there isn't any potential overlap
                     // issues to worry about.
                     if batch.key.is_compatible_with(&key) {
                         selected_batch_index = Some(batch_index);
                         break;
                     }
 
                     // check for intersections
-                    for item_rect in &batch.item_rects {
-                        if item_rect.intersects(item_bounding_rect) {
+                    for item_rect in &self.item_rects[batch_index] {
+                        if item_rect.intersects(task_relative_bounding_rect) {
                             break 'outer_default;
                         }
                     }
                 }
             }
         }
 
         if selected_batch_index.is_none() {
-            let new_batch = AlphaPrimitiveBatch::new(key);
+            let new_batch = PrimitiveBatch::new(key);
             selected_batch_index = Some(self.batches.len());
             self.batches.push(new_batch);
+            self.item_rects.push(Vec::new());
         }
 
-        let batch = &mut self.batches[selected_batch_index.unwrap()];
-        batch.item_rects.push(*item_bounding_rect);
-
-        &mut batch.instances
+        let selected_batch_index = selected_batch_index.unwrap();
+        self.item_rects[selected_batch_index].push(*task_relative_bounding_rect);
+        &mut self.batches[selected_batch_index].instances
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct OpaqueBatchList {
-    pub pixel_area_threshold_for_new_batch: i32,
-    pub batches: Vec<OpaquePrimitiveBatch>,
+    pub pixel_area_threshold_for_new_batch: f32,
+    pub batches: Vec<PrimitiveBatch>,
 }
 
 impl OpaqueBatchList {
-    fn new(pixel_area_threshold_for_new_batch: i32) -> Self {
+    fn new(pixel_area_threshold_for_new_batch: f32) -> Self {
         OpaqueBatchList {
             batches: Vec::new(),
             pixel_area_threshold_for_new_batch,
         }
     }
 
     pub fn get_suitable_batch(
         &mut self,
         key: BatchKey,
-        item_bounding_rect: &DeviceIntRect
+        task_relative_bounding_rect: &DeviceIntRect
     ) -> &mut Vec<PrimitiveInstance> {
         let mut selected_batch_index = None;
-        let item_area = item_bounding_rect.size.area();
+        let item_area = task_relative_bounding_rect.size.to_f32().area();
 
         // If the area of this primitive is larger than the given threshold,
         // then it is large enough to warrant breaking a batch for. In this
         // case we just see if it can be added to the existing batch or
         // create a new one.
         if item_area > self.pixel_area_threshold_for_new_batch {
             if let Some(ref batch) = self.batches.last() {
                 if batch.key.is_compatible_with(&key) {
@@ -309,17 +271,17 @@ impl OpaqueBatchList {
                 if batch.key.is_compatible_with(&key) {
                     selected_batch_index = Some(batch_index);
                     break;
                 }
             }
         }
 
         if selected_batch_index.is_none() {
-            let new_batch = OpaquePrimitiveBatch::new(key);
+            let new_batch = PrimitiveBatch::new(key);
             selected_batch_index = Some(self.batches.len());
             self.batches.push(new_batch);
         }
 
         let batch = &mut self.batches[selected_batch_index.unwrap()];
 
         &mut batch.instances
     }
@@ -332,128 +294,205 @@ impl OpaqueBatchList {
         //           build these in reverse and avoid having
         //           to reverse the instance array here.
         for batch in &mut self.batches {
             batch.instances.reverse();
         }
     }
 }
 
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct BatchList {
     pub alpha_batch_list: AlphaBatchList,
     pub opaque_batch_list: OpaqueBatchList,
+    pub combined_bounding_rect: DeviceIntRect,
 }
 
 impl BatchList {
     pub fn new(screen_size: DeviceIntSize) -> Self {
         // The threshold for creating a new batch is
         // one quarter the screen size.
-        let batch_area_threshold = screen_size.width * screen_size.height / 4;
+        let batch_area_threshold = (screen_size.width * screen_size.height) as f32 / 4.0;
 
         BatchList {
             alpha_batch_list: AlphaBatchList::new(),
             opaque_batch_list: OpaqueBatchList::new(batch_area_threshold),
+            combined_bounding_rect: DeviceIntRect::zero(),
         }
     }
 
     pub fn get_suitable_batch(
         &mut self,
         key: BatchKey,
-        item_bounding_rect: &DeviceIntRect,
+        task_relative_bounding_rect: &DeviceIntRect,
     ) -> &mut Vec<PrimitiveInstance> {
+        self.combined_bounding_rect = self.combined_bounding_rect.union(task_relative_bounding_rect);
+
         match key.blend_mode {
             BlendMode::None => {
                 self.opaque_batch_list
-                    .get_suitable_batch(key, item_bounding_rect)
+                    .get_suitable_batch(key, task_relative_bounding_rect)
             }
             BlendMode::Alpha |
             BlendMode::PremultipliedAlpha |
             BlendMode::PremultipliedDestOut |
             BlendMode::SubpixelConstantTextColor(..) |
             BlendMode::SubpixelVariableTextColor |
             BlendMode::SubpixelWithBgColor |
             BlendMode::SubpixelDualSource => {
                 self.alpha_batch_list
-                    .get_suitable_batch(key, item_bounding_rect)
+                    .get_suitable_batch(key, task_relative_bounding_rect)
             }
         }
     }
 
     fn finalize(&mut self) {
         self.opaque_batch_list.finalize()
     }
 }
 
-/// Encapsulates the logic of building batches for items that are blended.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-pub struct AlphaBatcher {
-    pub batch_list: BatchList,
-    pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
-    glyph_fetch_buffer: Vec<GlyphFetchResult>,
+pub struct PrimitiveBatch {
+    pub key: BatchKey,
+    pub instances: Vec<PrimitiveInstance>,
+}
+
+impl PrimitiveBatch {
+    fn new(key: BatchKey) -> PrimitiveBatch {
+        PrimitiveBatch {
+            key,
+            instances: Vec::new(),
+        }
+    }
 }
 
-impl AlphaBatcher {
-    pub fn new(screen_size: DeviceIntSize) -> Self {
-        AlphaBatcher {
-            batch_list: BatchList::new(screen_size),
-            glyph_fetch_buffer: Vec::new(),
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct AlphaBatchContainer {
+    pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
+    pub opaque_batches: Vec<PrimitiveBatch>,
+    pub alpha_batches: Vec<PrimitiveBatch>,
+    pub target_rect: Option<DeviceIntRect>,
+}
+
+impl AlphaBatchContainer {
+    pub fn new(target_rect: Option<DeviceIntRect>) -> AlphaBatchContainer {
+        AlphaBatchContainer {
             text_run_cache_prims: FastHashMap::default(),
+            opaque_batches: Vec::new(),
+            alpha_batches: Vec::new(),
+            target_rect,
         }
     }
 
-    pub fn build(
-        &mut self,
-        tasks: &[RenderTaskId],
-        ctx: &RenderTargetContext,
-        gpu_cache: &mut GpuCache,
-        render_tasks: &RenderTaskTree,
-        deferred_resolves: &mut Vec<DeferredResolve>,
-    ) {
-        for &task_id in tasks {
-            match render_tasks[task_id].kind {
-                RenderTaskKind::Picture(ref pic_task) => {
-                    let pic_index = ctx.prim_store.cpu_metadata[pic_task.prim_index.0].cpu_prim_index;
-                    let pic = &ctx.prim_store.cpu_pictures[pic_index.0];
-                    self.add_pic_to_batch(
-                        pic,
-                        task_id,
-                        ctx,
-                        gpu_cache,
-                        render_tasks,
-                        deferred_resolves,
-                    );
+    fn merge(&mut self, builder: AlphaBatchBuilder) {
+        self.text_run_cache_prims.extend(builder.text_run_cache_prims);
+
+        for other_batch in builder.batch_list.opaque_batch_list.batches {
+            let batch_index = self.opaque_batches.iter().position(|batch| {
+                batch.key.is_compatible_with(&other_batch.key)
+            });
+
+            match batch_index {
+                Some(batch_index) => {
+                    self.opaque_batches[batch_index].instances.extend(other_batch.instances);
                 }
-                _ => {
-                    unreachable!();
+                None => {
+                    self.opaque_batches.push(other_batch);
                 }
             }
         }
 
-        self.batch_list.finalize();
+        let mut min_batch_index = 0;
+
+        for other_batch in builder.batch_list.alpha_batch_list.batches {
+            let batch_index = self.alpha_batches.iter().skip(min_batch_index).position(|batch| {
+                batch.key.is_compatible_with(&other_batch.key)
+            });
+
+            match batch_index {
+                Some(batch_index) => {
+                    let batch_index = batch_index + min_batch_index;
+                    self.alpha_batches[batch_index].instances.extend(other_batch.instances);
+                    min_batch_index = batch_index;
+                }
+                None => {
+                    self.alpha_batches.push(other_batch);
+                    min_batch_index = self.alpha_batches.len();
+                }
+            }
+        }
+    }
+}
+
+/// Encapsulates the logic of building batches for items that are blended.
+pub struct AlphaBatchBuilder {
+    pub batch_list: BatchList,
+    pub text_run_cache_prims: FastHashMap<SourceTexture, Vec<PrimitiveInstance>>,
+    glyph_fetch_buffer: Vec<GlyphFetchResult>,
+    target_rect: DeviceIntRect,
+}
+
+impl AlphaBatchBuilder {
+    pub fn new(
+        screen_size: DeviceIntSize,
+        target_rect: DeviceIntRect,
+    ) -> Self {
+        AlphaBatchBuilder {
+            batch_list: BatchList::new(screen_size),
+            glyph_fetch_buffer: Vec::new(),
+            text_run_cache_prims: FastHashMap::default(),
+            target_rect,
+        }
     }
 
-    pub fn is_empty(&self) -> bool {
-        self.batch_list.opaque_batch_list.batches.is_empty() &&
-            self.batch_list.alpha_batch_list.batches.is_empty()
+    pub fn build(mut self, merged_batches: &mut AlphaBatchContainer) -> Option<AlphaBatchContainer> {
+        self.batch_list.finalize();
+
+        let task_relative_target_rect = DeviceIntRect::new(
+            DeviceIntPoint::zero(),
+            self.target_rect.size,
+        );
+
+        let can_merge = task_relative_target_rect.contains_rect(&self.batch_list.combined_bounding_rect);
+
+        if can_merge {
+            merged_batches.merge(self);
+            None
+        } else {
+            Some(AlphaBatchContainer {
+                alpha_batches: self.batch_list.alpha_batch_list.batches,
+                opaque_batches: self.batch_list.opaque_batch_list.batches,
+                target_rect: Some(self.target_rect),
+                text_run_cache_prims: self.text_run_cache_prims,
+            })
+        }
     }
 
-    fn add_pic_to_batch(
+    pub fn add_pic_to_batch(
         &mut self,
         pic: &PicturePrimitive,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
         let task_address = render_tasks.get_task_address(task_id);
 
+        let task = &render_tasks[task_id];
+        let content_origin = match task.kind {
+            RenderTaskKind::Picture(ref pic_task) => {
+                pic_task.content_origin
+            }
+            _ => {
+                panic!("todo: tidy this up");
+            }
+        };
+
         // 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_id = scroll_node.node_data_index;
@@ -462,17 +501,18 @@ impl AlphaBatcher {
                 scroll_id,
                 ctx,
                 gpu_cache,
                 render_tasks,
                 task_id,
                 task_address,
                 deferred_resolves,
                 &mut splitter,
-                pic.picture_type(),
+                pic,
+                content_origin,
             );
         }
 
         // Flush the accumulated plane splits onto the task tree.
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
             let prim_index = PrimitiveIndex(poly.anchor);
             debug!("process sorted poly {:?} {:?}", prim_index, poly.points);
@@ -485,17 +525,17 @@ impl AlphaBatcher {
             let gpu_handle = gpu_cache.push_per_frame_blocks(&gpu_blocks);
             let key = BatchKey::new(
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
             let pic_metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
             let pic = &ctx.prim_store.cpu_pictures[pic_metadata.cpu_prim_index.0];
-            let batch = self.batch_list.get_suitable_batch(key, pic_metadata.screen_rect.as_ref().expect("bug"));
+            let batch = self.batch_list.get_suitable_batch(key, &pic_metadata.screen_rect.as_ref().expect("bug").clipped);
 
             let render_task_id = match pic.surface {
                 Some(PictureSurface::RenderTask(render_task_id)) => render_task_id,
                 Some(PictureSurface::TextureCache(..)) | None => panic!("BUG: unexpected surface in splitting"),
             };
             let source_task_address = render_tasks.get_task_address(render_task_id);
             let gpu_address = gpu_handle.as_int(gpu_cache);
 
@@ -523,42 +563,49 @@ impl AlphaBatcher {
         scroll_id: ClipScrollNodeIndex,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
-        pic_type: PictureType,
+        pic: &PicturePrimitive,
+        content_origin: ContentOrigin,
     ) {
         for i in 0 .. run.count {
             let prim_index = PrimitiveIndex(run.base_prim_index.0 + i);
 
             let metadata = &ctx.prim_store.cpu_metadata[prim_index.0];
 
             // Now that we walk the primitive runs in order to add
             // items to batches, we need to check if they are
             // visible here.
             // We currently only support culling on normal (Image)
             // picture types.
             // TODO(gw): Support culling on shadow image types.
-            if pic_type != PictureType::Image || metadata.screen_rect.is_some() {
+            let is_image = match pic.kind {
+                PictureKind::Image { .. } => true,
+                PictureKind::BoxShadow { .. } | PictureKind::TextShadow { .. } => false,
+            };
+
+            if !is_image || metadata.screen_rect.is_some() {
                 self.add_prim_to_batch(
                     metadata.clip_chain_rect_index,
                     scroll_id,
                     prim_index,
                     ctx,
                     gpu_cache,
                     render_tasks,
                     task_id,
                     task_address,
                     deferred_resolves,
                     splitter,
-                    pic_type,
+                    content_origin,
+                    pic,
                 );
             }
         }
     }
 
     fn get_buffer_kind(texture: SourceTexture) -> ImageBufferKind {
         match texture {
             SourceTexture::External(ext_image) => {
@@ -588,32 +635,52 @@ impl AlphaBatcher {
         prim_index: PrimitiveIndex,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
         task_id: RenderTaskId,
         task_address: RenderTaskAddress,
         deferred_resolves: &mut Vec<DeferredResolve>,
         splitter: &mut BspSplitter<f64, WorldPixel>,
-        pic_type: PictureType,
+        content_origin: ContentOrigin,
+        pic: &PicturePrimitive,
     ) {
         let z = prim_index.0 as i32;
         let prim_metadata = ctx.prim_store.get_metadata(prim_index);
         let scroll_node = &ctx.node_data[scroll_id.0 as usize];
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = scroll_node.transform.transform_kind();
-        let item_bounding_rect = &match prim_metadata.screen_rect {
-            Some(screen_rect) => screen_rect,
-            None => {
-                debug_assert_ne!(pic_type, PictureType::Image);
-                DeviceIntRect::zero()
+
+        let task_relative_bounding_rect = match content_origin {
+            ContentOrigin::Screen(point) => {
+                // translate by content-origin
+                let screen_rect = prim_metadata.screen_rect.expect("bug");
+                DeviceIntRect::new(
+                    DeviceIntPoint::new(
+                        screen_rect.unclipped.origin.x - point.x,
+                        screen_rect.unclipped.origin.y - point.y,
+                    ),
+                    screen_rect.unclipped.size,
+                )
+            }
+            ContentOrigin::Local(point) => {
+                // scale local rect by device pixel ratio
+                let content_rect = LayerRect::new(
+                    LayerPoint::new(
+                        prim_metadata.local_rect.origin.x - point.x,
+                        prim_metadata.local_rect.origin.y - point.y,
+                    ),
+                    prim_metadata.local_rect.size,
+                );
+                (content_rect * LayerToWorldScale::new(1.0) * ctx.device_pixel_scale).round().to_i32()
             }
         };
+
         let prim_cache_address = gpu_cache.get_address(&prim_metadata.gpu_location);
         let no_textures = BatchTextures::no_texture();
         let clip_task_address = prim_metadata
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
         let base_instance = SimplePrimitiveInstance::new(
             prim_cache_address,
             task_address,
@@ -631,17 +698,17 @@ impl AlphaBatcher {
                 let batch_key = brush.get_batch_key(blend_mode);
 
                 self.add_brush_to_batch(
                     brush,
                     prim_metadata,
                     batch_key,
                     clip_chain_rect_index,
                     clip_task_address,
-                    item_bounding_rect,
+                    &task_relative_bounding_rect,
                     prim_cache_address,
                     scroll_id,
                     task_address,
                     transform_kind,
                     z,
                     render_tasks,
                     0,
                     0,
@@ -660,17 +727,17 @@ impl AlphaBatcher {
                     transform_kind,
                     TransformBatchKind::BorderEdge,
                 );
                 let edge_key = BatchKey::new(edge_kind, blend_mode, no_textures);
 
                 // Work around borrow ck on borrowing batch_list twice.
                 {
                     let batch =
-                        self.batch_list.get_suitable_batch(corner_key, item_bounding_rect);
+                        self.batch_list.get_suitable_batch(corner_key, &task_relative_bounding_rect);
                     for (i, instance_kind) in border_cpu.corner_instances.iter().enumerate()
                     {
                         let sub_index = i as i32;
                         match *instance_kind {
                             BorderCornerInstance::None => {}
                             BorderCornerInstance::Single => {
                                 batch.push(base_instance.build(
                                     sub_index,
@@ -689,70 +756,71 @@ impl AlphaBatcher {
                                     BorderCornerSide::Second as i32,
                                     0,
                                 ));
                             }
                         }
                     }
                 }
 
-                let batch = self.batch_list.get_suitable_batch(edge_key, item_bounding_rect);
+                let batch = self.batch_list.get_suitable_batch(edge_key, &task_relative_bounding_rect);
                 for (border_segment, instance_kind) in border_cpu.edges.iter().enumerate() {
                     match *instance_kind {
                         BorderEdgeKind::None => {},
                         _ => {
                           batch.push(base_instance.build(border_segment as i32, 0, 0));
                         }
                     }
                 }
             }
             PrimitiveKind::Image => {
                 let image_cpu = &ctx.prim_store.cpu_images[prim_metadata.cpu_prim_index.0];
 
                 let cache_item = match image_cpu.source {
                     ImageSource::Default => {
                         resolve_image(
-                            image_cpu.key.image_key,
-                            image_cpu.key.image_rendering,
-                            image_cpu.key.tile_offset,
+                            image_cpu.key.request,
                             ctx.resource_cache,
                             gpu_cache,
                             deferred_resolves,
                         )
                     }
                     ImageSource::Cache { ref item, .. } => {
                         item.clone()
                     }
                 };
 
                 if cache_item.texture_id == SourceTexture::Invalid {
                     warn!("Warnings: skip a PrimitiveKind::Image");
-                    debug!("at {:?}.", item_bounding_rect);
+                    debug!("at {:?}.", task_relative_bounding_rect);
                     return;
                 }
 
                 let batch_kind = TransformBatchKind::Image(Self::get_buffer_kind(cache_item.texture_id));
                 let key = BatchKey::new(
                     BatchKind::Transformable(transform_kind, batch_kind),
                     blend_mode,
                     BatchTextures {
                         colors: [
                             cache_item.texture_id,
                             SourceTexture::Invalid,
                             SourceTexture::Invalid,
                         ],
                     },
                 );
-                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                 batch.push(base_instance.build(cache_item.uv_rect_handle.as_int(gpu_cache), 0, 0));
             }
             PrimitiveKind::TextRun => {
                 let text_cpu =
                     &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0];
-                let is_shadow = pic_type == PictureType::TextShadow;
+                let is_shadow = match pic.kind {
+                    PictureKind::TextShadow { .. } => true,
+                    PictureKind::BoxShadow { .. } | PictureKind::Image { .. } => false,
+                };
 
                 // TODO(gw): It probably makes sense to base this decision on the content
                 //           origin field in the future (once that's configurable).
                 let font_transform = if is_shadow {
                     None
                 } else {
                     Some(&scroll_node.transform)
                 };
@@ -770,17 +838,17 @@ impl AlphaBatcher {
                     font,
                     &text_cpu.glyph_keys,
                     glyph_fetch_buffer,
                     gpu_cache,
                     |texture_id, mut glyph_format, glyphs| {
                         debug_assert_ne!(texture_id, SourceTexture::Invalid);
 
                         // Ignore color and only sample alpha when shadowing.
-                        if text_cpu.is_shadow() {
+                        if text_cpu.shadow {
                             glyph_format = glyph_format.ignore_color();
                         }
 
                         let subpx_dir = match glyph_format {
                             GlyphFormat::Bitmap |
                             GlyphFormat::ColorBitmap => SubpixelDirection::None,
                             _ => text_cpu.font.subpx_dir.limit_by(text_cpu.font.render_mode),
                         };
@@ -806,27 +874,27 @@ impl AlphaBatcher {
                             let blend_mode = match glyph_format {
                                 GlyphFormat::Subpixel |
                                 GlyphFormat::TransformedSubpixel => {
                                     if text_cpu.font.bg_color.a != 0 {
                                         BlendMode::SubpixelWithBgColor
                                     } else if ctx.use_dual_source_blending {
                                         BlendMode::SubpixelDualSource
                                     } else {
-                                        BlendMode::SubpixelConstantTextColor(text_cpu.get_color())
+                                        BlendMode::SubpixelConstantTextColor(text_cpu.font.color.into())
                                     }
                                 }
                                 GlyphFormat::Alpha |
                                 GlyphFormat::TransformedAlpha |
                                 GlyphFormat::Bitmap |
                                 GlyphFormat::ColorBitmap => BlendMode::PremultipliedAlpha,
                             };
 
                             let key = BatchKey::new(kind, blend_mode, textures);
-                            batch_list.get_suitable_batch(key, item_bounding_rect)
+                            batch_list.get_suitable_batch(key, &task_relative_bounding_rect)
                         };
 
                         for glyph in glyphs {
                             batch.push(base_instance.build(
                                 glyph.index_in_text_run,
                                 glyph.uv_rect_address.as_int(),
                                 subpx_dir as u32 as i32,
                             ));
@@ -858,17 +926,17 @@ impl AlphaBatcher {
                                 );
 
                                 self.add_brush_to_batch(
                                     &picture.brush,
                                     prim_metadata,
                                     alpha_batch_key,
                                     clip_chain_rect_index,
                                     clip_task_address,
-                                    item_bounding_rect,
+                                    &task_relative_bounding_rect,
                                     prim_cache_address,
                                     scroll_id,
                                     task_address,
                                     transform_kind,
                                     z,
                                     render_tasks,
                                     cache_item.uv_rect_handle.as_int(gpu_cache),
                                     image_kind as i32,
@@ -882,17 +950,17 @@ impl AlphaBatcher {
 
                         match picture.kind {
                             PictureKind::TextShadow { .. } => {
                                 let kind = BatchKind::Brush(
                                     BrushBatchKind::Picture(
                                         BrushImageSourceKind::from_render_target_kind(picture.target_kind())),
                                 );
                                 let key = BatchKey::new(kind, blend_mode, textures);
-                                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
 
                                 let instance = BrushInstance {
                                     picture_address: task_address,
                                     prim_address: prim_cache_address,
                                     clip_chain_rect_index,
                                     scroll_id,
                                     clip_task_address,
                                     z,
@@ -946,17 +1014,18 @@ impl AlphaBatcher {
                                         match filter {
                                             FilterOp::Blur(..) => {
                                                 let src_task_address = render_tasks.get_task_address(source_id);
                                                 let key = BatchKey::new(
                                                     BatchKind::HardwareComposite,
                                                     BlendMode::PremultipliedAlpha,
                                                     BatchTextures::render_target_cache(),
                                                 );
-                                                let batch = self.batch_list.get_suitable_batch(key, &item_bounding_rect);
+                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                                let item_bounding_rect = prim_metadata.screen_rect.expect("bug!!").clipped;
                                                 let instance = CompositePrimitiveInstance::new(
                                                     task_address,
                                                     src_task_address,
                                                     RenderTaskAddress(0),
                                                     item_bounding_rect.origin.x,
                                                     item_bounding_rect.origin.y,
                                                     z,
                                                     item_bounding_rect.size.width,
@@ -980,17 +1049,17 @@ impl AlphaBatcher {
                                                     z,
                                                     segment_index: 0,
                                                     edge_flags: EdgeAaSegmentMask::empty(),
                                                     user_data0: cache_task_address.0 as i32,
                                                     user_data1: BrushImageKind::Simple as i32,
                                                 };
 
                                                 {
-                                                    let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+                                                    let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                                                     batch.push(PrimitiveInstance::from(instance));
                                                 }
 
                                                 let secondary_id = secondary_render_task_id.expect("no secondary!?");
                                                 let render_task = &render_tasks[secondary_id];
                                                 let secondary_task_address = render_tasks.get_task_address(secondary_id);
                                                 let render_pass_index = render_task.pass_index.expect("no render_pass_index!?");
                                                 let secondary_textures = BatchTextures {
@@ -1000,17 +1069,17 @@ impl AlphaBatcher {
                                                         SourceTexture::Invalid,
                                                     ],
                                                 };
                                                 let key = BatchKey::new(
                                                     BatchKind::HardwareComposite,
                                                     BlendMode::PremultipliedAlpha,
                                                     secondary_textures,
                                                 );
-                                                let batch = self.batch_list.get_suitable_batch(key, &item_bounding_rect);
+                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                                                 let content_rect = prim_metadata.local_rect.translate(&-offset);
                                                 let rect =
                                                     (content_rect * LayerToWorldScale::new(1.0) * ctx.device_pixel_scale).round()
                                                                                                                          .to_i32();
 
                                                 let instance = CompositePrimitiveInstance::new(
                                                     task_address,
                                                     secondary_task_address,
@@ -1042,17 +1111,17 @@ impl AlphaBatcher {
                                                     FilterOp::Sepia(amount) => (6, amount),
                                                     FilterOp::Brightness(amount) => (7, amount),
                                                     FilterOp::Opacity(_, amount) => (8, amount),
                                                     FilterOp::DropShadow(..) => unreachable!(),
                                                     FilterOp::ColorMatrix(_) => (10, 0.0),
                                                 };
 
                                                 let amount = (amount * 65535.0).round() as i32;
-                                                let batch = self.batch_list.get_suitable_batch(key, &item_bounding_rect);
+                                                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
 
                                                 let instance = CompositePrimitiveInstance::new(
                                                     task_address,
                                                     src_task_address,
                                                     RenderTaskAddress(0),
                                                     filter_mode,
                                                     amount,
                                                     z,
@@ -1071,17 +1140,17 @@ impl AlphaBatcher {
                                             BatchKind::Composite {
                                                 task_id,
                                                 source_id,
                                                 backdrop_id,
                                             },
                                             BlendMode::PremultipliedAlpha,
                                             BatchTextures::no_texture(),
                                         );
-                                        let batch = self.batch_list.get_suitable_batch(key, &item_bounding_rect);
+                                        let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                                         let backdrop_task_address = render_tasks.get_task_address(backdrop_id);
                                         let source_task_address = render_tasks.get_task_address(source_id);
 
                                         let instance = CompositePrimitiveInstance::new(
                                             task_address,
                                             source_task_address,
                                             backdrop_task_address,
                                             mode as u32 as i32,
@@ -1095,17 +1164,18 @@ impl AlphaBatcher {
                                     }
                                     PictureCompositeMode::Blit => {
                                         let src_task_address = render_tasks.get_task_address(source_id);
                                         let key = BatchKey::new(
                                             BatchKind::HardwareComposite,
                                             BlendMode::PremultipliedAlpha,
                                             BatchTextures::render_target_cache(),
                                         );
-                                        let batch = self.batch_list.get_suitable_batch(key, &item_bounding_rect);
+                                        let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
+                                        let item_bounding_rect = prim_metadata.screen_rect.expect("bug!!").clipped;
                                         let instance = CompositePrimitiveInstance::new(
                                             task_address,
                                             src_task_address,
                                             RenderTaskAddress(0),
                                             item_bounding_rect.origin.x,
                                             item_bounding_rect.origin.y,
                                             z,
                                             item_bounding_rect.size.width,
@@ -1135,63 +1205,65 @@ impl AlphaBatcher {
             PrimitiveKind::AlignedGradient => {
                 let gradient_cpu =
                     &ctx.prim_store.cpu_gradients[prim_metadata.cpu_prim_index.0];
                 let kind = BatchKind::Transformable(
                     transform_kind,
                     TransformBatchKind::AlignedGradient,
                 );
                 let key = BatchKey::new(kind, blend_mode, no_textures);
-                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                 for part_index in 0 .. (gradient_cpu.stops_count - 1) {
                     batch.push(base_instance.build(part_index as i32, 0, 0));
                 }
             }
             PrimitiveKind::AngleGradient => {
                 let kind = BatchKind::Transformable(
                     transform_kind,
                     TransformBatchKind::AngleGradient,
                 );
                 let key = BatchKey::new(kind, blend_mode, no_textures);
-                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                 batch.push(base_instance.build(0, 0, 0));
             }
             PrimitiveKind::RadialGradient => {
                 let kind = BatchKind::Transformable(
                     transform_kind,
                     TransformBatchKind::RadialGradient,
                 );
                 let key = BatchKey::new(kind, blend_mode, no_textures);
-                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
                 batch.push(base_instance.build(0, 0, 0));
             }
             PrimitiveKind::YuvImage => {
                 let mut textures = BatchTextures::no_texture();
                 let mut uv_rect_addresses = [0; 3];
                 let image_yuv_cpu =
                     &ctx.prim_store.cpu_yuv_images[prim_metadata.cpu_prim_index.0];
 
                 //yuv channel
                 let channel_count = image_yuv_cpu.format.get_plane_num();
                 debug_assert!(channel_count <= 3);
                 for channel in 0 .. channel_count {
                     let image_key = image_yuv_cpu.yuv_key[channel];
 
                     let cache_item = resolve_image(
-                        image_key,
-                        image_yuv_cpu.image_rendering,
-                        None,
+                        ImageRequest {
+                            key: image_key,
+                            rendering: image_yuv_cpu.image_rendering,
+                            tile: None,
+                        },
                         ctx.resource_cache,
                         gpu_cache,
                         deferred_resolves,
                     );
 
                     if cache_item.texture_id == SourceTexture::Invalid {
                         warn!("Warnings: skip a PrimitiveKind::YuvImage");
-                        debug!("at {:?}.", item_bounding_rect);
+                        debug!("at {:?}.", task_relative_bounding_rect);
                         return;
                     }
 
                     textures.colors[channel] = cache_item.texture_id;
                     uv_rect_addresses[channel] = cache_item.uv_rect_handle.as_int(gpu_cache);
                 }
 
                 // All yuv textures should be the same type.
@@ -1206,17 +1278,17 @@ impl AlphaBatcher {
                     transform_kind,
                     TransformBatchKind::YuvImage(
                         buffer_kind,
                         image_yuv_cpu.format,
                         image_yuv_cpu.color_space,
                     ),
                 );
                 let key = BatchKey::new(kind, blend_mode, textures);
-                let batch = self.batch_list.get_suitable_batch(key, item_bounding_rect);
+                let batch = self.batch_list.get_suitable_batch(key, &task_relative_bounding_rect);
 
                 batch.push(base_instance.build(
                     uv_rect_addresses[0],
                     uv_rect_addresses[1],
                     uv_rect_addresses[2],
                 ));
             }
         }
@@ -1224,17 +1296,17 @@ impl AlphaBatcher {
 
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
         prim_metadata: &PrimitiveMetadata,
         batch_key: BatchKey,
         clip_chain_rect_index: ClipChainRectIndex,
         clip_task_address: RenderTaskAddress,
-        item_bounding_rect: &DeviceIntRect,
+        task_relative_bounding_rect: &DeviceIntRect,
         prim_cache_address: GpuCacheAddress,
         scroll_id: ClipScrollNodeIndex,
         task_address: RenderTaskAddress,
         transform_kind: TransformedRectKind,
         z: i32,
         render_tasks: &RenderTaskTree,
         user_data0: i32,
         user_data1: i32,
@@ -1256,27 +1328,27 @@ impl AlphaBatcher {
             Some(ref segment_desc) => {
                 let alpha_batch_key = BatchKey {
                     blend_mode: BlendMode::PremultipliedAlpha,
                     ..batch_key
                 };
 
                 let alpha_batch = self.batch_list.alpha_batch_list.get_suitable_batch(
                     alpha_batch_key,
-                    item_bounding_rect
+                    task_relative_bounding_rect
                 );
 
                 let opaque_batch_key = BatchKey {
                     blend_mode: BlendMode::None,
                     ..batch_key
                 };
 
                 let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
                     opaque_batch_key,
-                    item_bounding_rect
+                    task_relative_bounding_rect
                 );
 
                 for (i, segment) in segment_desc.segments.iter().enumerate() {
                     let is_inner = segment.edge_flags.is_empty();
                     let needs_blending = !prim_metadata.opacity.is_opaque ||
                                          segment.clip_task_id.is_some() ||
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
 
@@ -1294,17 +1366,17 @@ impl AlphaBatcher {
                     if needs_blending {
                         alpha_batch.push(instance);
                     } else {
                         opaque_batch.push(instance);
                     }
                 }
             }
             None => {
-                let batch = self.batch_list.get_suitable_batch(batch_key, item_bounding_rect);
+                let batch = self.batch_list.get_suitable_batch(batch_key, task_relative_bounding_rect);
                 batch.push(PrimitiveInstance::from(base_instance));
             }
         }
     }
 }
 
 impl BrushPrimitive {
     fn get_batch_key(&self, blend_mode: BlendMode) -> BatchKey {
@@ -1380,24 +1452,22 @@ impl AlphaBatchHelpers for PrimitiveStor
             } else {
                 BlendMode::None
             },
         }
     }
 }
 
 pub fn resolve_image(
-    image_key: ImageKey,
-    image_rendering: ImageRendering,
-    tile_offset: Option<TileOffset>,
+    request: ImageRequest,
     resource_cache: &ResourceCache,
     gpu_cache: &mut GpuCache,
     deferred_resolves: &mut Vec<DeferredResolve>,
 ) -> CacheItem {
-    match resource_cache.get_image_properties(image_key) {
+    match resource_cache.get_image_properties(request.key) {
         Some(image_properties) => {
             // Check if an external image that needs to be resolved
             // by the render thread.
             match image_properties.external_image {
                 Some(external_image) => {
                     // This is an external texture - we will add it to
                     // the deferred resolves list to be patched by
                     // the render thread...
@@ -1418,17 +1488,17 @@ pub fn resolve_image(
                     deferred_resolves.push(DeferredResolve {
                         image_properties,
                         address: gpu_cache.get_address(&cache_handle),
                     });
 
                     cache_item
                 }
                 None => {
-                    if let Ok(cache_item) = resource_cache.get_cached_image(image_key, image_rendering, tile_offset) {
+                    if let Ok(cache_item) = resource_cache.get_cached_image(request) {
                         cache_item
                     } else {
                         // There is no usable texture entry for the image key. Just return an invalid texture here.
                         CacheItem::invalid()
                     }
                 }
             }
         }
@@ -1511,17 +1581,23 @@ impl ClipBatcher {
                 .get_opt(&work_item.clip_sources)
                 .expect("bug: clip handle should be valid");
 
             for &(ref source, ref handle) in &info.clips {
                 let gpu_address = gpu_cache.get_address(handle);
 
                 match *source {
                     ClipSource::Image(ref mask) => {
-                        if let Ok(cache_item) = resource_cache.get_cached_image(mask.image, ImageRendering::Auto, None) {
+                        if let Ok(cache_item) = resource_cache.get_cached_image(
+                            ImageRequest {
+                                key: mask.image,
+                                rendering: ImageRendering::Auto,
+                                tile: None,
+                            }
+                        ) {
                             self.images
                                 .entry(cache_item.texture_id)
                                 .or_insert(Vec::new())
                                 .push(ClipMaskInstance {
                                     clip_data_address: gpu_address,
                                     resource_address: gpu_cache.get_address(&cache_item.uv_rect_handle),
                                     ..instance
                                 });
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -5,17 +5,17 @@
 use api::{BorderRadius, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
 use api::{ImageRendering, LayerRect, LayerToWorldTransform, LayoutPoint, LayoutVector2D};
 use api::LocalClip;
 use border::{BorderCornerClipSource, ensure_no_corner_overlap};
 use ellipse::Ellipse;
 use freelist::{FreeList, FreeListHandle, WeakFreeListHandle};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use prim_store::{ClipData, ImageMaskData};
-use resource_cache::ResourceCache;
+use resource_cache::{ImageRequest, ResourceCache};
 use util::{MaxRect, MatrixHelpers, calculate_screen_bounding_rect, extract_inner_rect_safe};
 
 pub type ClipStore = FreeList<ClipSources>;
 pub type ClipSourcesHandle = FreeListHandle<ClipSources>;
 pub type ClipSourcesWeakHandle = WeakFreeListHandle<ClipSources>;
 
 #[derive(Clone, Debug)]
 pub struct ClipRegion {
@@ -225,17 +225,24 @@ impl ClipSources {
                     }
                     ClipSource::BorderCorner(ref mut source) => {
                         source.write(request);
                     }
                 }
             }
 
             if let ClipSource::Image(ref mask) = *source {
-                resource_cache.request_image(mask.image, ImageRendering::Auto, None, gpu_cache);
+                resource_cache.request_image(
+                    ImageRequest {
+                        key: mask.image,
+                        rendering: ImageRendering::Auto,
+                        tile: None,
+                    },
+                    gpu_cache,
+                );
             }
         }
     }
 
     /// Whether or not this ClipSources has any clips (does any clipping).
     pub fn has_clips(&self) -> bool {
         !self.clips.is_empty()
     }
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -38,29 +38,29 @@ impl CoordinateSystemId {
         CoordinateSystemId(id + 1)
     }
 
     pub fn advance(&mut self) {
         self.0 += 1;
     }
 }
 
-struct ClipChainDescriptor {
-    id: ClipChainId,
-    parent: Option<ClipChainId>,
-    clips: Vec<ClipId>,
+pub struct ClipChainDescriptor {
+    pub id: ClipChainId,
+    pub parent: Option<ClipChainId>,
+    pub clips: Vec<ClipId>,
 }
 
 pub struct ClipScrollTree {
     pub nodes: FastHashMap<ClipId, 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.
-    clip_chains_descriptors: Vec<ClipChainDescriptor>,
+    pub clip_chains_descriptors: Vec<ClipChainDescriptor>,
 
     /// A HashMap of built ClipChains that are described by `clip_chains_descriptors`.
     pub clip_chains: FastHashMap<ClipChainId, ClipChain>,
 
     pub pending_scroll_offsets: FastHashMap<ScrollNodeIdType, (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.
--- a/gfx/webrender/src/debug_server.rs
+++ b/gfx/webrender/src/debug_server.rs
@@ -48,18 +48,16 @@ impl ws::Handler for Server {
             ws::Message::Text(string) => {
                 let cmd = match string.as_str() {
                     "enable_profiler" => DebugCommand::EnableProfiler(true),
                     "disable_profiler" => DebugCommand::EnableProfiler(false),
                     "enable_texture_cache_debug" => DebugCommand::EnableTextureCacheDebug(true),
                     "disable_texture_cache_debug" => DebugCommand::EnableTextureCacheDebug(false),
                     "enable_render_target_debug" => DebugCommand::EnableRenderTargetDebug(true),
                     "disable_render_target_debug" => DebugCommand::EnableRenderTargetDebug(false),
-                    "enable_alpha_rects_debug" => DebugCommand::EnableAlphaRectsDebug(true),
-                    "disable_alpha_rects_debug" => DebugCommand::EnableAlphaRectsDebug(false),
                     "enable_gpu_time_queries" => DebugCommand::EnableGpuTimeQueries(true),
                     "disable_gpu_time_queries" => DebugCommand::EnableGpuTimeQueries(false),
                     "enable_gpu_sample_queries" => DebugCommand::EnableGpuSampleQueries(true),
                     "disable_gpu_sample_queries" => DebugCommand::EnableGpuSampleQueries(false),
                     "fetch_passes" => DebugCommand::FetchPasses,
                     "fetch_screenshot" => DebugCommand::FetchScreenshot,
                     "fetch_documents" => DebugCommand::FetchDocuments,
                     "fetch_clip_scroll_tree" => DebugCommand::FetchClipScrollTree,
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -1938,16 +1938,29 @@ impl Device {
     pub fn disable_depth_write(&self) {
         self.gl.depth_mask(false);
     }
 
     pub fn disable_stencil(&self) {
         self.gl.disable(gl::STENCIL_TEST);
     }
 
+    pub fn set_scissor_rect(&self, rect: DeviceIntRect) {
+        self.gl.scissor(
+            rect.origin.x,
+            rect.origin.y,
+            rect.size.width,
+            rect.size.height,
+        );
+    }
+
+    pub fn enable_scissor(&self) {
+        self.gl.enable(gl::SCISSOR_TEST);
+    }
+
     pub fn disable_scissor(&self) {
         self.gl.disable(gl::SCISSOR_TEST);
     }
 
     pub fn set_blend(&self, enable: bool) {
         if enable {
             self.gl.enable(gl::BLEND);
         } else {
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -17,16 +17,17 @@ use euclid::rect;
 use frame_builder::{FrameBuilder, FrameBuilderConfig, ScrollbarInfo};
 use gpu_cache::GpuCache;
 use hit_test::HitTester;
 use internal_types::{FastHashMap, FastHashSet, RenderedDocument};
 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,
@@ -1096,42 +1097,51 @@ impl FrameContext {
             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(&self, frame: Frame) -> RenderedDocument {
+    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(self.pipeline_epoch_map.clone(), nodes_bouncing_back, frame)
+        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,
+		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,
@@ -1140,11 +1150,11 @@ impl FrameContext {
             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))
+        (hit_tester, self.make_rendered_document(frame, removed_pipelines))
     }
 }
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.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::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayList, ClipAndScrollInfo};
-use api::{ClipId, ColorF, ColorU, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
+use api::{ClipId, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
 use api::{DeviceUintPoint, DeviceUintRect, DeviceUintSize, DocumentLayer, Epoch, ExtendMode};
 use api::{ExternalScrollId, FontRenderMode, GlyphInstance, GlyphOptions, GradientStop, ImageKey};
 use api::{ImageRendering, ItemRange, LayerPoint, LayerPrimitiveInfo, LayerRect, LayerSize};
 use api::{LayerTransform, LayerVector2D, LayoutTransform, LayoutVector2D, LineOrientation};
 use api::{LineStyle, LocalClip, PipelineId, PremultipliedColorF, PropertyBinding, RepeatMode};
 use api::{ScrollSensitivity, Shadow, TexelRect, TileOffset, TransformStyle, WorldPoint};
 use api::{WorldToLayerTransform, YuvColorSpace, YuvData};
 use app_units::Au;
@@ -25,18 +25,18 @@ use hit_test::{HitTester, HitTestingItem
 use internal_types::{FastHashMap, FastHashSet, RenderPassIndex};
 use picture::{ContentOrigin, PictureCompositeMode, PictureKind, PicturePrimitive, PictureSurface};
 use prim_store::{BrushKind, BrushPrimitive, ImageCacheKey, YuvImagePrimitiveCpu};
 use prim_store::{GradientPrimitiveCpu, ImagePrimitiveCpu, ImageSource, PrimitiveKind};
 use prim_store::{PrimitiveContainer, PrimitiveIndex};
 use prim_store::{PrimitiveStore, RadialGradientPrimitiveCpu};
 use prim_store::{BrushSegmentDescriptor, PrimitiveRun, TextRunPrimitiveCpu};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
-use render_task::{ClearMode, ClipChain, RenderTask, RenderTaskId, RenderTaskTree};
-use resource_cache::ResourceCache;
+use render_task::{ClearMode, ClipChain, RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree};
+use resource_cache::{ImageRequest, 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, pack_as_float, RectHelpers, recycle_vec};
 
 #[derive(Debug)]
 pub struct ScrollbarInfo(pub ClipId, pub LayerRect);
@@ -1402,17 +1402,17 @@ impl FrameBuilder {
         );
         let prim = TextRunPrimitiveCpu {
             font: prim_font,
             glyph_range,
             glyph_count,
             glyph_gpu_blocks: Vec::new(),
             glyph_keys: Vec::new(),
             offset: run_offset,
-            shadow_color: ColorU::new(0, 0, 0, 0),
+            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
@@ -1420,17 +1420,18 @@ impl FrameBuilder {
         // 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.shadow_color = color.into();
+                    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 {
@@ -1513,19 +1514,21 @@ impl FrameBuilder {
 
         let prim_cpu = ImagePrimitiveCpu {
             tile_spacing,
             alpha_type,
             stretch_size,
             current_epoch: Epoch::invalid(),
             source: ImageSource::Default,
             key: ImageCacheKey {
-                image_key,
-                image_rendering,
-                tile_offset,
+                request: ImageRequest {
+                    key: image_key,
+                    rendering: image_rendering,
+                    tile: tile_offset,
+                },
                 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,
@@ -1641,17 +1644,17 @@ impl FrameBuilder {
             &frame_context,
             &mut frame_state,
         );
 
         let pic = &mut self.prim_store.cpu_pictures[0];
         pic.runs = pic_context.prim_runs;
 
         let root_render_task = RenderTask::new_picture(
-            None,
+            RenderTaskLocation::Fixed(frame_context.screen_rect),
             PrimitiveIndex(0),
             RenderTargetKind::Color,
             ContentOrigin::Screen(DeviceIntPoint::zero()),
             PremultipliedColorF::TRANSPARENT,
             ClearMode::Transparent,
             pic_state.tasks,
             PictureType::Image,
         );
--- a/gfx/webrender/src/hit_test.rs
+++ b/gfx/webrender/src/hit_test.rs
@@ -9,34 +9,40 @@ use clip::{ClipSource, ClipStore, Contai
 use clip_scroll_node::{ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use internal_types::FastHashMap;
 
 /// 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 {
-    /// This node's parent in the ClipScrollTree. This is used to ensure that we can
-    /// travel up the tree of nodes.
-    parent: Option<ClipId>,
-
     /// 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: LayerToWorldTransform,
 
     /// World viewport transform for content transformed by this node.
     world_viewport_transform: LayerToWorldTransform,
 
     /// Origin of the viewport of the node, used to calculate node-relative positions.
     node_origin: LayerPoint,
 }
 
+/// 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.
+struct HitTestClipChainDescriptor {
+    parent: Option<ClipId>,
+    clips: Vec<ClipId>,
+}
+
 #[derive(Clone)]
 pub struct HitTestingItem {
     rect: LayerRect,
     clip: LocalClip,
     tag: ItemTag,
 }
 
 impl HitTestingItem {
@@ -67,122 +73,137 @@ impl HitTestRegion {
                 !rounded_rectangle_contains_point(point, &rect, &radii),
         }
     }
 }
 
 pub struct HitTester {
     runs: Vec<HitTestingRun>,
     nodes: FastHashMap<ClipId, HitTestClipScrollNode>,
+    clip_chains: FastHashMap<ClipId, HitTestClipChainDescriptor>,
 }
 
 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(),
+            clip_chains: 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,
         clip_store: &ClipStore
     ) {
         self.nodes.clear();
 
         for (id, node) in &clip_scroll_tree.nodes {
             self.nodes.insert(*id, HitTestClipScrollNode {
-                parent: node.parent,
                 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,
             });
+
+            self.clip_chains.insert(*id, HitTestClipChainDescriptor {
+                parent: node.parent,
+                clips: vec![*id],
+            });
+        }
+
+        for descriptor in &clip_scroll_tree.clip_chains_descriptors {
+            self.clip_chains.insert(
+                ClipId::ClipChain(descriptor.id),
+                HitTestClipChainDescriptor {
+                    parent: descriptor.parent.map(|id| ClipId::ClipChain(id)),
+                    clips: descriptor.clips.clone(),
+                }
+            );
         }
     }
 
-    pub fn is_point_clipped_in_for_node(
+    fn is_point_clipped_in_for_clip_chain(
+        &self,
+        point: WorldPoint,
+        chain_id: &ClipId,
+        test: &mut HitTest
+    ) -> bool {
+        if let Some(result) = test.clip_chain_cache.get(&chain_id) {
+            return *result;
+        }
+
+        let descriptor = &self.clip_chains[&chain_id];
+        let parent_clipped_in = match descriptor.parent {
+            None => true,
+            Some(ref parent) => self.is_point_clipped_in_for_clip_chain(point, parent, test),
+        };
+
+        if !parent_clipped_in {
+            test.clip_chain_cache.insert(*chain_id, false);
+            return false;
+        }
+
+        for clip_node in &descriptor.clips {
+            if !self.is_point_clipped_in_for_node(point, clip_node, test) {
+                test.clip_chain_cache.insert(*chain_id, false);
+                return false;
+            }
+        }
+
+        test.clip_chain_cache.insert(*chain_id, true);
+        true
+    }
+
+    fn is_point_clipped_in_for_node(
         &self,
         point: WorldPoint,
         node_id: &ClipId,
-        cache: &mut FastHashMap<ClipId, Option<LayerPoint>>,
+        test: &mut HitTest
     ) -> bool {
-        if let Some(point) = cache.get(node_id) {
+        if let Some(point) = test.node_cache.get(node_id) {
             return point.is_some();
         }
 
         let node = self.nodes.get(node_id).unwrap();
-        let parent_clipped_in = match node.parent {
-            None => true, // This is the root node.
-            Some(ref parent_id) => {
-                self.is_point_clipped_in_for_node(point, parent_id, cache)
-            }
-        };
-
-        if !parent_clipped_in {
-            cache.insert(*node_id, None);
-            return false;
-        }
-
         let transform = node.world_viewport_transform;
         let transformed_point = match transform.inverse() {
             Some(inverted) => inverted.transform_point2d(&point),
             None => {
-                cache.insert(*node_id, None);
+                test.node_cache.insert(*node_id, None);
                 return false;
             }
         };
 
         let point_in_layer = transformed_point - node.node_origin.to_vector();
         for region in &node.regions {
             if !region.contains(&transformed_point) {
-                cache.insert(*node_id, None);
+                test.node_cache.insert(*node_id, None);
                 return false;
             }
         }
 
-        cache.insert(*node_id, Some(point_in_layer));
+        test.node_cache.insert(*node_id, Some(point_in_layer));
         true
     }
 
-    pub fn make_node_relative_point_absolute(
-        &self,
-        pipeline_id: Option<PipelineId>,
-        point: &LayerPoint
-    ) -> WorldPoint {
-        pipeline_id.and_then(|id| self.nodes.get(&ClipId::root_reference_frame(id)))
-                   .map(|node| node.world_viewport_transform.transform_point2d(point))
-                   .unwrap_or_else(|| WorldPoint::new(point.x, point.y))
-
-    }
+    pub fn hit_test(&self, mut test: HitTest) -> HitTestResult {
+        let point = test.get_absolute_point(self);
 
-    pub fn hit_test(
-        &self,
-        pipeline_id: Option<PipelineId>,
-        point: WorldPoint,
-        flags: HitTestFlags
-    ) -> HitTestResult {
-        let point = if flags.contains(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT) {
-            self.make_node_relative_point_absolute(pipeline_id, &LayerPoint::new(point.x, point.y))
-        } else {
-            point
-        };
-
-        let mut node_cache = FastHashMap::default();
         let mut result = HitTestResult::default();
         for &HitTestingRun(ref items, ref clip_and_scroll) in self.runs.iter().rev() {
             let scroll_node = &self.nodes[&clip_and_scroll.scroll_node_id];
-            match (pipeline_id, clip_and_scroll.scroll_node_id.pipeline_id()) {
+            match (test.pipeline_id, clip_and_scroll.scroll_node_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,
@@ -191,34 +212,43 @@ impl HitTester {
             let mut clipped_in = false;
             for item in items.iter().rev() {
                 if !item.rect.contains(&point_in_layer) || !item.clip.contains(&point_in_layer) {
                     continue;
                 }
 
                 let clip_id = &clip_and_scroll.clip_node_id();
                 if !clipped_in {
-                    clipped_in = self.is_point_clipped_in_for_node(point, clip_id, &mut node_cache);
+                    clipped_in = self.is_point_clipped_in_for_clip_chain(point, clip_id, &mut test);
                     if !clipped_in {
                         break;
                     }
                 }
 
-                let point_in_viewport = node_cache
-                    .get(&ClipId::root_reference_frame(clip_id.pipeline_id()))
-                    .expect("Hittest target's root reference frame not hit.")
-                    .expect("Hittest target's root reference frame not hit.");
+                // 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(clip_id.pipeline_id());
+                if !self.is_point_clipped_in_for_node(point, &root_reference_frame, &mut test) {
+                    continue;
+                }
+                let point_in_viewport = match test.node_cache[&root_reference_frame] {
+                    Some(point) => point,
+                    None => continue,
+                };
 
                 result.items.push(HitTestItem {
                     pipeline: clip_and_scroll.clip_node_id().pipeline_id(),
                     tag: item.tag,
                     point_in_viewport,
                     point_relative_to_item: point_in_layer - item.rect.origin.to_vector(),
                 });
-                if !flags.contains(HitTestFlags::FIND_ALL) {
+                if !test.flags.contains(HitTestFlags::FIND_ALL) {
                     return result;
                 }
             }
         }
 
         result.items.dedup();
         result
     }
@@ -239,8 +269,43 @@ fn get_regions_for_clip_scroll_node(
             ClipSource::RoundedRectangle(ref rect, ref radii, ref mode) =>
                 HitTestRegion::RoundedRectangle(*rect, *radii, *mode),
             ClipSource::Image(ref mask) => HitTestRegion::Rectangle(mask.rect),
             ClipSource::BorderCorner(_) =>
                 unreachable!("Didn't expect to hit test against BorderCorner"),
         }
     }).collect()
 }
+
+pub struct HitTest {
+    pipeline_id: Option<PipelineId>,
+    point: WorldPoint,
+    flags: HitTestFlags,
+    node_cache: FastHashMap<ClipId, Option<LayerPoint>>,
+    clip_chain_cache: FastHashMap<ClipId, bool>,
+}
+
+impl HitTest {
+    pub fn new(
+        pipeline_id: Option<PipelineId>,
+        point: WorldPoint,
+        flags: HitTestFlags,
+    ) -> HitTest {
+        HitTest {
+            pipeline_id,
+            point,
+            flags,
+            node_cache: FastHashMap::default(),
+            clip_chain_cache: FastHashMap::default(),
+        }
+    }
+
+    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))
+    }
+}
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.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::{ClipId, DeviceUintRect, DocumentId, Epoch};
+use api::{ClipId, DeviceUintRect, DocumentId};
 use api::{ExternalImageData, ExternalImageId};
-use api::{ImageFormat, PipelineId};
+use api::ImageFormat;
 use api::DebugCommand;
 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;
 use std::hash::BuildHasherDefault;
 use std::path::PathBuf;
@@ -126,34 +127,36 @@ impl TextureUpdateList {
     #[inline]
     pub fn push(&mut self, update: TextureUpdate) {
         self.updates.push(update);
     }
 }
 
 /// Mostly wraps a tiling::Frame, adding a bit of extra information.
 pub struct RenderedDocument {
-    /// The last rendered epoch for each pipeline present in the frame.
+    /// 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.
-    pub pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
+    /// - 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 frame: tiling::Frame,
 }
 
 impl RenderedDocument {
     pub fn new(
-        pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
+        pipeline_info: PipelineInfo,
         layers_bouncing_back: FastHashSet<ClipId>,
         frame: tiling::Frame,
     ) -> Self {
         RenderedDocument {
-            pipeline_epoch_map,
+            pipeline_info,
             layers_bouncing_back,
             frame,
         }
     }
 }
 
 pub enum DebugOutput {
     FetchDocuments(String),
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -7,17 +7,17 @@ use api::{DeviceIntPoint, DeviceIntRect,
 use api::{BoxShadowClipMode, LayerPoint, LayerRect, LayerVector2D, Shadow};
 use api::{ClipId, PremultipliedColorF};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowCacheKey};
 use frame_builder::{FrameContext, FrameState, PictureState};
 use gpu_cache::GpuDataRequest;
 use gpu_types::{BrushImageKind, PictureType};
 use prim_store::{BrushKind, BrushPrimitive, PrimitiveIndex, PrimitiveRun, PrimitiveRunLocalRect};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheKey};
-use render_task::{RenderTaskCacheKeyKind, RenderTaskId};
+use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use resource_cache::CacheItem;
 use scene::{FilterOpHelpers, SceneProperties};
 use tiling::RenderTargetKind;
 
 /*
  A picture represents a dynamically rendered image. It consists of:
 
  * A number of primitives that are drawn onto the picture.
@@ -313,24 +313,16 @@ impl PicturePrimitive {
                     }
                 };
 
                 prim_local_rect
             }
         }
     }
 
-    pub fn picture_type(&self) -> PictureType {
-        match self.kind {
-            PictureKind::Image { .. } => PictureType::Image,
-            PictureKind::BoxShadow { .. } => PictureType::BoxShadow,
-            PictureKind::TextShadow { .. } => PictureType::TextShadow,
-        }
-    }
-
     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,
@@ -343,17 +335,17 @@ impl PicturePrimitive {
                 ref mut secondary_render_task_id,
                 composite_mode,
                 ..
             } => {
                 let content_origin = ContentOrigin::Screen(prim_screen_rect.origin);
                 match composite_mode {
                     Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => {
                         let picture_task = RenderTask::new_picture(
-                            Some(prim_screen_rect.size),
+                            RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
@@ -372,17 +364,17 @@ impl PicturePrimitive {
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         pic_state.tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::Filter(FilterOp::DropShadow(offset, blur_radius, color))) => {
                         let rect = (prim_local_rect.translate(&-offset) * content_scale).round().to_i32();
                         let picture_task = RenderTask::new_picture(
-                            Some(rect.size),
+                            RenderTaskLocation::Dynamic(None, rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             ContentOrigin::Screen(rect.origin),
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
@@ -402,17 +394,17 @@ impl PicturePrimitive {
                         *secondary_render_task_id = Some(picture_task_id);
 
                         let render_task_id = frame_state.render_tasks.add(blur_render_task);
                         pic_state.tasks.push(render_task_id);
                         self.surface = Some(PictureSurface::RenderTask(render_task_id));
                     }
                     Some(PictureCompositeMode::MixBlend(..)) => {
                         let picture_task = RenderTask::new_picture(
-                            Some(prim_screen_rect.size),
+                            RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
@@ -432,34 +424,34 @@ impl PicturePrimitive {
                         // current render task. This most commonly occurs
                         // when opacity == 1.0, but can also occur on other
                         // filters and be a significant performance win.
                         if filter.is_noop() {
                             pic_state.tasks.extend(pic_state_for_children.tasks);
                             self.surface = None;
                         } else {
                             let picture_task = RenderTask::new_picture(
-                                Some(prim_screen_rect.size),
+                                RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
                                 prim_index,
                                 RenderTargetKind::Color,
                                 content_origin,
                                 PremultipliedColorF::TRANSPARENT,
                                 ClearMode::Transparent,
                                 pic_state_for_children.tasks,
                                 PictureType::Image,
                             );
 
                             let render_task_id = frame_state.render_tasks.add(picture_task);
                             pic_state.tasks.push(render_task_id);
                             self.surface = Some(PictureSurface::RenderTask(render_task_id));
                         }
                     }
                     Some(PictureCompositeMode::Blit) => {
                         let picture_task = RenderTask::new_picture(
-                            Some(prim_screen_rect.size),
+                            RenderTaskLocation::Dynamic(None, prim_screen_rect.size),
                             prim_index,
                             RenderTargetKind::Color,
                             content_origin,
                             PremultipliedColorF::TRANSPARENT,
                             ClearMode::Transparent,
                             pic_state_for_children.tasks,
                             PictureType::Image,
                         );
@@ -489,17 +481,17 @@ impl PicturePrimitive {
 
                 // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
                 // "the image that would be generated by applying to the shadow a
                 // Gaussian blur with a standard deviation equal to half the blur radius."
                 let device_radius = (blur_radius * frame_context.device_pixel_scale.0).round();
                 let blur_std_deviation = device_radius * 0.5;
 
                 let picture_task = RenderTask::new_picture(
-                    Some(cache_size),
+                    RenderTaskLocation::Dynamic(None, cache_size),
                     prim_index,
                     RenderTargetKind::Color,
                     ContentOrigin::Local(content_rect.origin),
                     color.premultiplied(),
                     ClearMode::Transparent,
                     Vec::new(),
                     PictureType::TextShadow,
                 );
@@ -549,17 +541,17 @@ impl PicturePrimitive {
                                 ClearMode::One
                             }
                             BoxShadowClipMode::Inset => {
                                 ClearMode::Zero
                             }
                         };
 
                         let picture_task = RenderTask::new_picture(
-                            Some(cache_size),
+                            RenderTaskLocation::Dynamic(None, cache_size),
                             prim_index,
                             RenderTargetKind::Alpha,
                             ContentOrigin::Local(content_rect.origin),
                             color.premultiplied(),
                             ClearMode::Zero,
                             Vec::new(),
                             PictureType::BoxShadow,
                         );
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -248,17 +248,17 @@ fn new_ct_font_with_variations(cg_font: 
                 vals.push((name, CFNumber::from(val)));
             }
         }
         if vals.is_empty() {
             return ct_font;
         }
         let vals_dict = CFDictionary::from_CFType_pairs(&vals);
         let cg_var_font = cg_font.create_copy_from_variations(&vals_dict).unwrap();
-        core_text::font::new_from_CGFont(&cg_var_font, size)
+        core_text::font::new_from_CGFont_with_variations(&cg_var_font, size, &vals_dict)
     }
 }
 
 fn is_bitmap_font(ct_font: &CTFont) -> bool {
     let traits = ct_font.symbolic_traits();
     (traits & kCTFontColorGlyphsTrait) != 0
 }
 
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,33 +1,33 @@
 /* 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, ClipAndScrollInfo, ClipMode};
-use api::{ColorF, ColorU, DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch};
+use api::{ColorF, DeviceIntRect, DeviceIntSize, DevicePixelScale, Epoch};
 use api::{ComplexClipRegion, ExtendMode, FontRenderMode};
 use api::{GlyphInstance, GlyphKey, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag};
 use api::{LayerPoint, LayerRect, LayerSize, LayerToWorldTransform, LayerVector2D, LineOrientation};
-use api::{LineStyle, PremultipliedColorF, TileOffset};
+use api::{LineStyle, PremultipliedColorF};
 use api::{WorldToLayerTransform, YuvColorSpace, YuvFormat};
 use border::{BorderCornerInstance, BorderEdgeKind};
 use clip_scroll_tree::{CoordinateSystemId};
 use clip_scroll_node::ClipScrollNode;
 use clip::{ClipSource, ClipSourcesHandle};
 use frame_builder::{FrameContext, FrameState, PictureContext, PictureState, 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, ClipChain, ClipChainNode, ClipChainNodeIter, ClipChainNodeRef, ClipWorkItem};
 use render_task::{RenderTask, RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
-use resource_cache::{CacheItem, ImageProperties, ResourceCache};
+use resource_cache::{CacheItem, ImageProperties, ImageRequest, ResourceCache};
 use segment::SegmentBuilder;
 use std::{mem, usize};
 use std::rc::Rc;
 use util::{MatrixHelpers, calculate_screen_bounding_rect, pack_as_float};
 use util::recycle_vec;
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 128.0 * 128.0;
@@ -131,16 +131,22 @@ impl GpuCacheAddress {
     pub fn as_int(&self) -> i32 {
         // TODO(gw): Temporarily encode GPU Cache addresses as a single int.
         //           In the future, we can change the PrimitiveInstance struct
         //           to use 2x u16 for the vertex attribute instead of an i32.
         self.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + self.u as i32
     }
 }
 
+#[derive(Debug, Copy, Clone)]
+pub struct ScreenRect {
+    pub clipped: DeviceIntRect,
+    pub unclipped: DeviceIntRect,
+}
+
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
 pub struct PrimitiveMetadata {
     pub opacity: PrimitiveOpacity,
     pub clip_sources: ClipSourcesHandle,
     pub prim_kind: PrimitiveKind,
     pub cpu_prim_index: SpecificPrimitiveIndex,
     pub gpu_location: GpuCacheHandle,
@@ -148,17 +154,17 @@ pub struct PrimitiveMetadata {
 
     // TODO(gw): In the future, we should just pull these
     //           directly from the DL item, instead of
     //           storing them here.
     pub local_rect: LayerRect,
     pub local_clip_rect: LayerRect,
     pub clip_chain_rect_index: ClipChainRectIndex,
     pub is_backface_visible: bool,
-    pub screen_rect: Option<DeviceIntRect>,
+    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 {
@@ -320,22 +326,17 @@ impl BrushPrimitive {
 }
 
 // Key that identifies a unique (partial) image that is being
 // stored in the render task cache.
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ImageCacheKey {
-    // TODO(gw): Consider introducing a struct that collectively
-    //           identifies an image in the resource cache
-    //           uniquely. We pass this around to a few places.
-    pub image_key: ImageKey,
-    pub image_rendering: ImageRendering,
-    pub tile_offset: Option<TileOffset>,
+    pub request: ImageRequest,
     pub texel_rect: Option<DeviceIntRect>,
 }
 
 // Where to find the texture data for an image primitive.
 #[derive(Debug)]
 pub enum ImageSource {
     // A normal image - just reference the texture cache.
     Default,
@@ -644,17 +645,17 @@ impl RadialGradientPrimitiveCpu {
 #[derive(Debug, Clone)]
 pub struct TextRunPrimitiveCpu {
     pub font: FontInstance,
     pub offset: LayerVector2D,
     pub glyph_range: ItemRange<GlyphInstance>,
     pub glyph_count: usize,
     pub glyph_keys: Vec<GlyphKey>,
     pub glyph_gpu_blocks: Vec<GpuBlockData>,
-    pub shadow_color: ColorU,
+    pub shadow: bool,
 }
 
 impl TextRunPrimitiveCpu {
     pub fn get_font(
         &self,
         device_pixel_scale: DevicePixelScale,
         transform: Option<&LayerToWorldTransform>,
     ) -> FontInstance {
@@ -665,28 +666,16 @@ impl TextRunPrimitiveCpu {
                 font.render_mode = font.render_mode.limit_by(FontRenderMode::Alpha);
             } else {
                 font.transform = FontTransform::from(transform).quantize();
             }
         }
         font
     }
 
-    pub fn is_shadow(&self) -> bool {
-        self.shadow_color.a != 0
-    }
-
-    pub fn get_color(&self) -> ColorF {
-        ColorF::from(if self.is_shadow() {
-            self.shadow_color
-        } else {
-            self.font.color
-        })
-    }
-
     fn prepare_for_render(
         &mut self,
         resource_cache: &mut ResourceCache,
         device_pixel_scale: DevicePixelScale,
         transform: Option<&LayerToWorldTransform>,
         display_list: &BuiltDisplayList,
         gpu_cache: &mut GpuCache,
     ) {
@@ -726,17 +715,17 @@ impl TextRunPrimitiveCpu {
                 self.glyph_gpu_blocks.push(gpu_block.into());
             }
         }
 
         resource_cache.request_glyphs(font, &self.glyph_keys, gpu_cache);
     }
 
     fn write_gpu_blocks(&self, request: &mut GpuDataRequest) {
-        request.push(self.get_color().premultiplied());
+        request.push(ColorF::from(self.font.color).premultiplied());
         // this is the only case where we need to provide plain color to GPU
         let bg_color = ColorF::from(self.font.bg_color);
         request.push([bg_color.r, bg_color.g, bg_color.b, 1.0]);
         request.push([
             self.offset.x,
             self.offset.y,
             0.0,
             0.0,
@@ -1153,17 +1142,19 @@ impl PrimitiveStore {
     ) {
         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,
-                        metadata.screen_rect.as_ref().expect("bug: trying to draw an off-screen picture!?"),
+                        &metadata.screen_rect
+                            .expect("bug: trying to draw an off-screen picture!?")
+                            .clipped,
                         &metadata.local_rect,
                         pic_state_for_children,
                         pic_state,
                         frame_context,
                         frame_state,
                     );
             }
             PrimitiveKind::TextRun => {
@@ -1181,17 +1172,17 @@ impl PrimitiveStore {
                     pic_context.display_list,
                     frame_state.gpu_cache,
                 );
             }
             PrimitiveKind::Image => {
                 let image_cpu = &mut self.cpu_images[metadata.cpu_prim_index.0];
                 let image_properties = frame_state
                     .resource_cache
-                    .get_image_properties(image_cpu.key.image_key);
+                    .get_image_properties(image_cpu.key.request.key);
 
                 // TODO(gw): Add image.rs and move this code out to a separate
                 //           source file as it gets more complicated, and we
                 //           start pre-rendering images for other reasons.
 
                 if let Some(image_properties) = image_properties {
                     // See if this image has been updated since we last hit this code path.
                     // If so, we need to (at least) update the opacity, and also rebuild
@@ -1216,84 +1207,98 @@ impl PrimitiveStore {
                             }
                             None => {
                                 // Simple image - just use a normal texture cache entry.
                                 ImageSource::Default
                             }
                         };
                     }
 
-                    // TODO(gw): Don't actually need this in cached source mode if
-                    //           the cache item is still valid...
-                    frame_state.resource_cache.request_image(
-                        image_cpu.key.image_key,
-                        image_cpu.key.image_rendering,
-                        image_cpu.key.tile_offset,
-                        frame_state.gpu_cache,
-                    );
+                    // Set if we need to request the source image from the cache this frame.
+                    let mut request_source_image = false;
 
                     // Every frame, for cached items, we need to request the render
                     // task cache item. The closure will be invoked on the first
                     // time through, and any time the render task output has been
                     // evicted from the texture cache.
-                    if let ImageSource::Cache { size, ref mut item } = image_cpu.source {
-                        let key = image_cpu.key;
+                    match image_cpu.source {
+                        ImageSource::Cache { size, ref mut item } => {
+                            let key = image_cpu.key;
 
-                        // Request a pre-rendered image task.
-                        *item = frame_state.resource_cache.request_render_task(
-                            RenderTaskCacheKey {
-                                size,
-                                kind: RenderTaskCacheKeyKind::Image(key),
-                            },
-                            frame_state.gpu_cache,
-                            frame_state.render_tasks,
-                            |render_tasks| {
-                                // Create a task to blit from the texture cache to
-                                // a normal transient render task surface. This will
-                                // copy only the sub-rect, if specified.
-                                let cache_to_target_task = RenderTask::new_blit(
+                            // Request a pre-rendered image task.
+                            *item = frame_state.resource_cache.request_render_task(
+                                RenderTaskCacheKey {
                                     size,
-                                    BlitSource::Image {
-                                        key,
-                                    },
-                                );
-                                let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
+                                    kind: RenderTaskCacheKeyKind::Image(key),
+                                },
+                                frame_state.gpu_cache,
+                                frame_state.render_tasks,
+                                |render_tasks| {
+                                    // We need to render the image cache this frame,
+                                    // so will need access to the source texture.
+                                    request_source_image = true;
+
+                                    // Create a task to blit from the texture cache to
+                                    // a normal transient render task surface. This will
+                                    // copy only the sub-rect, if specified.
+                                    let cache_to_target_task = RenderTask::new_blit(
+                                        size,
+                                        BlitSource::Image {
+                                            key,
+                                        },
+                                    );
+                                    let cache_to_target_task_id = render_tasks.add(cache_to_target_task);
 
-                                // Create a task to blit the rect from the child render
-                                // task above back into the right spot in the persistent
-                                // render target cache.
-                                let target_to_cache_task = RenderTask::new_blit(
-                                    size,
-                                    BlitSource::RenderTask {
-                                        task_id: cache_to_target_task_id,
-                                    },
-                                );
-                                let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
+                                    // Create a task to blit the rect from the child render
+                                    // task above back into the right spot in the persistent
+                                    // render target cache.
+                                    let target_to_cache_task = RenderTask::new_blit(
+                                        size,
+                                        BlitSource::RenderTask {
+                                            task_id: cache_to_target_task_id,
+                                        },
+                                    );
+                                    let target_to_cache_task_id = render_tasks.add(target_to_cache_task);
+
+                                    // Hook this into the render task tree at the right spot.
+                                    pic_state.tasks.push(target_to_cache_task_id);
 
-                                // Hook this into the render task tree at the right spot.
-                                pic_state.tasks.push(target_to_cache_task_id);
+                                    // Pass the image opacity, so that the cached render task
+                                    // item inherits the same opacity properties.
+                                    (target_to_cache_task_id, [0.0; 3], image_properties.descriptor.is_opaque)
+                                }
+                            );
+                        }
+                        ImageSource::Default => {
+                            // Normal images just reference the source texture each frame.
+                            request_source_image = true;
+                        }
+                    }
 
-                                // Pass the image opacity, so that the cached render task
-                                // item inherits the same opacity properties.
-                                (target_to_cache_task_id, [0.0; 3], image_properties.descriptor.is_opaque)
-                            }
+                    // Request source image from the texture cache, if required.
+                    if request_source_image {
+                        frame_state.resource_cache.request_image(
+                            image_cpu.key.request,
+                            frame_state.gpu_cache,
                         );
                     }
                 }
             }
             PrimitiveKind::YuvImage => {
                 let image_cpu = &mut self.cpu_yuv_images[metadata.cpu_prim_index.0];
 
                 let channel_num = image_cpu.format.get_plane_num();
                 debug_assert!(channel_num <= 3);
                 for channel in 0 .. channel_num {
                     frame_state.resource_cache.request_image(
-                        image_cpu.yuv_key[channel],
-                        image_cpu.image_rendering,
-                        None,
+                        ImageRequest {
+                            key: image_cpu.yuv_key[channel],
+                            rendering: image_cpu.image_rendering,
+                            tile: None,
+                        },
                         frame_state.gpu_cache,
                     );
                 }
             }
             PrimitiveKind::Brush |
             PrimitiveKind::AlignedGradient |
             PrimitiveKind::AngleGradient |
             PrimitiveKind::RadialGradient => {}
@@ -1814,17 +1819,24 @@ impl PrimitiveStore {
                 &local_rect,
                 frame_context.device_pixel_scale,
             );
 
             let clip_bounds = match prim_run_context.clip_chain {
                 Some(ref node) => node.combined_outer_screen_rect,
                 None => frame_context.screen_rect,
             };
-            metadata.screen_rect = screen_bounding_rect.intersection(&clip_bounds);
+            metadata.screen_rect = screen_bounding_rect
+                .intersection(&clip_bounds)
+                .map(|clipped| {
+                    ScreenRect {
+                        clipped,
+                        unclipped: screen_bounding_rect,
+                    }
+                });
 
             if metadata.screen_rect.is_none() && pic_context.perform_culling {
                 return None;
             }
 
             metadata.clip_chain_rect_index = prim_run_context.clip_chain_rect_index;
 
             (local_rect, screen_bounding_rect)
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -14,17 +14,17 @@ use api::channel::{PayloadSender, Payloa
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame::FrameContext;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
-use hit_test::HitTester;
+use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use resource_cache::PlainResources;
@@ -32,20 +32,20 @@ use scene::Scene;
 #[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::u32;
 use time::precise_time_ns;
 
-
 #[cfg_attr(feature = "capture", derive(Clone, Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 struct DocumentView {
     window_size: DeviceUintSize,
     inner_rect: DeviceUintRect,
     layer: DocumentLayer,
     pan: DeviceIntPoint,
     device_pixel_ratio: f32,
@@ -67,16 +67,22 @@ struct Document {
     scene: Scene,
     view: DocumentView,
     frame_ctx: FrameContext,
     // 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,
@@ -99,16 +105,17 @@ impl Document {
     ) -> Self {
         let render_on_scroll = if enable_render_on_scroll {
             Some(false)
         } else {
             None
         };
         Document {
             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,
@@ -128,16 +135,17 @@ impl Document {
             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(..));
         self.frame_builder = Some(frame_builder);
     }
 
     fn render(
         &mut self,
         resource_cache: &mut ResourceCache,
         gpu_cache: &mut GpuCache,
         resource_profile: &mut ResourceProfileCounters,
@@ -150,16 +158,17 @@ impl Document {
             gpu_cache,
             &self.scene.pipelines,
             accumulated_scale_factor,
             self.view.layer,
             pan,
             &mut resource_profile.texture_cache,
             &mut resource_profile.gpu_cache,
             &self.scene.properties,
+            replace(&mut self.removed_pipelines, Vec::new()),
         );
 
         self.hit_tester = Some(hit_tester);
 
         rendered_document
     }
 }
 
@@ -414,17 +423,19 @@ impl RenderBackend {
                     render: should_render,
                     composite: should_render,
                     ..DocumentOps::nop()
                 }
             }
             DocumentMsg::HitTest(pipeline_id, point, flags, tx) => {
 
                 let result = match doc.hit_tester {
-                    Some(ref hit_tester) => hit_tester.hit_test(pipeline_id, point, flags),
+                    Some(ref hit_tester) => {
+                        hit_tester.hit_test(HitTest::new(pipeline_id, point, flags))
+                    }
                     None => HitTestResult { items: Vec::new() },
                 };
 
                 tx.send(result).unwrap();
                 DocumentOps::nop()
             }
             DocumentMsg::ScrollNodeWithId(origin, id, clamp) => {
                 profile_scope!("ScrollNodeWithScrollId");
@@ -971,24 +982,25 @@ impl RenderBackend {
             let mut doc = Document {
                 scene,
                 view,
                 frame_ctx: FrameContext::new(self.frame_config.clone()),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 render_on_scroll: None,
                 render_on_hittest: false,
+                removed_pipelines: Vec::new(),
                 hit_tester: None,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let render_doc = match CaptureConfig::deserialize::<Frame, _>(root, frame_name) {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
-                    doc.frame_ctx.make_rendered_document(frame)
+                    doc.frame_ctx.make_rendered_document(frame, Vec::new())
                 }
                 None => {
                     doc.build_scene(&mut self.resource_cache);
                     doc.render(
                         &mut self.resource_cache,
                         &mut self.gpu_cache,
                         &mut profile_counters.resources,
                     )
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -162,17 +162,17 @@ impl RenderTaskTree {
         let task = &self.tasks[id.0 as usize];
 
         for child in &task.children {
             self.assign_to_passes(*child, pass_index - 1, passes);
         }
 
         // Sanity check - can be relaxed if needed
         match task.location {
-            RenderTaskLocation::Fixed => {
+            RenderTaskLocation::Fixed(..) => {
                 debug_assert!(pass_index == passes.len() - 1);
             }
             RenderTaskLocation::Dynamic(..) |
             RenderTaskLocation::TextureCache(..) => {
                 debug_assert!(pass_index < passes.len() - 1);
             }
         }
 
@@ -213,17 +213,17 @@ impl ops::IndexMut<RenderTaskId> for Ren
         &mut self.tasks[id.0 as usize]
     }
 }
 
 #[derive(Debug)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub enum RenderTaskLocation {
-    Fixed,
+    Fixed(DeviceIntRect),
     Dynamic(Option<(DeviceIntPoint, RenderTargetIndex)>, DeviceIntSize),
     TextureCache(SourceTexture, i32, DeviceIntRect),
 }
 
 #[derive(Debug, Clone)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ClipWorkItem {
@@ -331,30 +331,25 @@ pub struct RenderTask {
     pub children: Vec<RenderTaskId>,
     pub kind: RenderTaskKind,
     pub clear_mode: ClearMode,
     pub pass_index: Option<RenderPassIndex>,
 }
 
 impl RenderTask {
     pub fn new_picture(
-        size: Option<DeviceIntSize>,
+        location: RenderTaskLocation,
         prim_index: PrimitiveIndex,
         target_kind: RenderTargetKind,
         content_origin: ContentOrigin,
         color: PremultipliedColorF,
         clear_mode: ClearMode,
         children: Vec<RenderTaskId>,
         pic_type: PictureType,
     ) -> Self {
-        let location = match size {
-            Some(size) => RenderTaskLocation::Dynamic(None, size),
-            None => RenderTaskLocation::Fixed,
-        };
-
         RenderTask {
             children,
             location,
             kind: RenderTaskKind::Picture(PictureTask {
                 prim_index,
                 target_kind,
                 content_origin,
                 color,
@@ -596,26 +591,26 @@ impl RenderTask {
                 data2[2],
                 data2[3],
             ]
         }
     }
 
     pub fn get_dynamic_size(&self) -> DeviceIntSize {
         match self.location {
-            RenderTaskLocation::Fixed => DeviceIntSize::zero(),
+            RenderTaskLocation::Fixed(..) => DeviceIntSize::zero(),
             RenderTaskLocation::Dynamic(_, size) => size,
             RenderTaskLocation::TextureCache(_, _, rect) => rect.size,
         }
     }
 
     pub fn get_target_rect(&self) -> (DeviceIntRect, RenderTargetIndex) {
         match self.location {
-            RenderTaskLocation::Fixed => {
-                (DeviceIntRect::zero(), RenderTargetIndex(0))
+            RenderTaskLocation::Fixed(rect) => {
+                (rect, RenderTargetIndex(0))
             }
             // Previously, we only added render tasks after the entire
             // primitive chain was determined visible. This meant that
             // we could assert any render task in the list was also
             // allocated (assigned to passes). Now, we add render
             // tasks earlier, and the picture they belong to may be
             // culled out later, so we can't assert that the task
             // has been allocated.
@@ -819,17 +814,17 @@ impl RenderTaskCache {
             // Select the right texture page to allocate from.
             let image_format = match render_task.target_kind() {
                 RenderTargetKind::Color => ImageFormat::BGRA8,
                 RenderTargetKind::Alpha => ImageFormat::R8,
             };
 
             // Find out what size to alloc in the texture cache.
             let size = match render_task.location {
-                RenderTaskLocation::Fixed |
+                RenderTaskLocation::Fixed(..) |
                 RenderTaskLocation::TextureCache(..) => {
                     panic!("BUG: dynamic task was expected");
                 }
                 RenderTaskLocation::Dynamic(_, size) => size,
             };
 
             // TODO(gw): Support color tasks in the texture cache,
             //           and perhaps consider if we can determine
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -257,22 +257,21 @@ impl BatchKind {
 }
 
 bitflags! {
     #[derive(Default)]
     pub struct DebugFlags: u32 {
         const PROFILER_DBG      = 1 << 0;
         const RENDER_TARGET_DBG = 1 << 1;
         const TEXTURE_CACHE_DBG = 1 << 2;
-        const ALPHA_PRIM_DBG    = 1 << 3;
-        const GPU_TIME_QUERIES  = 1 << 4;
-        const GPU_SAMPLE_QUERIES= 1 << 5;
-        const DISABLE_BATCHING  = 1 << 6;
-        const EPOCHS            = 1 << 7;
-        const COMPACT_PROFILER  = 1 << 8;
+        const GPU_TIME_QUERIES  = 1 << 3;
+        const GPU_SAMPLE_QUERIES= 1 << 4;
+        const DISABLE_BATCHING  = 1 << 5;
+        const EPOCHS            = 1 << 6;
+        const COMPACT_PROFILER  = 1 << 7;
     }
 }
 
 fn flag_changed(before: DebugFlags, after: DebugFlags, select: DebugFlags) -> Option<bool> {
     if before & select != after & select {
         Some(after.contains(select))
     } else {
         None
@@ -1646,17 +1645,17 @@ pub struct Renderer {
 
     node_data_texture: VertexDataTexture,
     local_clip_rects_texture: VertexDataTexture,
     render_task_texture: VertexDataTexture,
     gpu_cache_texture: CacheTexture,
 
     gpu_cache_frame_id: FrameId,
 
-    pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
+    pipeline_info: PipelineInfo,
 
     // Manages and resolves source textures IDs to real texture IDs.
     texture_resolver: SourceTextureResolver,
 
     // A PBO used to do asynchronous texture cache uploads.
     texture_cache_upload_pbo: PBO,
 
     dither_matrix_texture: Option<Texture>,
@@ -2301,17 +2300,17 @@ impl Renderer {
             last_time: 0,
             gpu_profile,
             prim_vao,
             blur_vao,
             clip_vao,
             node_data_texture,
             local_clip_rects_texture,
             render_task_texture,
-            pipeline_epoch_map: FastHashMap::default(),
+            pipeline_info: PipelineInfo::default(),
             dither_matrix_texture,
             external_image_handler: None,
             output_image_handler: None,
             output_targets: FastHashMap::default(),
             cpu_profiles: VecDeque::new(),
             gpu_profiles: VecDeque::new(),
             gpu_cache_texture,
             gpu_cache_frame_id: FrameId::new(0),
@@ -2348,23 +2347,21 @@ impl Renderer {
         color_space: YuvColorSpace,
     ) -> usize {
         ((buffer_kind as usize) * YUV_FORMATS.len() + (format as usize)) * YUV_COLOR_SPACES.len() +
             (color_space as usize)
     }
 
     /// Returns the Epoch of the current frame in a pipeline.
     pub fn current_epoch(&self, pipeline_id: PipelineId) -> Option<Epoch> {
-        self.pipeline_epoch_map.get(&pipeline_id).cloned()
+        self.pipeline_info.epochs.get(&pipeline_id).cloned()
     }
 
-    /// Returns a HashMap containing the pipeline ids that have been received by the renderer and
-    /// their respective epochs since the last time the method was called.
-    pub fn flush_rendered_epochs(&mut self) -> FastHashMap<PipelineId, Epoch> {
-        mem::replace(&mut self.pipeline_epoch_map, FastHashMap::default())
+    pub fn flush_pipeline_info(&mut self) -> PipelineInfo {
+        mem::replace(&mut self.pipeline_info, PipelineInfo::default())
     }
 
     // update the program cache with new binaries, e.g. when some of the lazy loaded
     // shader programs got activated in the mean time
     pub fn update_program_cache(&mut self, cached_programs: Rc<ProgramCache>) {
         self.device.update_program_cache(cached_programs);
     }
 
@@ -2373,25 +2370,26 @@ impl Renderer {
     /// Should be called before `render()`, as texture cache updates are done here.
     pub fn update(&mut self) {
         profile_scope!("update");
         // Pull any pending results and return the most recent.
         while let Ok(msg) = self.result_rx.try_recv() {
             match msg {
                 ResultMsg::PublishDocument(
                     document_id,
-                    doc,
+                    mut doc,
                     texture_update_list,
                     profile_counters,
                 ) => {
                     // Update the list of available epochs for use during reftests.
                     // This is a workaround for https://github.com/servo/servo/issues/13149.
-                    for (pipeline_id, epoch) in &doc.pipeline_epoch_map {
-                        self.pipeline_epoch_map.insert(*pipeline_id, *epoch);
+                    for (pipeline_id, epoch) in &doc.pipeline_info.epochs {
+                        self.pipeline_info.epochs.insert(*pipeline_id, *epoch);
                     }
+                    self.pipeline_info.removed_pipelines.extend(doc.pipeline_info.removed_pipelines.drain(..));
 
                     // Add a new document to the active set, expressed as a `Vec` in order
                     // to re-order based on `DocumentLayer` during rendering.
                     match self.active_documents.iter().position(|&(id, _)| id == document_id) {
                         Some(pos) => {
                             // If the document we are replacing must be drawn
                             // (in order to update the texture cache), issue
                             // a render just to off-screen targets.
@@ -2562,45 +2560,45 @@ impl Renderer {
             "Vertical Blur",
             target.vertical_blurs.len(),
         );
         debug_target.add(
             debug_server::BatchKind::Cache,
             "Horizontal Blur",
             target.horizontal_blurs.len(),
         );
-        for (_, batch) in &target.alpha_batcher.text_run_cache_prims {
-            debug_target.add(
-                debug_server::BatchKind::Cache,
-                "Text Shadow",
-                batch.len(),
-            );
-        }
-
-        for batch in target
-            .alpha_batcher
-            .batch_list
-            .opaque_batch_list
-            .batches
-            .iter()
-            .rev()
-        {
-            debug_target.add(
-                debug_server::BatchKind::Opaque,
-                batch.key.kind.debug_name(),
-                batch.instances.len(),
-            );
-        }
-
-        for batch in &target.alpha_batcher.batch_list.alpha_batch_list.batches {
-            debug_target.add(
-                debug_server::BatchKind::Alpha,
-                batch.key.kind.debug_name(),
-                batch.instances.len(),
-            );
+
+        for alpha_batch_container in &target.alpha_batch_containers {
+            for (_, batch) in &alpha_batch_container.text_run_cache_prims {
+                debug_target.add(
+                    debug_server::BatchKind::Cache,
+                    "Text Shadow",
+                    batch.len(),
+                );
+            }
+
+            for batch in alpha_batch_container
+                .opaque_batches
+                .iter()
+                .rev() {
+                debug_target.add(
+                    debug_server::BatchKind::Opaque,
+                    batch.key.kind.debug_name(),
+                    batch.instances.len(),
+                );
+            }
+
+            for batch in &alpha_batch_container
+                .alpha_batches {
+                debug_target.add(
+                    debug_server::BatchKind::Alpha,
+                    batch.key.kind.debug_name(),
+                    batch.instances.len(),
+                );
+            }
         }
 
         debug_target
     }
 
     #[cfg(feature = "debugger")]
     fn debug_texture_cache_target(target: &TextureCacheRenderTarget) -> debug_server::Target {
         let mut debug_target = debug_server::Target::new("Texture Cache");
@@ -2670,19 +2668,16 @@ impl Renderer {
                 self.set_debug_flag(DebugFlags::PROFILER_DBG, enable);
             }
             DebugCommand::EnableTextureCacheDebug(enable) => {
                 self.set_debug_flag(DebugFlags::TEXTURE_CACHE_DBG, enable);
             }
             DebugCommand::EnableRenderTargetDebug(enable) => {
                 self.set_debug_flag(DebugFlags::RENDER_TARGET_DBG, enable);
             }
-            DebugCommand::EnableAlphaRectsDebug(enable) => {
-                self.set_debug_flag(DebugFlags::ALPHA_PRIM_DBG, enable);
-            }
             DebugCommand::EnableGpuTimeQueries(enable) => {
                 self.set_debug_flag(DebugFlags::GPU_TIME_QUERIES, enable);
             }
             DebugCommand::EnableGpuSampleQueries(enable) => {
                 self.set_debug_flag(DebugFlags::GPU_SAMPLE_QUERIES, enable);
             }
             DebugCommand::EnableDualSourceBlending(_) => {
                 panic!("Should be handled by render backend");
@@ -3162,16 +3157,17 @@ impl Renderer {
         &mut self,
         key: &BatchKey,
         instances: &[PrimitiveInstance],
         projection: &Transform3D<f32>,
         render_tasks: &RenderTaskTree,
         render_target: Option<(&Texture, i32)>,
         framebuffer_size: DeviceUintSize,
         stats: &mut RendererStats,
+        scissor_rect: Option<DeviceIntRect>,
     ) {
         match key.kind {
             BatchKind::Composite { .. } => {
                 self.ps_composite.bind(&mut self.device, projection, 0, &mut self.renderer_errors);
             }
             BatchKind::HardwareComposite => {
                 self.ps_hw_composite
                     .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
@@ -3298,16 +3294,20 @@ impl Renderer {
                         &mut self.renderer_errors,
                     );
                 }
             },
         };
 
         // Handle special case readback for composites.
         if let BatchKind::Composite { task_id, source_id, backdrop_id } = key.kind {
+            if scissor_rect.is_some() {
+                self.device.disable_scissor();
+            }
+
             // composites can't be grouped together because
             // they may overlap and affect each other.
             debug_assert_eq!(instances.len(), 1);
             let cache_texture = self.texture_resolver
                 .resolve(&SourceTexture::CacheRGBA8)
                 .unwrap();
 
             // Before submitting the composite batch, do the
@@ -3356,16 +3356,20 @@ impl Renderer {
             }
 
             self.device.bind_read_target(render_target);
             self.device.blit_render_target(src, dest);
 
             // Restore draw target to current pass render target + layer.
             // Note: leaving the viewport unchanged, it's not a part of FBO state
             self.device.bind_draw_target(render_target, None);
+
+            if scissor_rect.is_some() {
+                self.device.enable_scissor();
+            }
         }
 
         let _timer = self.gpu_profile.start_timer(key.kind.gpu_sampler_tag());
         self.draw_instanced_batch(
             instances,
             VertexArrayKind::Primitive,
             &key.textures,
             stats
@@ -3543,92 +3547,92 @@ impl Renderer {
         self.handle_scaling(render_tasks, &target.scalings, SourceTexture::CacheRGBA8);
 
         // Draw any textrun caches for this target. For now, this
         // is only used to cache text runs that are to be blurred
         // for shadow support. In the future it may be worth
         // considering using this for (some) other text runs, since
         // it removes the overhead of submitting many small glyphs
         // to multiple tiles in the normal text run case.
-        if !target.alpha_batcher.text_run_cache_prims.is_empty() {
-            self.device.set_blend(true);
-            self.device.set_blend_mode_premultiplied_alpha();
-
-            let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_TEXT_RUN);
-            self.cs_text_run
-                .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
-            for (texture_id, instances) in &target.alpha_batcher.text_run_cache_prims {
-                self.draw_instanced_batch(
-                    instances,
-                    VertexArrayKind::Primitive,
-                    &BatchTextures::color(*texture_id),
-                    stats,
-                );
+        for alpha_batch_container in &target.alpha_batch_containers {
+            if !alpha_batch_container.text_run_cache_prims.is_empty() {
+                self.device.set_blend(true);
+                self.device.set_blend_mode_premultiplied_alpha();
+
+                let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_TEXT_RUN);
+                self.cs_text_run
+                    .bind(&mut self.device, projection, 0, &mut self.renderer_errors);
+                for (texture_id, instances) in &alpha_batch_container.text_run_cache_prims {
+                    self.draw_instanced_batch(
+                        instances,
+                        VertexArrayKind::Primitive,
+                        &BatchTextures::color(*texture_id),
+                        stats,
+                    );
+                }
             }
         }
 
         //TODO: record the pixel count for cached primitives
 
-        if !target.alpha_batcher.is_empty() {
-            let _gl = self.gpu_profile.start_marker("alpha batches");
+        if target.needs_depth() {
+            let _gl = self.gpu_profile.start_marker("opaque batches");
+            let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
             self.device.set_blend(false);
-            let mut prev_blend_mode = BlendMode::None;
-
-            if target.needs_depth() {
-                let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE);
-
-                //Note: depth equality is needed for split planes
-                self.device.set_depth_func(DepthFunction::LessEqual);
-                self.device.enable_depth();
-                self.device.enable_depth_write();
+            //Note: depth equality is needed for split planes
+            self.device.set_depth_func(DepthFunction::LessEqual);
+            self.device.enable_depth();
+            self.device.enable_depth_write();
+
+            for alpha_batch_container in &target.alpha_batch_containers {
+                if let Some(target_rect) = alpha_batch_container.target_rect {
+                    self.device.enable_scissor();
+                    self.device.set_scissor_rect(target_rect);
+                }
 
                 // Draw opaque batches front-to-back for maximum
                 // z-buffer efficiency!
-                for batch in target
-                    .alpha_batcher
-                    .batch_list
-                    .opaque_batch_list
-                    .batches
+                for batch in alpha_batch_container
+                    .opaque_batches
                     .iter()
                     .rev()
                 {
                     self.submit_batch(
                         &batch.key,
                         &batch.instances,
                         &projection,
                         render_tasks,
                         render_target,
                         target_size,
                         stats,
+                        alpha_batch_container.target_rect,
                     );
                 }
 
-                self.device.disable_depth_write();
-                self.gpu_profile.finish_sampler(opaque_sampler);
+                if alpha_batch_container.target_rect.is_some() {
+                    self.device.disable_scissor();
+                }
             }
 
-            let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
-
-            for batch in &target.alpha_batcher.batch_list.alpha_batch_list.batches {
-                if self.debug_flags.contains(DebugFlags::ALPHA_PRIM_DBG) {
-                    let color = match batch.key.blend_mode {
-                        BlendMode::None => debug_colors::BLACK,
-                        BlendMode::Alpha |
-                        BlendMode::PremultipliedAlpha => debug_colors::GREY,
-                        BlendMode::PremultipliedDestOut => debug_colors::SALMON,
-                        BlendMode::SubpixelConstantTextColor(..) => debug_colors::GREEN,
-                        BlendMode::SubpixelVariableTextColor => debug_colors::RED,
-                        BlendMode::SubpixelWithBgColor => debug_colors::BLUE,
-                        BlendMode::SubpixelDualSource => debug_colors::YELLOW,
-                    }.into();
-                    for item_rect in &batch.item_rects {
-                        self.debug.add_rect(item_rect, color);
-                    }
-                }
-
+            self.device.disable_depth_write();
+            self.gpu_profile.finish_sampler(opaque_sampler);
+        }
+
+        let _gl = self.gpu_profile.start_marker("alpha batches");
+        let transparent_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT);
+        self.device.set_blend(false);
+        let mut prev_blend_mode = BlendMode::None;
+
+        for alpha_batch_container in &target.alpha_batch_containers {
+            if let Some(target_rect) = alpha_batch_container.target_rect {
+                self.device.enable_scissor();
+                self.device.set_scissor_rect(target_rect);
+            }
+
+            for batch in &alpha_batch_container.alpha_batches {
                 match batch.key.kind {
                     BatchKind::Transformable(transform_kind, TransformBatchKind::TextRun(glyph_format)) => {
                         // Text run batches are handled by this special case branch.
                         // In the case of subpixel text, we draw it as a two pass
                         // effect, to ensure we can apply clip masks correctly.
                         // In the future, there are several optimizations available:
                         // 1) Use dual source blending where available (almost all recent hardware).
                         // 2) Use frame buffer fetch where available (most modern hardware).
@@ -3832,26 +3836,31 @@ impl Renderer {
                         self.submit_batch(
                             &batch.key,
                             &batch.instances,
                             &projection,
                             render_tasks,
                             render_target,
                             target_size,
                             stats,
+                            alpha_batch_container.target_rect,
                         );
                     }
                 }
             }
 
-            self.device.disable_depth();
-            self.device.set_blend(false);
-            self.gpu_profile.finish_sampler(transparent_sampler);
+            if alpha_batch_container.target_rect.is_some() {
+                self.device.disable_scissor();
+            }
         }
 
+        self.device.disable_depth();
+        self.device.set_blend(false);
+        self.gpu_profile.finish_sampler(transparent_sampler);
+
         // For any registered image outputs on this render target,
         // get the texture from caller and blit it.
         for output in &target.outputs {
             let handler = self.output_image_handler
                 .as_mut()
                 .expect("Found output image, but no handler set!");
             if let Some((texture_id, output_size)) = handler.lock(output.pipeline_id) {
                 let fbo_id = match self.output_targets.entry(texture_id) {
@@ -4620,17 +4629,17 @@ impl Renderer {
             return;
         }
 
         let dy = self.debug.line_height();
         let x0: f32 = 30.0;
         let y0: f32 = 30.0;
         let mut y = y0;
         let mut text_width = 0.0;
-        for (pipeline, epoch) in  &self.pipeline_epoch_map {
+        for (pipeline, epoch) in  &self.pipeline_info.epochs {
             y += dy;
             let w = self.debug.add_text(
                 x0, y,
                 &format!("{:?}: {:?}", pipeline, epoch),
                 ColorU::new(255, 255, 0, 255),
             ).size.width;
             text_width = f32::max(text_width, w);
         }
@@ -4935,16 +4944,22 @@ impl OutputImageHandler for () {
     fn lock(&mut self, _: PipelineId) -> Option<(u32, DeviceIntSize)> {
         None
     }
     fn unlock(&mut self, _: PipelineId) {
         unreachable!()
     }
 }
 
+#[derive(Default)]
+pub struct PipelineInfo {
+    pub epochs: FastHashMap<PipelineId, Epoch>,
+    pub removed_pipelines: Vec<PipelineId>,
+}
+
 impl Renderer {
     #[cfg(feature = "capture")]
     fn save_texture(
         texture: &Texture, name: &str, root: &PathBuf, device: &mut Device
     ) -> PlainTexture {
         use std::fs;
         use std::io::Write;
 
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -204,20 +204,20 @@ where
         }
     }
 }
 
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
-struct ImageRequest {
-    key: ImageKey,
-    rendering: ImageRendering,
-    tile: Option<TileOffset>,
+pub struct ImageRequest {
+    pub key: ImageKey,
+    pub rendering: ImageRendering,
+    pub tile: Option<TileOffset>,
 }
 
 impl Into<BlobImageRequest> for ImageRequest {
     fn into(self) -> BlobImageRequest {
         BlobImageRequest {
             key: self.key,
             tile: self.tile,
         }
@@ -533,33 +533,26 @@ impl ResourceCache {
                 warn!("Delete the non-exist key");
                 debug!("key={:?}", image_key);
             }
         }
     }
 
     pub fn request_image(
         &mut self,
-        key: ImageKey,
-        rendering: ImageRendering,
-        tile: Option<TileOffset>,
+        request: ImageRequest,
         gpu_cache: &mut GpuCache,
     ) {
         debug_assert_eq!(self.state, State::AddResources);
-        let request = ImageRequest {
-            key,
-            rendering,
-            tile,
-        };
 
-        let template = match self.resources.image_templates.get(key) {
+        let template = match self.resources.image_templates.get(request.key) {
             Some(template) => template,
             None => {
                 warn!("ERROR: Trying to render deleted / non-existent key");
-                debug!("key={:?}", key);
+                debug!("key={:?}", request.key);
                 return
             }
         };
 
         // Images that don't use the texture cache can early out.
         if !template.data.uses_texture_cache() {
             return;
         }
@@ -729,36 +722,29 @@ impl ResourceCache {
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.glyph_rasterizer.get_glyph_index(font_key, ch)
     }
 
     #[inline]
     pub fn get_cached_image(
         &self,
-        image_key: ImageKey,
-        image_rendering: ImageRendering,
-        tile: Option<TileOffset>,
+        request: ImageRequest,
     ) -> Result<CacheItem, ()> {
         debug_assert_eq!(self.state, State::QueryResources);
-        let key = ImageRequest {
-            key: image_key,
-            rendering: image_rendering,
-            tile,
-        };
 
         // TODO(Jerry): add a debug option to visualize the corresponding area for
         // the Err() case of CacheItem.
-        match *self.cached_images.get(&key) {
-          Ok(ref image_info) => {
-              Ok(self.texture_cache.get(&image_info.texture_cache_handle))
-          }
-          Err(_) => {
-              Err(())
-          }
+        match *self.cached_images.get(&request) {
+            Ok(ref image_info) => {
+                Ok(self.texture_cache.get(&image_info.texture_cache_handle))
+            }
+            Err(_) => {
+                Err(())
+            }
         }
     }
 
     pub fn get_image_properties(&self, image_key: ImageKey) -> Option<ImageProperties> {
         let image_template = &self.resources.image_templates.get(image_key);
 
         image_template.map(|image_template| {
             let external_image = match image_template.data {
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -96,24 +96,26 @@ pub struct ScenePipeline {
 }
 
 /// A complete representation of the layout bundling visible pipelines together.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct Scene {
     pub root_pipeline_id: Option<PipelineId>,
     pub pipelines: FastHashMap<PipelineId, ScenePipeline>,
+    pub removed_pipelines: Vec<PipelineId>,
     pub properties: SceneProperties,
 }
 
 impl Scene {
     pub fn new() -> Self {
         Scene {
             root_pipeline_id: None,
             pipelines: FastHashMap::default(),
+            removed_pipelines: Vec::new(),
             properties: SceneProperties::new(),
         }
     }
 
     pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) {
         self.root_pipeline_id = Some(pipeline_id);
     }
 
@@ -138,16 +140,17 @@ impl Scene {
         self.pipelines.insert(pipeline_id, new_pipeline);
     }
 
     pub fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
         if self.root_pipeline_id == Some(pipeline_id) {
             self.root_pipeline_id = None;
         }
         self.pipelines.remove(&pipeline_id);
+        self.removed_pipelines.push(pipeline_id);
     }
 
     pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
         if let Some(pipeline) = self.pipelines.get_mut(&pipeline_id) {
             pipeline.epoch = epoch;
         }
     }
 }
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -1,32 +1,32 @@
 /* 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, ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{DocumentLayer, FilterOp, ImageFormat};
 use api::{LayerRect, MixBlendMode, PipelineId};
-use batch::{AlphaBatcher, ClipBatcher, resolve_image};
+use batch::{AlphaBatchBuilder, AlphaBatchContainer, ClipBatcher, resolve_image};
 use clip::{ClipStore};
 use clip_scroll_tree::{ClipScrollTree};
 use device::{FrameId, Texture};
 use gpu_cache::{GpuCache};
 use gpu_types::{BlurDirection, BlurInstance, BrushInstance, ClipChainRectIndex};
 use gpu_types::{ClipScrollNodeData, ClipScrollNodeIndex};
 use gpu_types::{PrimitiveInstance};
 use internal_types::{FastHashMap, RenderPassIndex, SourceTexture};
 use picture::{PictureKind};
 use prim_store::{PrimitiveIndex, PrimitiveKind, PrimitiveStore};
 use prim_store::{BrushMaskKind, BrushKind, DeferredResolve, EdgeAaSegmentMask};
 use profiler::FrameProfileCounters;
 use render_task::{BlitSource, RenderTaskAddress, RenderTaskId, RenderTaskKind};
 use render_task::{BlurTask, ClearMode, RenderTaskLocation, RenderTaskTree};
-use resource_cache::{ResourceCache};
+use resource_cache::ResourceCache;
 use std::{cmp, usize, f32, i32};
 use texture_allocator::GuillotineAllocator;
 
 const MIN_TARGET_SIZE: u32 = 2048;
 
 #[derive(Debug)]
 pub struct ScrollbarPrimitive {
     pub clip_id: ClipId,
@@ -264,68 +264,96 @@ pub struct BlitJob {
     pub source: BlitJobSource,
     pub target_rect: DeviceIntRect,
 }
 
 /// A render target represents a number of rendering operations on a surface.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct ColorRenderTarget {
-    pub alpha_batcher: AlphaBatcher,
+    pub alpha_batch_containers: Vec<AlphaBatchContainer>,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurInstance>,
     pub horizontal_blurs: Vec<BlurInstance>,
     pub readbacks: Vec<DeviceIntRect>,
     pub scalings: Vec<ScalingInfo>,
     pub blits: Vec<BlitJob>,
     // List of frame buffer outputs for this render target.
     pub outputs: Vec<FrameOutput>,
     allocator: Option<TextureAllocator>,
     alpha_tasks: Vec<RenderTaskId>,
+    screen_size: DeviceIntSize,
 }
 
 impl RenderTarget for ColorRenderTarget {
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
         self.allocator
             .as_mut()
             .expect("bug: calling allocate on framebuffer")
             .allocate(&size)
     }
 
     fn new(
         size: Option<DeviceUintSize>,
         screen_size: DeviceIntSize,
     ) -> Self {
         ColorRenderTarget {
-            alpha_batcher: AlphaBatcher::new(screen_size),
+            alpha_batch_containers: Vec::new(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
             scalings: Vec::new(),
             blits: Vec::new(),
             allocator: size.map(TextureAllocator::new),
             outputs: Vec::new(),
             alpha_tasks: Vec::new(),
+            screen_size,
         }
     }
 
     fn build(
         &mut self,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
         deferred_resolves: &mut Vec<DeferredResolve>,
     ) {
-        self.alpha_batcher.build(
-            &self.alpha_tasks,
-            ctx,
-            gpu_cache,
-            render_tasks,
-            deferred_resolves,
-        );
+        let mut merged_batches = AlphaBatchContainer::new(None);
+
+        for task_id in &self.alpha_tasks {
+            let task = &render_tasks[*task_id];
+
+            match task.kind {
+                RenderTaskKind::Picture(ref pic_task) => {
+                    let pic_index = ctx.prim_store.cpu_metadata[pic_task.prim_index.0].cpu_prim_index;
+                    let pic = &ctx.prim_store.cpu_pictures[pic_index.0];
+                    let (target_rect, _) = task.get_target_rect();
+
+                    let mut batch_builder = AlphaBatchBuilder::new(self.screen_size, target_rect);
+
+                    batch_builder.add_pic_to_batch(
+                        pic,
+                        *task_id,
+                        ctx,
+                        gpu_cache,
+                        render_tasks,
+                        deferred_resolves,
+                    );
+
+                    if let Some(batch_container) = batch_builder.build(&mut merged_batches) {
+                        self.alpha_batch_containers.push(batch_container);
+                    }
+                }
+                _ => {
+                    unreachable!();
+                }
+            }
+        }
+
+        self.alpha_batch_containers.push(merged_batches);
     }
 
     fn add_task(
         &mut self,
         task_id: RenderTaskId,
         ctx: &RenderTargetContext,
         gpu_cache: &mut GpuCache,
         render_tasks: &RenderTaskTree,
@@ -390,19 +418,17 @@ impl RenderTarget for ColorRenderTarget 
                     dest_task_id: task_id,
                 });
             }
             RenderTaskKind::Blit(ref task_info) => {
                 match task_info.source {
                     BlitSource::Image { key } => {
                         // Get the cache item for the source texture.
                         let cache_item = resolve_image(
-                            key.image_key,
-                            key.image_rendering,
-                            key.tile_offset,
+                            key.request,
                             ctx.resource_cache,
                             gpu_cache,
                             deferred_resolves,
                         );
 
                         // Work out a source rect to copy from the texture, depending on whether
                         // a sub-rect is present or not.
                         // TODO(gw): We have much type confusion below - f32, i32 and u32 for
@@ -441,17 +467,19 @@ impl RenderTarget for ColorRenderTarget 
     fn used_rect(&self) -> DeviceIntRect {
         self.allocator
             .as_ref()
             .expect("bug: used_rect called on framebuffer")
             .used_rect
     }
 
     fn needs_depth(&self) -> bool {
-        !self.alpha_batcher.batch_list.opaque_batch_list.batches.is_empty()
+        self.alpha_batch_containers.iter().any(|ab| {
+            !ab.opaque_batches.is_empty()
+        })
     }
 }
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct AlphaRenderTarget {
     pub clip_batcher: ClipBatcher,
     pub brush_mask_corners: Vec<PrimitiveInstance>,
@@ -788,17 +816,17 @@ impl RenderPass {
                         // one if required.
                         match task.location {
                             RenderTaskLocation::TextureCache(texture_id, layer, _) => {
                                 // TODO(gw): When we support caching color items, we will
                                 //           need to calculate that here to get the
                                 //           correct target kind.
                                 (RenderTargetKind::Alpha, Some((texture_id, layer)))
                             }
-                            RenderTaskLocation::Fixed => {
+                            RenderTaskLocation::Fixed(..) => {
                                 (RenderTargetKind::Color, None)
                             }
                             RenderTaskLocation::Dynamic(ref mut origin, size) => {
                                 let alloc_size = DeviceUintSize::new(size.width as u32, size.height as u32);
                                 let (alloc_origin, target_index) =  match target_kind {
                                     RenderTargetKind::Color => color.allocate(alloc_size),
                                     RenderTargetKind::Alpha => alpha.allocate(alloc_size),
                                 };
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -106,27 +106,21 @@ impl<Src, Dst> MatrixHelpers<Src, Dst> f
             self.m34.abs() < NEARLY_ZERO
     }
 }
 
 pub trait RectHelpers<U>
 where
     Self: Sized,
 {
-    fn contains_rect(&self, other: &Self) -> bool;
     fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self;
     fn is_well_formed_and_nonempty(&self) -> bool;
 }
 
 impl<U> RectHelpers<U> for TypedRect<f32, U> {
-    fn contains_rect(&self, other: &Self) -> bool {
-        self.origin.x <= other.origin.x && self.origin.y <= other.origin.y &&
-            self.max_x() >= other.max_x() && self.max_y() >= other.max_y()
-    }
-
     fn from_floats(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
         TypedRect::new(
             TypedPoint2D::new(x0, y0),
             TypedSize2D::new(x1 - x0, y1 - y0),
         )
     }
 
     fn is_well_formed_and_nonempty(&self) -> bool {
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -472,18 +472,16 @@ pub struct CapturedDocument {
 #[derive(Clone, Deserialize, Serialize)]
 pub enum DebugCommand {
     /// Display the frame profiler on screen.
     EnableProfiler(bool),
     /// Display all texture cache pages on screen.
     EnableTextureCacheDebug(bool),
     /// Display intermediate render targets on screen.
     EnableRenderTargetDebug(bool),
-    /// Display alpha primitive rects.
-    EnableAlphaRectsDebug(bool),
     /// Display GPU timing results.
     EnableGpuTimeQueries(bool),
     /// Display GPU overdraw results
     EnableGpuSampleQueries(bool),
     /// Configure if dual-source blending is used, if available.
     EnableDualSourceBlending(bool),
     /// Fetch current documents and display lists.
     FetchDocuments,
--- a/gfx/webrender_bindings/Cargo.toml
+++ b/gfx/webrender_bindings/Cargo.toml
@@ -5,17 +5,17 @@ authors = ["The Mozilla Project Develope
 license = "MPL-2.0"
 
 [dependencies]
 rayon = "0.8"
 thread_profiler = "0.1.1"
 euclid = "0.16"
 app_units = "0.6"
 gleam = "0.4.20"
-log = "0.3"
+log = "0.4"
 
 [dependencies.webrender]
 path = "../webrender"
 version = "0.57.0"
 default-features = false
 features = ["capture"]
 
 [target.'cfg(target_os = "windows")'.dependencies]
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-08e49649f1fc9cacff4e10ebc390babcea752236
+342bc314db94aa439b2001249c5f24ccfcbccc22
--- a/gfx/wrench/Cargo.toml
+++ b/gfx/wrench/Cargo.toml
@@ -4,25 +4,25 @@ version = "0.3.0"
 authors = ["Vladimir Vukicevic <vladimir@pobox.com>"]
 build = "build.rs"
 license = "MPL-2.0"
 
 [dependencies]
 base64 = "0.3"
 bincode = "0.9"
 byteorder = "1.0"
-env_logger = { version = "0.4", optional = true }
+env_logger = { version = "0.5", optional = true }
 euclid = "0.16"
 gleam = "0.4"
-servo-glutin = "0.14"
+glutin = "0.12"
 app_units = "0.6"
 image = "0.17"
 clap = { version = "2", features = ["yaml"] }
 lazy_static = "1"
-log = "0.3"
+log = "0.4"
 yaml-rust = { git = "https://github.com/vvuk/yaml-rust", features = ["preserve_order"] }
 serde_json = "1.0"
 ron = "0.1.5"
 time = "0.1"
 crossbeam = "0.2"
 osmesa-sys = { version = "0.1.2", optional = true }
 osmesa-src = { git = "https://github.com/servo/osmesa-src", optional = true }
 webrender = {path = "../webrender", features=["capture","replay","debugger","png","profiler"]}
@@ -30,15 +30,14 @@ webrender_api = {path = "../webrender_ap
 serde = {version = "1.0", features = ["derive"] }
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.13"
 core-foundation = "0.5"
 
 [features]
 headless = [ "osmesa-sys", "osmesa-src" ]
-logging = [ "env_logger" ]
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.4.1"
 
 [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
 font-loader = "0.6"
--- a/gfx/wrench/README.md
+++ b/gfx/wrench/README.md
@@ -24,12 +24,12 @@ If you are working on gecko integration 
 static ENABLE_RECORDING: bool = true;
 ```
 
 `wrench replay --save yaml` will convert the recording into frames described in yaml. Frames can then be replayed with `wrench show`.
 
 ## `reftest`
 
 Wrench also has a reftest system for catching regressions.
-* To run all reftests, run `./headless.py reftest`
-* To run specific reftests, run `./headless.py reftest path/to/test/or/dir`
+* To run all reftests, run `script/headless.py reftest`
+* To run specific reftests, run `script/headless.py reftest path/to/test/or/dir`
 * To examine test failures, use the [reftest analyzer](https://hg.mozilla.org/mozilla-central/raw-file/tip/layout/tools/reftest/reftest-analyzer.xhtml)
 * To add a new reftest, create an example frame and a reference frame in `reftests/` and then add an entry to `reftests/reftest.list`
--- a/gfx/wrench/src/main.rs
+++ b/gfx/wrench/src/main.rs
@@ -52,17 +52,17 @@ mod wrench;
 mod yaml_frame_reader;
 mod yaml_frame_writer;
 mod yaml_helper;
 #[cfg(target_os = "macos")]
 mod cgfont_to_data;
 
 use binary_frame_reader::BinaryFrameReader;
 use gleam::gl;
-use glutin::{ElementState, VirtualKeyCode, WindowProxy};
+use glutin::{GlContext, VirtualKeyCode};
 use perf::PerfHarness;
 use png::save_flipped;
 use rawtest::RawtestHarness;
 use reftest::{ReftestHarness, ReftestOptions};
 #[cfg(feature = "headless")]
 use std::ffi::CString;
 #[cfg(feature = "headless")]
 use std::mem;
@@ -95,17 +95,17 @@ pub struct HeadlessContext {
 #[cfg(not(feature = "headless"))]
 pub struct HeadlessContext {
     width: u32,
     height: u32,
 }
 
 impl HeadlessContext {
     #[cfg(feature = "headless")]
-    fn new(width: u32, height: u32) -> HeadlessContext {
+    fn new(width: u32, height: u32) -> Self {
         let mut attribs = Vec::new();
 
         attribs.push(osmesa_sys::OSMESA_PROFILE);
         attribs.push(osmesa_sys::OSMESA_CORE_PROFILE);
         attribs.push(osmesa_sys::OSMESA_CONTEXT_MAJOR_VERSION);
         attribs.push(3);
         attribs.push(osmesa_sys::OSMESA_CONTEXT_MINOR_VERSION);
         attribs.push(3);
@@ -135,80 +135,81 @@ impl HeadlessContext {
             width,
             height,
             _context: context,
             _buffer: buffer,
         }
     }
 
     #[cfg(not(feature = "headless"))]
-    fn new(width: u32, height: u32) -> HeadlessContext {
+    fn new(width: u32, height: u32) -> Self {
         HeadlessContext { width, height }
     }
 
     #[cfg(feature = "headless")]
     fn get_proc_address(s: &str) -> *const c_void {
         let c_str = CString::new(s).expect("Unable to create CString");
         unsafe { mem::transmute(osmesa_sys::OSMesaGetProcAddress(c_str.as_ptr())) }
     }
 
     #[cfg(not(feature = "headless"))]
     fn get_proc_address(_: &str) -> *const c_void {
         ptr::null() as *const _
     }
 }
 
 pub enum WindowWrapper {
-    Window(glutin::Window, Rc<gl::Gl>),
+    Window(glutin::GlWindow, Rc<gl::Gl>),
     Headless(HeadlessContext, Rc<gl::Gl>),
 }
 
 pub struct HeadlessEventIterater;
 
 impl WindowWrapper {
     fn swap_buffers(&self) {
         match *self {
             WindowWrapper::Window(ref window, _) => window.swap_buffers().unwrap(),
-            WindowWrapper::Headless(..) => {}
+            WindowWrapper::Headless(_, _) => {}
         }
     }
 
     fn get_inner_size(&self) -> DeviceUintSize {
         let (w, h) = match *self {
-            WindowWrapper::Window(ref window, _) => window.get_inner_size_pixels().unwrap(),
+            //HACK: `winit` needs to figure out its hidpi story...
+            #[cfg(target_os = "macos")]
+            WindowWrapper::Window(ref window, _) => {
+                let (w, h) = window.get_inner_size().unwrap();
+                let factor = window.hidpi_factor();
+                ((w as f32 * factor) as _, (h as f32 * factor) as _)
+            },
+            #[cfg(not(target_os = "macos"))]
+            WindowWrapper::Window(ref window, _) => window.get_inner_size().unwrap(),
             WindowWrapper::Headless(ref context, _) => (context.width, context.height),
         };
         DeviceUintSize::new(w, h)
     }
 
     fn hidpi_factor(&self) -> f32 {
         match *self {
             WindowWrapper::Window(ref window, _) => window.hidpi_factor(),
-            WindowWrapper::Headless(..) => 1.0,
+            WindowWrapper::Headless(_, _) => 1.0,
         }
     }
 
     fn resize(&mut self, size: DeviceUintSize) {
         match *self {
             WindowWrapper::Window(ref mut window, _) => window.set_inner_size(size.width, size.height),
             WindowWrapper::Headless(_, _) => unimplemented!(), // requites Glutin update
         }
     }
 
-    fn create_window_proxy(&mut self) -> Option<WindowProxy> {
-        match *self {
-            WindowWrapper::Window(ref window, _) => Some(window.create_window_proxy()),
-            WindowWrapper::Headless(..) => None,
-        }
-    }
-
     fn set_title(&mut self, title: &str) {
         match *self {
             WindowWrapper::Window(ref window, _) => window.set_title(title),
-            WindowWrapper::Headless(..) => (),
+            WindowWrapper::Headless(_, _) => (),
         }
     }
 
     pub fn gl(&self) -> &gl::Gl {
         match *self {
             WindowWrapper::Window(_, ref gl) | WindowWrapper::Headless(_, ref gl) => &**gl,
         }
     }
@@ -219,57 +220,65 @@ impl WindowWrapper {
         }
     }
 }
 
 fn make_window(
     size: DeviceUintSize,
     dp_ratio: Option<f32>,
     vsync: bool,
-    headless: bool,
+    events_loop: &Option<glutin::EventsLoop>,
 ) -> WindowWrapper {
-    let wrapper = if headless {
-        let gl = match gl::GlType::default() {
-            gl::GlType::Gl => unsafe {
-                gl::GlFns::load_with(|symbol| {
-                    HeadlessContext::get_proc_address(symbol) as *const _
-                })
-            },
-            gl::GlType::Gles => unsafe {
-                gl::GlesFns::load_with(|symbol| {
-                    HeadlessContext::get_proc_address(symbol) as *const _
+    let wrapper = match *events_loop {
+        Some(ref events_loop) => {
+            let context_builder = glutin::ContextBuilder::new()
+                .with_gl(glutin::GlRequest::GlThenGles {
+                    opengl_version: (3, 2),
+                    opengles_version: (3, 0),
                 })
-            },
-        };
-        WindowWrapper::Headless(HeadlessContext::new(size.width, size.height), gl)
-    } else {
-        let mut builder = glutin::WindowBuilder::new()
-            .with_gl(glutin::GlRequest::GlThenGles {
-                opengl_version: (3, 2),
-                opengles_version: (3, 0),
-            })
-            .with_dimensions(size.width, size.height);
-        builder.opengl.vsync = vsync;
-        let window = builder.build().unwrap();
-        unsafe {
-            window
-                .make_current()
-                .expect("unable to make context current!");
+                .with_vsync(vsync);
+            let window_builder = glutin::WindowBuilder::new()
+                .with_title("WRech")
+                .with_multitouch()
+                .with_dimensions(size.width, size.height);
+            let window = glutin::GlWindow::new(window_builder, context_builder, events_loop)
+                .unwrap();
+
+            unsafe {
+                window
+                    .make_current()
+                    .expect("unable to make context current!");
+            }
+
+            let gl = match window.get_api() {
+                glutin::Api::OpenGl => unsafe {
+                    gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _)
+                },
+                glutin::Api::OpenGlEs => unsafe {
+                    gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _)
+                },
+                glutin::Api::WebGl => unimplemented!(),
+            };
+            WindowWrapper::Window(window, gl)
         }
-
-        let gl = match window.get_api() {
-            glutin::Api::OpenGl => unsafe {
-                gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _)
-            },
-            glutin::Api::OpenGlEs => unsafe {
-                gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _)
-            },
-            glutin::Api::WebGl => unimplemented!(),
-        };
-        WindowWrapper::Window(window, gl)
+        None => {
+            let gl = match gl::GlType::default() {
+                gl::GlType::Gl => unsafe {
+                    gl::GlFns::load_with(|symbol| {
+                        HeadlessContext::get_proc_address(symbol) as *const _
+                    })
+                },
+                gl::GlType::Gles => unsafe {
+                    gl::GlesFns::load_with(|symbol| {
+                        HeadlessContext::get_proc_address(symbol) as *const _
+                    })
+                },
+            };
+            WindowWrapper::Headless(HeadlessContext::new(size.width, size.height), gl)
+        }
     };
 
     wrapper.gl().clear_color(0.3, 0.0, 0.0, 1.0);
 
     let gl_version = wrapper.gl().get_string(gl::VERSION);
     let gl_renderer = wrapper.gl().get_string(gl::RENDERER);
 
     let dp_ratio = dp_ratio.unwrap_or(wrapper.hidpi_factor());
@@ -308,17 +317,17 @@ impl RenderNotifier for Notifier {
 
 fn create_notifier() -> (Box<RenderNotifier>, Receiver<()>) {
     let (tx, rx) = channel();
     (Box::new(Notifier { tx: tx }), rx)
 }
 
 fn main() {
     #[cfg(feature = "logging")]
-    env_logger::init().unwrap();
+    env_logger::init();
 
     let args_yaml = load_yaml!("args.yaml");
     let args = clap::App::from_yaml(args_yaml)
         .setting(clap::AppSettings::ArgRequiredElseHelp)
         .get_matches();
 
     // handle some global arguments
     let res_path = args.value_of("shaders").map(|s| PathBuf::from(s));
@@ -341,34 +350,41 @@ fn main() {
             let x = s.find('x').expect(
                 "Size must be specified exactly as 720p, 1080p, 4k, or width x height",
             );
             let w = s[0 .. x].parse::<u32>().expect("Invalid size width");
             let h = s[x + 1 ..].parse::<u32>().expect("Invalid size height");
             DeviceUintSize::new(w, h)
         })
         .unwrap_or(DeviceUintSize::new(1920, 1080));
-    let is_headless = args.is_present("headless");
     let zoom_factor = args.value_of("zoom").map(|z| z.parse::<f32>().unwrap());
-    let mut window = make_window(size, dp_ratio, args.is_present("vsync"), is_headless);
+
+    let mut events_loop = if args.is_present("headless") {
+        None
+    } else {
+        Some(glutin::EventsLoop::new())
+    };
+
+    let mut window = make_window(size, dp_ratio, args.is_present("vsync"), &events_loop);
     let dp_ratio = dp_ratio.unwrap_or(window.hidpi_factor());
     let dim = window.get_inner_size();
 
     let needs_frame_notifier = ["perf", "reftest", "png", "rawtest"]
         .iter()
         .any(|s| args.subcommand_matches(s).is_some());
     let (notifier, rx) = if needs_frame_notifier {
         let (notifier, rx) = create_notifier();
         (Some(notifier), Some(rx))
     } else {
         (None, None)
     };
 
     let mut wrench = Wrench::new(
         &mut window,
+        events_loop.as_mut().map(|el| el.create_proxy()),
         res_path,
         dp_ratio,
         save_type,
         dim,
         args.is_present("rebuild"),
         args.is_present("no_subpixel_aa"),
         args.is_present("debug"),
         args.is_present("verbose"),
@@ -442,69 +458,62 @@ fn main() {
     let mut show_help = false;
     let mut do_loop = false;
     let mut cpu_profile_index = 0;
 
     let dim = window.get_inner_size();
     wrench.update(dim);
     thing.do_frame(&mut wrench);
 
-    'outer: loop {
+    let mut body = |wrench: &mut Wrench, global_event: glutin::Event| {
         if let Some(window_title) = wrench.take_title() {
             window.set_title(&window_title);
         }
 
-        let mut events = Vec::new();
-
-        match window {
-            WindowWrapper::Headless(..) => {
-                events.push(glutin::Event::Awakened);
-            }
-            WindowWrapper::Window(ref window, _) => {
-                events.push(window.wait_events().next().unwrap());
-                events.extend(window.poll_events());
-            }
-        }
-
         let mut do_frame = false;
         let mut do_render = false;
 
-        for event in events {
-            match event {
-                glutin::Event::Closed => {
-                    break 'outer;
+        match global_event {
+            glutin::Event::Awakened => {
+                do_render = true;
+            }
+            glutin::Event::WindowEvent { event, .. } => match event {
+                glutin::WindowEvent::Closed => {
+                    return glutin::ControlFlow::Break;
                 }
 
-                glutin::Event::Refresh |
-                glutin::Event::Awakened |
-                glutin::Event::Focused(..) |
-                glutin::Event::MouseMoved(..) => {
+                glutin::WindowEvent::Refresh |
+                glutin::WindowEvent::Focused(..) |
+                glutin::WindowEvent::CursorMoved { .. } => {
                     do_render = true;
                 }
 
-                glutin::Event::KeyboardInput(ElementState::Pressed, _scan_code, Some(vk)) => match vk {
+                glutin::WindowEvent::KeyboardInput {
+                    input: glutin::KeyboardInput {
+                        state: glutin::ElementState::Pressed,
+                        virtual_keycode: Some(vk),
+                        ..
+                    },
+                    ..
+                } => match vk {
                     VirtualKeyCode::Escape => {
-                        break 'outer;
+                        return glutin::ControlFlow::Break;
                     }
                     VirtualKeyCode::P => {
                         wrench.renderer.toggle_debug_flags(DebugFlags::PROFILER_DBG);
                         do_render = true;
                     }
                     VirtualKeyCode::O => {
                         wrench.renderer.toggle_debug_flags(DebugFlags::RENDER_TARGET_DBG);
                         do_render = true;
                     }
                     VirtualKeyCode::I => {
                         wrench.renderer.toggle_debug_flags(DebugFlags::TEXTURE_CACHE_DBG);
                         do_render = true;
                     }
-                    VirtualKeyCode::B => {
-                        wrench.renderer.toggle_debug_flags(DebugFlags::ALPHA_PRIM_DBG);
-                        do_render = true;
-                    }
                     VirtualKeyCode::S => {
                         wrench.renderer.toggle_debug_flags(DebugFlags::COMPACT_PROFILER);
                         do_render = true;
                     }
                     VirtualKeyCode::Q => {
                         wrench.renderer.toggle_debug_flags(
                             DebugFlags::GPU_TIME_QUERIES | DebugFlags::GPU_SAMPLE_QUERIES
                         );
@@ -555,24 +564,25 @@ fn main() {
                         let new_zoom_factor = ZoomFactor::new((current_zoom.get() - 0.1).max(0.1));
 
                         wrench.set_page_zoom(new_zoom_factor);
                         do_frame = true;
                     }
                     _ => {}
                 }
                 _ => {}
-            }
-        }
+            },
+            _ => return glutin::ControlFlow::Continue,
+        };
 
         let dim = window.get_inner_size();
         wrench.update(dim);
 
         if do_frame {
-            let frame_num = thing.do_frame(&mut wrench);
+            let frame_num = thing.do_frame(wrench);
             unsafe {
                 CURRENT_FRAME_NUMBER = frame_num;
             }
         }
 
         if do_render {
             if show_help {
                 wrench.show_onscreen_help();
@@ -580,18 +590,26 @@ fn main() {
 
             wrench.render();
             window.swap_buffers();
 
             if do_loop {
                 thing.next_frame();
             }
         }
-    }
+
+        glutin::ControlFlow::Continue
+    };
 
-    if is_headless {
-        let rect = DeviceUintRect::new(DeviceUintPoint::zero(), size);
-        let pixels = wrench.renderer.read_pixels_rgba8(rect);
-        save_flipped("screenshot.png", pixels, size);
+    match events_loop {
+        None => {
+            while body(&mut wrench, glutin::Event::Awakened) == glutin::ControlFlow::Continue {}
+            let rect = DeviceUintRect::new(DeviceUintPoint::zero(), size);
+            let pixels = wrench.renderer.read_pixels_rgba8(rect);
+            save_flipped("screenshot.png", pixels, size);
+        }
+        Some(ref mut events_loop) => {
+            events_loop.run_forever(|event| body(&mut wrench, event));
+        }
     }
 
     wrench.renderer.deinit();
 }
--- a/gfx/wrench/src/rawtest.rs
+++ b/gfx/wrench/src/rawtest.rs
@@ -34,16 +34,17 @@ impl<'a> RawtestHarness<'a> {
         RawtestHarness {
             wrench,
             rx,
             window,
         }
     }
 
     pub fn run(mut self) {
+        self.test_hit_testing();
         self.test_retained_blob_images_test();
         self.test_blob_update_test();
         self.test_blob_update_epoch_test();
         self.test_tile_decomposition();
         self.test_save_restore();
         self.test_capture();
     }
 
@@ -566,9 +567,129 @@ impl<'a> RawtestHarness<'a> {
         // 6. rebuild the scene and compare again
         let mut txn = Transaction::new();
         txn.set_root_pipeline(captured.root_pipeline_id.unwrap());
         txn.generate_frame();
         self.wrench.api.send_transaction(captured.document_id, txn);
         let pixels2 = self.render_and_get_pixels(window_rect);
         assert!(pixels0 == pixels2);
     }
+
+
+    fn test_hit_testing(&mut self) {
+        println!("\thit testing test...");
+
+        let layout_size = LayoutSize::new(400., 400.);
+        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id, layout_size);
+
+        // Add a rectangle that covers the entire scene.
+        let mut info = LayoutPrimitiveInfo::new(LayoutRect::new(LayoutPoint::zero(), layout_size));
+        info.tag = Some((0, 1));
+        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+
+
+        // Add a simple 100x100 rectangle at 100,0.
+        let mut info = LayoutPrimitiveInfo::new(LayoutRect::new(
+            LayoutPoint::new(100., 0.),
+            LayoutSize::new(100., 100.)
+        ));
+        info.tag = Some((0, 2));
+        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+
+        let make_rounded_complex_clip = | rect: &LayoutRect, radius: f32 | -> ComplexClipRegion {
+            ComplexClipRegion::new(
+                *rect,
+                BorderRadius::uniform_size(LayoutSize::new(radius, radius)),
+                ClipMode::Clip
+            )
+        };
+
+        // Add a rounded 100x100 rectangle at 200,0.
+        let rect = LayoutRect::new(LayoutPoint::new(200., 0.), LayoutSize::new(100., 100.));
+        let mut info = LayoutPrimitiveInfo::with_clip(
+            rect, LocalClip::RoundedRect(rect, make_rounded_complex_clip(&rect, 20.)));
+        info.tag = Some((0, 3));
+        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+
+
+        // Add a rectangle that is clipped by a rounded rect clip item.
+        let rect = LayoutRect::new(LayoutPoint::new(100., 100.), LayoutSize::new(100., 100.));
+        let clip_id = builder.define_clip(rect, vec![make_rounded_complex_clip(&rect, 20.)], None);
+        builder.push_clip_id(clip_id);
+        let mut info = LayoutPrimitiveInfo::new(rect);
+        info.tag = Some((0, 4));
+        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+        builder.pop_clip_id();
+
+
+        // Add a rectangle that is clipped by a ClipChain containing a rounded rect.
+        let rect = LayoutRect::new(LayoutPoint::new(200., 100.), LayoutSize::new(100., 100.));
+        let clip_id = builder.define_clip(rect, vec![make_rounded_complex_clip(&rect, 20.)], None);
+        let clip_chain_id = builder.define_clip_chain(None, vec![clip_id]);
+        builder.push_clip_and_scroll_info(ClipAndScrollInfo::new(
+            ClipId::root_scroll_node(self.wrench.root_pipeline_id),
+            ClipId::ClipChain(clip_chain_id),
+        ));
+        let mut info = LayoutPrimitiveInfo::new(rect);
+        info.tag = Some((0, 5));
+        builder.push_rect(&info, ColorF::new(1.0, 1.0, 1.0, 1.0));
+        builder.pop_clip_id();
+
+
+        let mut epoch = Epoch(0);
+        self.submit_dl(&mut epoch, layout_size, builder, None);
+
+        // We render to ensure that the hit tester is up to date with the current scene.
+        self.rx.recv().unwrap();
+        self.wrench.render();
+
+        let hit_test = | point: WorldPoint | -> HitTestResult {
+            self.wrench.api.hit_test(
+                self.wrench.document_id,
+                None,
+                point,
+                HitTestFlags::FIND_ALL,
+            )
+        };
+
+        let assert_hit_test = | point: WorldPoint, tags: Vec<ItemTag> | {
+            let result = hit_test(point);
+            assert_eq!(result.items.len(), tags.len());
+
+            for (hit_test_item, item_b) in result.items.iter().zip(tags.iter()) {
+                assert_eq!(hit_test_item.tag, *item_b);
+            }
+        };
+
+        // We should not have any hits outside the boundaries of the scene.
+        assert_hit_test(WorldPoint::new(-10., -10.), Vec::new());
+        assert_hit_test(WorldPoint::new(-10., 10.), Vec::new());
+        assert_hit_test(WorldPoint::new(450., 450.), Vec::new());
+        assert_hit_test(WorldPoint::new(100., 450.), Vec::new());
+
+        // The top left corner of the scene should only contain the background.
+        assert_hit_test(WorldPoint::new(50., 50.), vec![(0, 1)]);
+
+        // The middle of the normal rectangle should be hit.
+        assert_hit_test(WorldPoint::new(150., 50.), vec![(0, 2), (0, 1)]);
+
+        let test_rounded_rectangle = | point: WorldPoint, size: WorldSize, tag: ItemTag | {
+            // The cut out corners of the rounded rectangle should not be hit.
+            let top_left = point + WorldVector2D::new(5., 5.);
+            let bottom_right = point + size.to_vector() - WorldVector2D::new(5., 5.);
+
+            assert_hit_test(
+                WorldPoint::new(point.x + (size.width / 2.), point.y + (size.height / 2.)),
+                vec![tag, (0, 1)]
+            );
+
+            assert_hit_test(top_left, vec![(0, 1)]);
+            assert_hit_test(WorldPoint::new(bottom_right.x, top_left.y), vec![(0, 1)]);
+            assert_hit_test(WorldPoint::new(top_left.x, bottom_right.y), vec![(0, 1)]);
+            assert_hit_test(bottom_right, vec![(0, 1)]);
+        };
+
+        test_rounded_rectangle(WorldPoint::new(200., 0.), WorldSize::new(100., 100.), (0, 3));
+        test_rounded_rectangle(WorldPoint::new(100., 100.), WorldSize::new(100., 100.), (0, 4));
+        test_rounded_rectangle(WorldPoint::new(200., 100.), WorldSize::new(100., 100.), (0, 5));
+    }
+
 }
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -5,17 +5,17 @@
 
 use app_units::Au;
 use blob;
 use crossbeam::sync::chase_lev;
 #[cfg(windows)]
 use dwrote;
 #[cfg(any(target_os = "linux", target_os = "macos"))]
 use font_loader::system_fonts;
-use glutin::WindowProxy;
+use glutin::EventsLoopProxy;
 use json_frame_writer::JsonFrameWriter;
 use ron_frame_writer::RonFrameWriter;
 use std::collections::HashMap;
 use std::path::PathBuf;
 use std::sync::{Arc, Mutex};
 use time;
 use webrender;
 use webrender::api::*;
@@ -41,30 +41,30 @@ pub enum FontDescriptor {
 pub enum SaveType {
     Yaml,
     Json,
     Ron,
     Binary,
 }
 
 struct NotifierData {
-    window_proxy: Option<WindowProxy>,
+    events_loop_proxy: Option<EventsLoopProxy>,
     frames_notified: u32,
     timing_receiver: chase_lev::Stealer<time::SteadyTime>,
     verbose: bool,
 }
 
 impl NotifierData {
     fn new(
-        window_proxy: Option<WindowProxy>,
+        events_loop_proxy: Option<EventsLoopProxy>,
         timing_receiver: chase_lev::Stealer<time::SteadyTime>,
         verbose: bool,
     ) -> Self {
         NotifierData {
-            window_proxy,
+            events_loop_proxy,
             frames_notified: 0,
             timing_receiver,
             verbose,
         }
     }
 }
 
 struct Notifier(Arc<Mutex<NotifierData>>);
@@ -86,19 +86,20 @@ impl Notifier {
                         data.frames_notified = 0;
                     }
                 }
                 _ => {
                     println!("Notified of frame, but no frame was ready?");
                 }
             }
         }
-        if let Some(ref window_proxy) = data.window_proxy {
+
+        if let Some(ref elp) = data.events_loop_proxy {
             #[cfg(not(target_os = "android"))]
-            window_proxy.wakeup_event_loop();
+            let _ = elp.wakeup();
         }
     }
 }
 
 impl RenderNotifier for Notifier {
     fn clone(&self) -> Box<RenderNotifier> {
         Box::new(Notifier(self.0.clone()))
     }
@@ -160,16 +161,17 @@ pub struct Wrench {
     pub frame_start_sender: chase_lev::Worker<time::SteadyTime>,
 
     pub callbacks: Arc<Mutex<blob::BlobCallbacks>>,
 }
 
 impl Wrench {
     pub fn new(
         window: &mut WindowWrapper,
+        proxy: Option<EventsLoopProxy>,
         shader_override_path: Option<PathBuf>,
         dp_ratio: f32,
         save_type: Option<SaveType>,
         size: DeviceUintSize,
         do_rebuild: bool,
         no_subpixel_aa: bool,
         debug: bool,
         verbose: bool,
@@ -209,21 +211,20 @@ impl Wrench {
             enable_clear_scissor: !no_scissor,
             max_recorded_profiles: 16,
             precache_shaders,
             blob_image_renderer: Some(Box::new(blob::CheckerboardRenderer::new(callbacks.clone()))),
             disable_dual_source_blending,
             ..Default::default()
         };
 
-        let proxy = window.create_window_proxy();
         // put an Awakened event into the queue to kick off the first frame
-        if let Some(ref wp) = proxy {
+        if let Some(ref elp) = proxy {
             #[cfg(not(target_os = "android"))]
-            wp.wakeup_event_loop();
+            let _ = elp.wakeup();
         }
 
         let (timing_sender, timing_receiver) = chase_lev::deque();
         let notifier = notifier.unwrap_or_else(|| {
             let data = Arc::new(Mutex::new(NotifierData::new(proxy, timing_receiver, verbose)));
             Box::new(Notifier(data))
         });
 
@@ -543,16 +544,17 @@ impl Wrench {
     pub fn get_frame_profiles(
         &mut self,
     ) -> (Vec<webrender::CpuProfile>, Vec<webrender::GpuProfile>) {
         self.renderer.get_frame_profiles()
     }
 
     pub fn render(&mut self) -> RendererStats {
         self.renderer.update();
+        let _ = self.renderer.flush_pipeline_info();
         self.renderer
             .render(self.window_size)
             .expect("errors encountered during render!")
     }
 
     pub fn refresh(&mut self) {
         self.begin_frame();
         let mut txn = Transaction::new();
--- a/gfx/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wrench/src/yaml_frame_reader.rs
@@ -69,44 +69,47 @@ fn broadcast<T: Clone>(base_vals: &[T], 
         }
         vals.extend_from_slice(base_vals);
     }
     vals
 }
 
 fn generate_checkerboard_image(
     border: u32,
-    tile_size: u32,
-    tile_count: u32
+    tile_x_size: u32,
+    tile_y_size: u32,
+    tile_x_count: u32,
+    tile_y_count: u32,
 ) -> (ImageDescriptor, ImageData) {
-    let size = 2 * border + tile_size * tile_count;
+    let width = 2 * border + tile_x_size * tile_x_count;
+    let height = 2 * border + tile_y_size * tile_y_count;
     let mut pixels = Vec::new();
 
-    for y in 0 .. size {
-        for x in 0 .. size {
-            if y < border || y >= (size-border) ||
-               x < border || x >= (size-border) {
+    for y in 0 .. height {
+        for x in 0 .. width {
+            if y < border || y >= (height - border) ||
+               x < border || x >= (width - border) {
                 pixels.push(0);
                 pixels.push(0);
                 pixels.push(0xff);
                 pixels.push(0xff);
             } else {
-                let xon = ((x - border) % (2 * tile_size)) < tile_size;
-                let yon = ((y - border) % (2 * tile_size)) < tile_size;
+                let xon = ((x - border) % (2 * tile_x_size)) < tile_x_size;
+                let yon = ((y - border) % (2 * tile_y_size)) < tile_y_size;
                 let value = if xon ^ yon { 0xff } else { 0x7f };
                 pixels.push(value);
                 pixels.push(value);
                 pixels.push(value);
                 pixels.push(0xff);
             }
         }
     }
 
     (
-        ImageDescriptor::new(size, size, ImageFormat::BGRA8, true),
+        ImageDescriptor::new(width, height, ImageFormat::BGRA8, true),
         ImageData::new(pixels),
     )
 }
 
 fn generate_xy_gradient_image(w: u32, h: u32) -> (ImageDescriptor, ImageData) {
     let mut pixels = Vec::with_capacity((w * h * 4) as usize);
     for y in 0 .. h {
         for x in 0 .. w {
@@ -434,21 +437,45 @@ impl YamlFrameReader {
                     ("solid-color", args, _) => generate_solid_color_image(
                         args.get(0).unwrap_or(&"255").parse::<u8>().unwrap(),
                         args.get(1).unwrap_or(&"255").parse::<u8>().unwrap(),
                         args.get(2).unwrap_or(&"255").parse::<u8>().unwrap(),
                         args.get(3).unwrap_or(&"255").parse::<u8>().unwrap(),
                         args.get(4).unwrap_or(&"1000").parse::<u32>().unwrap(),
                         args.get(5).unwrap_or(&"1000").parse::<u32>().unwrap(),
                     ),
-                    ("checkerboard", args, _) => generate_checkerboard_image(
-                        args.get(0).unwrap_or(&"4").parse::<u32>().unwrap(),
-                        args.get(1).unwrap_or(&"32").parse::<u32>().unwrap(),
-                        args.get(2).unwrap_or(&"8").parse::<u32>().unwrap(),
-                    ),
+                    ("checkerboard", args, _) => {
+                        let border = args.get(0).unwrap_or(&"4").parse::<u32>().unwrap();
+
+                        let (x_size, y_size, x_count, y_count) = match args.len() {
+                            3 => {
+                                let size = args.get(1).unwrap_or(&"32").parse::<u32>().unwrap();
+                                let count = args.get(2).unwrap_or(&"8").parse::<u32>().unwrap();
+                                (size, size, count, count)
+                            }
+                            5 => {
+                                let x_size = args.get(1).unwrap_or(&"32").parse::<u32>().unwrap();
+                                let y_size = args.get(2).unwrap_or(&"32").parse::<u32>().unwrap();
+                                let x_count = args.get(3).unwrap_or(&"8").parse::<u32>().unwrap();
+                                let y_count = args.get(4).unwrap_or(&"8").parse::<u32>().unwrap();
+                                (x_size, y_size, x_count, y_count)
+                            }
+                            _ => {
+                                panic!("invalid checkerboard function");
+                            }
+                        };
+
+                        generate_checkerboard_image(
+                            border,
+                            x_size,
+                            y_size,
+                            x_count,
+                            y_count,
+                        )
+                    }
                     _ => {
                         panic!("Failed to load image {:?}", file.to_str());
                     }
                 }
             }
         };
         let tiling = tiling.map(|tile_size| tile_size as u16);
         let image_key = wrench.api.generate_image_key();
--- a/gfx/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wrench/src/yaml_frame_writer.rs
@@ -1013,17 +1013,17 @@ impl YamlFrameWriter {
                     if let Some(mask_yaml) = self.make_clip_mask_image_node(&item.image_mask) {
                         yaml_node(&mut v, "image-mask", mask_yaml);
                     }
                 }
                 ClipChain(item) => {
                     str_node(&mut v, "type", "clip-chain");
 
                     let id = ClipId::ClipChain(item.id);
-                    u32_node(&mut v, "id", clip_id_mapper.map_id(&id) as u32);
+                    u32_node(&mut v, "id", clip_id_mapper.add_id(id) as u32);
 
                     let clip_ids: Vec<u32> = display_list.get(base.clip_chain_items()).map(|clip_id| {
                         clip_id_mapper.map_id(&clip_id) as u32
                     }).collect();
                     u32_vec_node(&mut v, "clips", &clip_ids);
 
                     if let Some(parent) = item.parent {
                         let parent = ClipId::ClipChain(parent);