Bug 1358156 - Update webrender to 1437cc124696ecc95b726dffa17f918bb6ea5af1. r?jrmuizel draft
authorKartikaya Gupta <kgupta@mozilla.com>
Wed, 26 Apr 2017 05:31:37 -0400
changeset 568571 cc6d57736741bfff9fca371ecf30e09aa7f7af8d
parent 568570 8ac463bab10476c06f86ce91989b624acbeb4b16
child 568572 2cd432a5a0d3fedb3d7ad8872d75ac1a732ebbdf
push id55902
push userkgupta@mozilla.com
push dateWed, 26 Apr 2017 09:37:04 +0000
reviewersjrmuizel
bugs1358156
milestone55.0a1
Bug 1358156 - Update webrender to 1437cc124696ecc95b726dffa17f918bb6ea5af1. r?jrmuizel MozReview-Commit-ID: HsljOA3WG3E
gfx/doc/README.webrender
gfx/webrender/Cargo.toml
gfx/webrender/doc/CLIPPING.md
gfx/webrender/examples/basic.rs
gfx/webrender/examples/scrolling.rs
gfx/webrender/res/prim_shared.glsl
gfx/webrender/res/ps_border.fs.glsl
gfx/webrender/res/ps_border_corner.fs.glsl
gfx/webrender/res/ps_border_corner.glsl
gfx/webrender/res/ps_border_corner.vs.glsl
gfx/webrender/res/ps_border_edge.fs.glsl
gfx/webrender/res/ps_border_edge.glsl
gfx/webrender/res/ps_border_edge.vs.glsl
gfx/webrender/res/ps_composite.fs.glsl
gfx/webrender/src/border.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/gpu_store.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/profiler.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/render_task.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/resource_cache.rs
gfx/webrender/src/scene.rs
gfx/webrender/src/texture_cache.rs
gfx/webrender/src/tiling.rs
gfx/webrender/src/util.rs
gfx/webrender_traits/Cargo.toml
gfx/webrender_traits/src/api.rs
gfx/webrender_traits/src/display_item.rs
gfx/webrender_traits/src/display_list.rs
gfx/webrender_traits/src/webgl.rs
--- a/gfx/doc/README.webrender
+++ b/gfx/doc/README.webrender
@@ -74,9 +74,9 @@ there is another crate in m-c called moz
 the same folder to store its rust dependencies. If one of the libraries that is
 required by both mozjs_sys and webrender is updated without updating the other
 project's Cargo.lock file, that results in build bustage.
 This means that any time you do this sort of manual update of packages, you need
 to make sure that mozjs_sys also has its Cargo.lock file updated if needed, hence
 the need to run the cargo update command in js/src as well. Hopefully this will
 be resolved soon.
 
-Latest Commit: f3fa3481aac63ac93c6ccbe805379875e24e5b77
+Latest Commit: 1437cc124696ecc95b726dffa17f918bb6ea5af1
--- a/gfx/webrender/Cargo.toml
+++ b/gfx/webrender/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender"
-version = "0.35.0"
+version = "0.36.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 build = "build.rs"
 
 [features]
 default = ["freetype-lib", "webgl"]
 freetype-lib = ["freetype/servo-freetype-sys"]
@@ -28,18 +28,25 @@ time = "0.1"
 threadpool = "1.3.2"
 webrender_traits = {path = "../webrender_traits"}
 bitflags = "0.7"
 gamma-lut = "0.1"
 thread_profiler = "0.1.1"
 
 [dev-dependencies]
 angle = {git = "https://github.com/servo/angle", branch = "servo"}
+servo-glutin = "0.10.1"     # for the example apps
 
 [target.'cfg(any(target_os = "android", all(unix, not(target_os = "macos"))))'.dependencies]
 freetype = { version = "0.2", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 dwrote = "0.3"
 
 [target.'cfg(target_os = "macos")'.dependencies]
 core-graphics = "0.7.0"
 core-text = "4.0"
+
+[[example]]
+name = "basic"
+
+[[example]]
+name = "scrolling"
--- a/gfx/webrender/doc/CLIPPING.md
+++ b/gfx/webrender/doc/CLIPPING.md
@@ -77,16 +77,20 @@ provided `define_clip` will return a uni
 should always be true:
 
 ```rust
 let id = ClipId::new(my_internal_id, pipeline_id);
 let generated_id = define_clip(content_rect, clip, id);
 assert!(id == generated_id);
 ```
 
+Note that calling `define_clip` multiple times with the same `clip_id` value
+results in undefined behaviour, and should be avoided. There is a debug mode
+assertion to catch this.
+
 ## Pending changes
 1. Normalize the way that clipping coordinates are defined. Having them
    specified in two different ways makes for a confusing API. This should be
    fixed.  ([github issue](https://github.com/servo/webrender/issues/1090))
 
 1. It should be possible to specify more than one predefined clip for an item.
    This is necessary for items that live in a scrolling frame, but are also
    clipped by a clip that lives outside that frame.
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/basic.rs
@@ -0,0 +1,524 @@
+/* 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 app_units;
+extern crate euclid;
+extern crate gleam;
+extern crate glutin;
+extern crate webrender;
+extern crate webrender_traits;
+
+use app_units::Au;
+use gleam::gl;
+use glutin::TouchPhase;
+use std::collections::HashMap;
+use std::env;
+use std::fs::File;
+use std::io::Read;
+use std::path::PathBuf;
+use std::sync::Arc;
+use webrender_traits::{BlobImageData, BlobImageDescriptor, BlobImageError, BlobImageRenderer};
+use webrender_traits::{BlobImageResult, ClipRegion, ColorF, Epoch, GlyphInstance};
+use webrender_traits::{DeviceIntPoint, DeviceUintSize, DeviceUintRect, LayoutPoint, LayoutRect, LayoutSize};
+use webrender_traits::{ImageData, ImageDescriptor, ImageFormat, ImageKey, ImageRendering};
+use webrender_traits::{PipelineId, RasterizedBlobImage, TransformStyle, BoxShadowClipMode};
+
+#[derive(Debug)]
+enum Gesture {
+    None,
+    Pan,
+    Zoom,
+}
+
+#[derive(Debug)]
+struct Touch {
+    id: u64,
+    start_x: f32,
+    start_y: f32,
+    current_x: f32,
+    current_y: f32,
+}
+
+fn dist(x0: f32, y0: f32, x1: f32, y1: f32) -> f32 {
+    let dx = x0 - x1;
+    let dy = y0 - y1;
+    ((dx * dx) + (dy * dy)).sqrt()
+}
+
+impl Touch {
+    fn distance_from_start(&self) -> f32 {
+        dist(self.start_x, self.start_y, self.current_x, self.current_y)
+    }
+
+    fn initial_distance_from_other(&self, other: &Touch) -> f32 {
+        dist(self.start_x, self.start_y, other.start_x, other.start_y)
+    }
+
+    fn current_distance_from_other(&self, other: &Touch) -> f32 {
+        dist(self.current_x, self.current_y, other.current_x, other.current_y)
+    }
+}
+
+struct TouchState {
+    active_touches: HashMap<u64, Touch>,
+    current_gesture: Gesture,
+    start_zoom: f32,
+    current_zoom: f32,
+    start_pan: DeviceIntPoint,
+    current_pan: DeviceIntPoint,
+}
+
+enum TouchResult {
+    None,
+    Pan(DeviceIntPoint),
+    Zoom(f32),
+}
+
+impl TouchState {
+    fn new() -> TouchState {
+        TouchState {
+            active_touches: HashMap::new(),
+            current_gesture: Gesture::None,
+            start_zoom: 1.0,
+            current_zoom: 1.0,
+            start_pan: DeviceIntPoint::zero(),
+            current_pan: DeviceIntPoint::zero(),
+        }
+    }
+
+    fn handle_event(&mut self, touch: glutin::Touch) -> TouchResult {
+        match touch.phase {
+            TouchPhase::Started => {
+                debug_assert!(!self.active_touches.contains_key(&touch.id));
+                self.active_touches.insert(touch.id, Touch {
+                    id: touch.id,
+                    start_x: touch.location.0 as f32,
+                    start_y: touch.location.1 as f32,
+                    current_x: touch.location.0 as f32,
+                    current_y: touch.location.1 as f32,
+                });
+                self.current_gesture = Gesture::None;
+            }
+            TouchPhase::Moved => {
+                match self.active_touches.get_mut(&touch.id) {
+                    Some(active_touch) => {
+                        active_touch.current_x = touch.location.0 as f32;
+                        active_touch.current_y = touch.location.1 as f32;
+                    }
+                    None => panic!("move touch event with unknown touch id!")
+                }
+
+                match self.current_gesture {
+                    Gesture::None => {
+                        let mut over_threshold_count = 0;
+                        let active_touch_count = self.active_touches.len();
+
+                        for (_, touch) in &self.active_touches {
+                            if touch.distance_from_start() > 8.0 {
+                                over_threshold_count += 1;
+                            }
+                        }
+
+                        if active_touch_count == over_threshold_count {
+                            if active_touch_count == 1 {
+                                self.start_pan = self.current_pan;
+                                self.current_gesture = Gesture::Pan;
+                            } else if active_touch_count == 2 {
+                                self.start_zoom = self.current_zoom;
+                                self.current_gesture = Gesture::Zoom;
+                            }
+                        }
+                    }
+                    Gesture::Pan => {
+                        let keys: Vec<u64> = self.active_touches.keys().cloned().collect();
+                        debug_assert!(keys.len() == 1);
+                        let active_touch = &self.active_touches[&keys[0]];
+                        let x = active_touch.current_x - active_touch.start_x;
+                        let y = active_touch.current_y - active_touch.start_y;
+                        self.current_pan.x = self.start_pan.x + x.round() as i32;
+                        self.current_pan.y = self.start_pan.y + y.round() as i32;
+                        return TouchResult::Pan(self.current_pan);
+                    }
+                    Gesture::Zoom => {
+                        let keys: Vec<u64> = self.active_touches.keys().cloned().collect();
+                        debug_assert!(keys.len() == 2);
+                        let touch0 = &self.active_touches[&keys[0]];
+                        let touch1 = &self.active_touches[&keys[1]];
+                        let initial_distance = touch0.initial_distance_from_other(touch1);
+                        let current_distance = touch0.current_distance_from_other(touch1);
+                        self.current_zoom = self.start_zoom * current_distance / initial_distance;
+                        return TouchResult::Zoom(self.current_zoom);
+                    }
+                }
+            }
+            TouchPhase::Ended | TouchPhase::Cancelled => {
+                self.active_touches.remove(&touch.id).unwrap();
+                self.current_gesture = Gesture::None;
+            }
+        }
+
+        TouchResult::None
+    }
+}
+
+fn load_file(name: &str) -> Vec<u8> {
+    let mut file = File::open(name).unwrap();
+    let mut buffer = vec![];
+    file.read_to_end(&mut buffer).unwrap();
+    buffer
+}
+
+struct Notifier {
+    window_proxy: glutin::WindowProxy,
+}
+
+impl Notifier {
+    fn new(window_proxy: glutin::WindowProxy) -> Notifier {
+        Notifier {
+            window_proxy: window_proxy,
+        }
+    }
+}
+
+impl webrender_traits::RenderNotifier for Notifier {
+    fn new_frame_ready(&mut self) {
+        #[cfg(not(target_os = "android"))]
+        self.window_proxy.wakeup_event_loop();
+    }
+
+    fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
+        #[cfg(not(target_os = "android"))]
+        self.window_proxy.wakeup_event_loop();
+    }
+}
+
+fn main() {
+    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("WebRender Sample")
+                .with_multitouch()
+                .with_gl(glutin::GlRequest::GlThenGles {
+                    opengl_version: (3, 2),
+                    opengles_version: (3, 0)
+                })
+                .build()
+                .unwrap();
+
+    unsafe {
+        window.make_current().ok();
+    }
+
+    let gl = match gl::GlType::default() {
+        gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
+        gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
+    };
+
+    println!("OpenGL version {}", gl.get_string(gl::VERSION));
+    println!("Shader resource path: {:?}", res_path);
+
+    let (width, height) = window.get_inner_size_pixels().unwrap();
+
+    let opts = webrender::RendererOptions {
+        resource_override_path: res_path,
+        debug: true,
+        precache_shaders: true,
+        blob_image_renderer: Some(Box::new(FakeBlobImageRenderer::new())),
+        device_pixel_ratio: window.hidpi_factor(),
+        .. Default::default()
+    };
+
+    let size = DeviceUintSize::new(width, height);
+    let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
+    let api = sender.create_api();
+
+    let notifier = Box::new(Notifier::new(window.create_window_proxy()));
+    renderer.set_render_notifier(notifier);
+
+    let epoch = Epoch(0);
+    let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
+
+    let vector_img = api.generate_image_key();
+    api.add_image(
+        vector_img,
+        ImageDescriptor::new(100, 100, ImageFormat::RGBA8, true),
+        ImageData::new_blob_image(Vec::new()),
+        None,
+    );
+
+    let pipeline_id = PipelineId(0, 0);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+
+    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
+                                  bounds,
+                                  None,
+                                  TransformStyle::Flat,
+                                  None,
+                                  webrender_traits::MixBlendMode::Normal,
+                                  Vec::new());
+    builder.push_image(
+        LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(100.0, 100.0)),
+        ClipRegion::simple(&bounds),
+        LayoutSize::new(100.0, 100.0),
+        LayoutSize::new(0.0, 0.0),
+        ImageRendering::Auto,
+        vector_img,
+    );
+
+    let sub_clip = {
+        let mask_image = api.generate_image_key();
+        api.add_image(
+            mask_image,
+            ImageDescriptor::new(2, 2, ImageFormat::A8, true),
+            ImageData::new(vec![0, 80, 180, 255]),
+            None,
+        );
+        let mask = webrender_traits::ImageMask {
+            image: mask_image,
+            rect: LayoutRect::new(LayoutPoint::new(75.0, 75.0), LayoutSize::new(100.0, 100.0)),
+            repeat: false,
+        };
+        let complex = webrender_traits::ComplexClipRegion::new(
+            LayoutRect::new(LayoutPoint::new(50.0, 50.0), LayoutSize::new(100.0, 100.0)),
+            webrender_traits::BorderRadius::uniform(20.0));
+
+        builder.new_clip_region(&bounds, vec![complex], Some(mask))
+    };
+
+    builder.push_rect(LayoutRect::new(LayoutPoint::new(100.0, 100.0), LayoutSize::new(100.0, 100.0)),
+                      sub_clip,
+                      ColorF::new(0.0, 1.0, 0.0, 1.0));
+    builder.push_rect(LayoutRect::new(LayoutPoint::new(250.0, 100.0), LayoutSize::new(100.0, 100.0)),
+                      sub_clip,
+                      ColorF::new(0.0, 1.0, 0.0, 1.0));
+    let border_side = webrender_traits::BorderSide {
+        color: ColorF::new(0.0, 0.0, 1.0, 1.0),
+        style: webrender_traits::BorderStyle::Groove,
+    };
+    let border_widths = webrender_traits::BorderWidths {
+        top: 10.0,
+        left: 10.0,
+        bottom: 10.0,
+        right: 10.0,
+    };
+    let border_details = webrender_traits::BorderDetails::Normal(webrender_traits::NormalBorder {
+        top: border_side,
+        right: border_side,
+        bottom: border_side,
+        left: border_side,
+        radius: webrender_traits::BorderRadius::uniform(20.0),
+    });
+    builder.push_border(LayoutRect::new(LayoutPoint::new(100.0, 100.0), LayoutSize::new(100.0, 100.0)),
+                        sub_clip,
+                        border_widths,
+                        border_details);
+
+
+    if false { // draw text?
+        let font_key = api.generate_font_key();
+        let font_bytes = load_file("res/FreeSans.ttf");
+        api.add_raw_font(font_key, font_bytes, 0);
+
+        let text_bounds = LayoutRect::new(LayoutPoint::new(100.0, 200.0), LayoutSize::new(700.0, 300.0));
+
+        let glyphs = vec![
+            GlyphInstance {
+                index: 48,
+                point: LayoutPoint::new(100.0, 100.0),
+            },
+            GlyphInstance {
+                index: 68,
+                point: LayoutPoint::new(150.0, 100.0),
+            },
+            GlyphInstance {
+                index: 80,
+                point: LayoutPoint::new(200.0, 100.0),
+            },
+            GlyphInstance {
+                index: 82,
+                point: LayoutPoint::new(250.0, 100.0),
+            },
+            GlyphInstance {
+                index: 81,
+                point: LayoutPoint::new(300.0, 100.0),
+            },
+            GlyphInstance {
+                index: 3,
+                point: LayoutPoint::new(350.0, 100.0),
+            },
+            GlyphInstance {
+                index: 86,
+                point: LayoutPoint::new(400.0, 100.0),
+            },
+            GlyphInstance {
+                index: 79,
+                point: LayoutPoint::new(450.0, 100.0),
+            },
+            GlyphInstance {
+                index: 72,
+                point: LayoutPoint::new(500.0, 100.0),
+            },
+            GlyphInstance {
+                index: 83,
+                point: LayoutPoint::new(550.0, 100.0),
+            },
+            GlyphInstance {
+                index: 87,
+                point: LayoutPoint::new(600.0, 100.0),
+            },
+            GlyphInstance {
+                index: 17,
+                point: LayoutPoint::new(650.0, 100.0),
+            },
+        ];
+
+        builder.push_text(text_bounds,
+                          webrender_traits::ClipRegion::simple(&bounds),
+                          &glyphs,
+                          font_key,
+                          ColorF::new(1.0, 1.0, 0.0, 1.0),
+                          Au::from_px(32),
+                          Au::from_px(0),
+                          None);
+    }
+
+    if false { // draw box shadow?
+        let rect = LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(0.0, 0.0));
+        let simple_box_bounds = LayoutRect::new(LayoutPoint::new(20.0, 200.0),
+                                                LayoutSize::new(50.0, 50.0));
+        let offset = LayoutPoint::new(10.0, 10.0);
+        let color = ColorF::new(1.0, 1.0, 1.0, 1.0);
+        let blur_radius = 0.0;
+        let spread_radius = 0.0;
+        let simple_border_radius = 8.0;
+        let box_shadow_type = BoxShadowClipMode::Inset;
+        let full_screen_clip = builder.new_clip_region(&bounds, Vec::new(), None);
+
+        builder.push_box_shadow(rect,
+                                full_screen_clip,
+                                simple_box_bounds,
+                                offset,
+                                color,
+                                blur_radius,
+                                spread_radius,
+                                simple_border_radius,
+                                box_shadow_type);
+    }
+
+    builder.pop_stacking_context();
+
+    api.set_display_list(
+        Some(root_background_color),
+        epoch,
+        LayoutSize::new(width as f32, height as f32),
+        builder.finalize(),
+        true);
+    api.set_root_pipeline(pipeline_id);
+    api.generate_frame(None);
+
+    let mut touch_state = TouchState::new();
+
+    'outer: for event in window.wait_events() {
+        let mut events = Vec::new();
+        events.push(event);
+
+        for event in window.poll_events() {
+            events.push(event);
+        }
+
+        for event in events {
+            match event {
+                glutin::Event::Closed |
+                glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
+                glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Q)) => break 'outer,
+                glutin::Event::KeyboardInput(glutin::ElementState::Pressed,
+                                             _, Some(glutin::VirtualKeyCode::P)) => {
+                    let enable_profiler = !renderer.get_profiler_enabled();
+                    renderer.set_profiler_enabled(enable_profiler);
+                    api.generate_frame(None);
+                }
+                glutin::Event::Touch(touch) => {
+                    match touch_state.handle_event(touch) {
+                        TouchResult::Pan(pan) => {
+                            api.set_pan(pan);
+                            api.generate_frame(None);
+                        }
+                        TouchResult::Zoom(zoom) => {
+                            api.set_pinch_zoom(webrender_traits::ZoomFactor::new(zoom));
+                            api.generate_frame(None);
+                        }
+                        TouchResult::None => {}
+                    }
+                }
+                _ => ()
+            }
+        }
+
+        renderer.update();
+        renderer.render(DeviceUintSize::new(width, height));
+        window.swap_buffers().ok();
+    }
+}
+
+struct FakeBlobImageRenderer {
+    images: HashMap<ImageKey, BlobImageResult>,
+}
+
+impl FakeBlobImageRenderer {
+    fn new() -> Self {
+        FakeBlobImageRenderer { images: HashMap::new() }
+    }
+}
+
+impl BlobImageRenderer for FakeBlobImageRenderer {
+    fn request_blob_image(&mut self,
+                          key: ImageKey,
+                          _: Arc<BlobImageData>,
+                          descriptor: &BlobImageDescriptor,
+                          _dirty_rect: Option<DeviceUintRect>) {
+        let mut texels = Vec::with_capacity((descriptor.width * descriptor.height * 4) as usize);
+        for y in 0..descriptor.height {
+            for x in 0..descriptor.width {
+                // render a simple checkerboard pattern
+                let a = if (x % 20 >= 10) != (y % 20 >= 10) { 255 } else { 0 };
+                match descriptor.format {
+                    ImageFormat::RGBA8 => {
+                        texels.push(a);
+                        texels.push(a);
+                        texels.push(a);
+                        texels.push(255);
+                    }
+                    ImageFormat::A8 => {
+                        texels.push(a);
+                    }
+                    _ => {
+                        self.images.insert(key,
+                            Err(BlobImageError::Other(format!(
+                                "Usupported image format {:?}",
+                                descriptor.format
+                            )))
+                        );
+                        return;
+                    }
+                }
+            }
+        }
+
+        self.images.insert(key, Ok(RasterizedBlobImage {
+            data: texels,
+            width: descriptor.width,
+            height: descriptor.height,
+        }));
+    }
+
+    fn resolve_blob_image(&mut self, key: ImageKey) -> BlobImageResult {
+        self.images.remove(&key).unwrap_or(Err(BlobImageError::InvalidKey))
+    }
+}
new file mode 100644
--- /dev/null
+++ b/gfx/webrender/examples/scrolling.rs
@@ -0,0 +1,239 @@
+/* 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 gleam;
+extern crate glutin;
+extern crate webrender;
+extern crate webrender_traits;
+
+use gleam::gl;
+use std::env;
+use std::path::PathBuf;
+use webrender_traits::{ClipId, ClipRegion, ColorF, DeviceUintSize, Epoch, LayoutPoint, LayoutRect};
+use webrender_traits::{LayoutSize, PipelineId, ScrollEventPhase, ScrollLocation, TransformStyle};
+use webrender_traits::WorldPoint;
+
+struct Notifier {
+    window_proxy: glutin::WindowProxy,
+}
+
+impl Notifier {
+    fn new(window_proxy: glutin::WindowProxy) -> Notifier {
+        Notifier {
+            window_proxy: window_proxy,
+        }
+    }
+}
+
+impl webrender_traits::RenderNotifier for Notifier {
+    fn new_frame_ready(&mut self) {
+        #[cfg(not(target_os = "android"))]
+        self.window_proxy.wakeup_event_loop();
+    }
+
+    fn new_scroll_frame_ready(&mut self, _composite_needed: bool) {
+        #[cfg(not(target_os = "android"))]
+        self.window_proxy.wakeup_event_loop();
+    }
+}
+
+trait HandyDandyRectBuilder {
+    fn to(&self, x2: i32, y2: i32) -> LayoutRect;
+}
+// Allows doing `(x, y).to(x2, y2)` to build a LayoutRect
+impl HandyDandyRectBuilder for (i32, i32) {
+    fn to(&self, x2: i32, y2: i32) -> LayoutRect {
+        LayoutRect::new(LayoutPoint::new(self.0 as f32, self.1 as f32),
+                        LayoutSize::new((x2 - self.0) as f32, (y2 - self.1) as f32))
+    }
+}
+
+
+fn main() {
+    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("WebRender Scrolling Sample")
+                .with_gl(glutin::GlRequest::GlThenGles {
+                    opengl_version: (3, 2),
+                    opengles_version: (3, 0)
+                })
+                .build()
+                .unwrap();
+
+    unsafe {
+        window.make_current().ok();
+    }
+
+    let gl = match gl::GlType::default() {
+        gl::GlType::Gl => unsafe { gl::GlFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
+        gl::GlType::Gles => unsafe { gl::GlesFns::load_with(|symbol| window.get_proc_address(symbol) as *const _) },
+    };
+
+    println!("OpenGL version {}", gl.get_string(gl::VERSION));
+    println!("Shader resource path: {:?}", res_path);
+
+    let (width, height) = window.get_inner_size_pixels().unwrap();
+
+    let opts = webrender::RendererOptions {
+        resource_override_path: res_path,
+        debug: true,
+        precache_shaders: true,
+        device_pixel_ratio: window.hidpi_factor(),
+        .. Default::default()
+    };
+
+    let size = DeviceUintSize::new(width, height);
+    let (mut renderer, sender) = webrender::renderer::Renderer::new(gl, opts, size).unwrap();
+    let api = sender.create_api();
+
+    let notifier = Box::new(Notifier::new(window.create_window_proxy()));
+    renderer.set_render_notifier(notifier);
+
+    let epoch = Epoch(0);
+    let root_background_color = ColorF::new(0.3, 0.0, 0.0, 1.0);
+
+    let pipeline_id = PipelineId(0, 0);
+    let mut builder = webrender_traits::DisplayListBuilder::new(pipeline_id);
+
+    let bounds = LayoutRect::new(LayoutPoint::zero(), LayoutSize::new(width as f32, height as f32));
+    builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
+                                  bounds,
+                                  None,
+                                  TransformStyle::Flat,
+                                  None,
+                                  webrender_traits::MixBlendMode::Normal,
+                                  Vec::new());
+
+    if true {   // scrolling and clips stuff
+        // let's make a scrollbox
+        let scrollbox = (0, 0).to(300, 400);
+        builder.push_stacking_context(webrender_traits::ScrollPolicy::Scrollable,
+                                      LayoutRect::new(LayoutPoint::new(10.0, 10.0),
+                                                      LayoutSize::zero()),
+                                      None,
+                                      TransformStyle::Flat,
+                                      None,
+                                      webrender_traits::MixBlendMode::Normal,
+                                      Vec::new());
+        // set the scrolling clip
+        let clip_id = builder.define_clip((0, 0).to(1000, 1000),
+                                          ClipRegion::simple(&scrollbox),
+                                          Some(ClipId::new(42, pipeline_id)));
+        builder.push_clip_id(clip_id);
+        // now put some content into it.
+        // start with a white background
+        builder.push_rect((0, 0).to(500, 500),
+                          ClipRegion::simple(&(0, 0).to(1000, 1000)),
+                          ColorF::new(1.0, 1.0, 1.0, 1.0));
+        // let's make a 50x50 blue square as a visual reference
+        builder.push_rect((0, 0).to(50, 50),
+                          ClipRegion::simple(&(0, 0).to(50, 50)),
+                          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
+        builder.push_rect((50, 0).to(100, 50),
+                          ClipRegion::simple(&(60, 10).to(110, 60)),
+                          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_clip((0, 100).to(300, 400),
+                                                 ClipRegion::simple(&(0, 100).to(200, 300)),
+                                                 Some(ClipId::new(43, pipeline_id)));
+        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
+        builder.push_rect((-1000, -1000).to(5000, 5000),
+                          ClipRegion::simple(&(-1000, -1000).to(5000, 5000)),
+                          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 with WASD keys
+        builder.push_rect((0, 100).to(50, 150),
+                          ClipRegion::simple(&(0, 100).to(50, 150)),
+                          ColorF::new(0.0, 1.0, 1.0, 1.0));
+        // just for good measure add another teal square in the bottom-right
+        // corner of the nested scrollframe content, which can be scrolled into
+        // view by the user
+        builder.push_rect((250, 350).to(300, 400),
+                          ClipRegion::simple(&(250, 350).to(300, 400)),
+                          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();
+
+    api.set_display_list(
+        Some(root_background_color),
+        epoch,
+        LayoutSize::new(width as f32, height as f32),
+        builder.finalize(),
+        true);
+    api.set_root_pipeline(pipeline_id);
+    api.generate_frame(None);
+
+    let mut cursor_position = WorldPoint::zero();
+
+    'outer: for event in window.wait_events() {
+        let mut events = Vec::new();
+        events.push(event);
+
+        for event in window.poll_events() {
+            events.push(event);
+        }
+
+        for event in events {
+            match event {
+                glutin::Event::Closed |
+                glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
+                glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Q)) => break 'outer,
+                glutin::Event::KeyboardInput(glutin::ElementState::Pressed, _, 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),
+                         _ => continue,
+                    };
+
+                    api.scroll(ScrollLocation::Delta(LayoutPoint::new(offset.0, offset.1)),
+                               cursor_position,
+                               ScrollEventPhase::Start);
+                }
+                glutin::Event::MouseMoved(x, y) => {
+                    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 {
+                        cursor_position = WorldPoint::new(x as f32, y as f32);
+                    }
+
+                    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),
+                    };
+
+                    api.scroll(ScrollLocation::Delta(LayoutPoint::new(dx, dy)),
+                               cursor_position,
+                               ScrollEventPhase::Start);
+                }
+                _ => ()
+            }
+        }
+
+        renderer.update();
+        renderer.render(DeviceUintSize::new(width, height));
+        window.swap_buffers().ok();
+    }
+}
--- a/gfx/webrender/res/prim_shared.glsl
+++ b/gfx/webrender/res/prim_shared.glsl
@@ -22,16 +22,28 @@
 #define PST_BOTTOM_LEFT  6
 #define PST_LEFT         7
 
 #define BORDER_LEFT      0
 #define BORDER_TOP       1
 #define BORDER_RIGHT     2
 #define BORDER_BOTTOM    3
 
+// Border styles as defined in webrender_traits/types.rs
+#define BORDER_STYLE_NONE         0
+#define BORDER_STYLE_SOLID        1
+#define BORDER_STYLE_DOUBLE       2
+#define BORDER_STYLE_DOTTED       3
+#define BORDER_STYLE_DASHED       4
+#define BORDER_STYLE_HIDDEN       5
+#define BORDER_STYLE_GROOVE       6
+#define BORDER_STYLE_RIDGE        7
+#define BORDER_STYLE_INSET        8
+#define BORDER_STYLE_OUTSET       9
+
 #define UV_NORMALIZED    uint(0)
 #define UV_PIXEL         uint(1)
 
 #define EXTEND_MODE_CLAMP  0
 #define EXTEND_MODE_REPEAT 1
 
 uniform sampler2DArray sCacheA8;
 uniform sampler2DArray sCacheRGBA8;
@@ -297,16 +309,38 @@ RadialGradient fetch_radial_gradient(int
 
 struct Border {
     vec4 style;
     vec4 widths;
     vec4 colors[4];
     vec4 radii[2];
 };
 
+vec4 get_effective_border_widths(Border border) {
+    switch (int(border.style.x)) {
+        case BORDER_STYLE_DOUBLE:
+            // Calculate the width of a border segment in a style: double
+            // border. Round to the nearest CSS pixel.
+
+            // The CSS spec doesn't define what width each of the segments
+            // in a style: double border should be. It only says that the
+            // sum of the segments should be equal to the total border
+            // width. We pick to make the segments (almost) equal thirds
+            // for now - we can adjust this if we find other browsers pick
+            // different values in some cases.
+            // SEE: https://drafts.csswg.org/css-backgrounds-3/#double
+            return floor(0.5 + border.widths / 3.0);
+        case BORDER_STYLE_GROOVE:
+        case BORDER_STYLE_RIDGE:
+            return floor(0.5 + border.widths * 0.5);
+        default:
+            return border.widths;
+    }
+}
+
 Border fetch_border(int index) {
     vec4 data[8] = fetch_data_8(index);
     return Border(data[0], data[1],
                   vec4[4](data[2], data[3], data[4], data[5]),
                   vec4[2](data[6], data[7]));
 }
 
 struct BorderCorners {
--- a/gfx/webrender/res/ps_border.fs.glsl
+++ b/gfx/webrender/res/ps_border.fs.glsl
@@ -1,26 +1,14 @@
 #line 1
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-// Border styles as defined in webrender_traits/types.rs
-#define BORDER_STYLE_NONE         0
-#define BORDER_STYLE_SOLID        1
-#define BORDER_STYLE_DOUBLE       2
-#define BORDER_STYLE_DOTTED       3
-#define BORDER_STYLE_DASHED       4
-#define BORDER_STYLE_HIDDEN       5
-#define BORDER_STYLE_GROOVE       6
-#define BORDER_STYLE_RIDGE        7
-#define BORDER_STYLE_INSET        8
-#define BORDER_STYLE_OUTSET       9
-
 void discard_pixels_in_rounded_borders(vec2 local_pos) {
   float distanceFromRef = distance(vRefPoint, local_pos);
   if (vRadii.x > 0.0 && (distanceFromRef > vRadii.x || distanceFromRef < vRadii.z)) {
       discard;
   }
 }
 
 vec4 get_fragment_color(float distanceFromMixLine, float pixelsPerFragment) {
--- a/gfx/webrender/res/ps_border_corner.fs.glsl
+++ b/gfx/webrender/res/ps_border_corner.fs.glsl
@@ -79,34 +79,75 @@ void main(void) {
     vec2 local_pos = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
     // Find the appropriate distance to apply the AA smoothstep over.
     vec2 fw = fwidth(local_pos);
     float afwidth = length(fw);
+    float distance_for_color;
+    float color_mix_factor;
 
     // Only apply the clip AA if inside the clip region. This is
     // necessary for correctness when the border width is greater
     // than the border radius.
     if (all(lessThan(local_pos * vClipSign, vClipCenter * vClipSign))) {
         vec2 p = local_pos - vClipCenter;
 
         // Get signed distance from the inner/outer clips.
-        float d0 = distance_to_ellipse(p, vOuterRadii);
-        float d1 = distance_to_ellipse(p, vInnerRadii);
+        float d0 = distance_to_ellipse(p, vRadii0.xy);
+        float d1 = distance_to_ellipse(p, vRadii0.zw);
+        float d2 = distance_to_ellipse(p, vRadii1.xy);
+        float d3 = distance_to_ellipse(p, vRadii1.zw);
 
-        // Signed distance field subtract
-        float d = max(d0, 0.5 * afwidth - d1);
+        // SDF subtract main radii
+        float d_main = max(d0, 0.5 * afwidth - d1);
+
+        // SDF subtract inner radii (double style borders)
+        float d_inner = max(d2 - 0.5 * afwidth, -d3);
+
+        // Select how to combine the SDF based on border style.
+        float d = mix(max(d_main, -d_inner), d_main, vSDFSelect);
 
         // Only apply AA to fragments outside the signed distance field.
-        alpha = min(alpha, 1.0 - smoothstep(0.0, afwidth, d));
+        alpha = min(alpha, 1.0 - smoothstep(0.0, 0.5 * afwidth, d));
+
+        // Get the groove/ridge mix factor.
+        color_mix_factor = smoothstep(-0.5 * afwidth,
+                                      0.5 * afwidth,
+                                      -d2);
+    } else {
+        // Handle the case where the fragment is outside the clip
+        // region in a corner. This occurs when border width is
+        // greater than border radius.
+
+        // Get linear distances along horizontal and vertical edges.
+        vec2 d0 = vClipSign.xx * (local_pos.xx - vEdgeDistance.xz);
+        vec2 d1 = vClipSign.yy * (local_pos.yy - vEdgeDistance.yw);
+        // Apply union to get the outer edge signed distance.
+        float da = min(d0.x, d1.x);
+        // Apply intersection to get the inner edge signed distance.
+        float db = max(-d0.y, -d1.y);
+        // Apply union to get both edges.
+        float d = min(da, db);
+        // Select fragment on/off based on signed distance.
+        // No AA here, since we know we're on a straight edge
+        // and the width is rounded to a whole CSS pixel.
+        alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
+
+        // Get the groove/ridge mix factor.
+        // TODO(gw): Support AA for groove/ridge border edge with transforms.
+        color_mix_factor = mix(0.0, 1.0, da > 0.0);
     }
 
+    // Mix inner/outer color.
+    vec4 color0 = mix(vColor00, vColor01, color_mix_factor);
+    vec4 color1 = mix(vColor10, vColor11, color_mix_factor);
+
     // Select color based on side of line. Get distance from the
     // reference line, and then apply AA along the edge.
     float ld = distance_to_line(vColorEdgeLine.xy, vColorEdgeLine.zw, local_pos);
     float m = smoothstep(-0.5 * afwidth, 0.5 * afwidth, ld);
-    vec4 color = mix(vColor0, vColor1, m);
+    vec4 color = mix(color0, color1, m);
 
     oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
 }
--- a/gfx/webrender/res/ps_border_corner.glsl
+++ b/gfx/webrender/res/ps_border_corner.glsl
@@ -1,22 +1,29 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Edge color transition
-flat varying vec4 vColor0;
-flat varying vec4 vColor1;
+flat varying vec4 vColor00;
+flat varying vec4 vColor01;
+flat varying vec4 vColor10;
+flat varying vec4 vColor11;
 flat varying vec4 vColorEdgeLine;
 
 // Border radius
 flat varying vec2 vClipCenter;
-flat varying vec2 vOuterRadii;
-flat varying vec2 vInnerRadii;
+flat varying vec4 vRadii0;
+flat varying vec4 vRadii1;
 flat varying vec2 vClipSign;
+flat varying vec4 vEdgeDistance;
+flat varying float vSDFSelect;
+
+// Border style
+flat varying float vAlphaSelect;
 
 #ifdef WR_FEATURE_TRANSFORM
 flat varying RectWithSize vLocalRect;
 varying vec3 vLocalPos;
 #else
 varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_corner.vs.glsl
+++ b/gfx/webrender/res/ps_border_corner.vs.glsl
@@ -1,105 +1,213 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-void set_radii(vec2 border_radius,
-               vec2 border_width) {
-    if (border_radius.x > 0.0 && border_radius.y > 0.0) {
-        // Set inner/outer radius on valid border radius.
-        vOuterRadii = border_radius;
-    } else {
-        // No border radius - ensure clip has no effect.
-        vOuterRadii = vec2(2.0 * border_width);
+vec2 get_radii(vec2 radius, vec2 invalid) {
+    if (all(greaterThan(radius, vec2(0.0)))) {
+        return radius;
     }
 
-    if (all(greaterThan(border_radius, border_width))) {
-        vInnerRadii = border_radius - border_width;
-    } else {
-        vInnerRadii = vec2(0.0);
+    return invalid;
+}
+
+void set_radii(float style,
+               vec2 radii,
+               vec2 widths,
+               vec2 adjusted_widths) {
+    vRadii0.xy = get_radii(radii, 2.0 * widths);
+    vRadii0.zw = get_radii(radii - widths, -widths);
+
+    switch (int(style)) {
+        case BORDER_STYLE_RIDGE:
+        case BORDER_STYLE_GROOVE:
+            vRadii1.xy = radii - adjusted_widths;
+            vRadii1.zw = -widths;
+            break;
+        case BORDER_STYLE_DOUBLE:
+            vRadii1.xy = get_radii(radii - adjusted_widths, -widths);
+            vRadii1.zw = get_radii(radii - widths + adjusted_widths, -widths);
+            break;
+        default:
+            vRadii1.xy = -widths;
+            vRadii1.zw = -widths;
+            break;
     }
 }
 
 void set_edge_line(vec2 border_width,
                    vec2 outer_corner,
                    vec2 gradient_sign) {
     vec2 gradient = border_width * gradient_sign;
     vColorEdgeLine = vec4(outer_corner, vec2(-gradient.y, gradient.x));
 }
 
+void write_color(vec4 color0, vec4 color1, int style, vec2 delta) {
+    vec4 modulate;
+
+    switch (style) {
+        case BORDER_STYLE_GROOVE:
+            modulate = vec4(1.0 - 0.3 * delta.x,
+                            1.0 + 0.3 * delta.x,
+                            1.0 - 0.3 * delta.y,
+                            1.0 + 0.3 * delta.y);
+
+            break;
+        case BORDER_STYLE_RIDGE:
+            modulate = vec4(1.0 + 0.3 * delta.x,
+                            1.0 - 0.3 * delta.x,
+                            1.0 + 0.3 * delta.y,
+                            1.0 - 0.3 * delta.y);
+            break;
+        default:
+            modulate = vec4(1.0);
+            break;
+    }
+
+    vColor00 = vec4(color0.rgb * modulate.x, color0.a);
+    vColor01 = vec4(color0.rgb * modulate.y, color0.a);
+    vColor10 = vec4(color1.rgb * modulate.z, color1.a);
+    vColor11 = vec4(color1.rgb * modulate.w, color1.a);
+}
+
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
 
-    RectWithSize segment_rect;
+    vec4 adjusted_widths = get_effective_border_widths(border);
+    vec4 inv_adjusted_widths = border.widths - adjusted_widths;
+    vec2 p0, p1;
+
+    // TODO(gw): We'll need to pass through multiple styles
+    //           once we support style transitions per corner.
+    int style;
+    vec4 edge_distances;
+    vec4 color0, color1;
+    vec2 color_delta;
+
     switch (sub_part) {
         case 0: {
-            segment_rect.p0 = corners.tl_outer;
-            segment_rect.size = corners.tl_inner - corners.tl_outer;
-            vColor0 = border.colors[0];
-            vColor1 = border.colors[1];
+            p0 = corners.tl_outer;
+            p1 = corners.tl_inner;
+            color0 = border.colors[0];
+            color1 = border.colors[1];
             vClipCenter = corners.tl_outer + border.radii[0].xy;
             vClipSign = vec2(1.0);
-            set_radii(border.radii[0].xy,
-                      border.widths.xy);
+            set_radii(border.style.x,
+                      border.radii[0].xy,
+                      border.widths.xy,
+                      adjusted_widths.xy);
             set_edge_line(border.widths.xy,
                           corners.tl_outer,
                           vec2(1.0, 1.0));
+            style = int(border.style.x);
+            edge_distances = vec4(p0 + adjusted_widths.xy,
+                                  p0 + inv_adjusted_widths.xy);
+            color_delta = vec2(1.0);
             break;
         }
         case 1: {
-            segment_rect.p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
-            segment_rect.size = vec2(corners.tr_outer.x - corners.tr_inner.x,
-                                     corners.tr_inner.y - corners.tr_outer.y);
-            vColor0 = border.colors[1];
-            vColor1 = border.colors[2];
+            p0 = vec2(corners.tr_inner.x, corners.tr_outer.y);
+            p1 = vec2(corners.tr_outer.x, corners.tr_inner.y);
+            color0 = border.colors[1];
+            color1 = border.colors[2];
             vClipCenter = corners.tr_outer + vec2(-border.radii[0].z, border.radii[0].w);
             vClipSign = vec2(-1.0, 1.0);
-            set_radii(border.radii[0].zw,
-                      border.widths.zy);
+            set_radii(border.style.y,
+                      border.radii[0].zw,
+                      border.widths.zy,
+                      adjusted_widths.zy);
             set_edge_line(border.widths.zy,
                           corners.tr_outer,
                           vec2(-1.0, 1.0));
+            style = int(border.style.y);
+            edge_distances = vec4(p1.x - adjusted_widths.z,
+                                  p0.y + adjusted_widths.y,
+                                  p1.x - border.widths.z + adjusted_widths.z,
+                                  p0.y + inv_adjusted_widths.y);
+            color_delta = vec2(1.0, -1.0);
             break;
         }
         case 2: {
-            segment_rect.p0 = corners.br_inner;
-            segment_rect.size = corners.br_outer - corners.br_inner;
-            vColor0 = border.colors[2];
-            vColor1 = border.colors[3];
+            p0 = corners.br_inner;
+            p1 = corners.br_outer;
+            color0 = border.colors[2];
+            color1 = border.colors[3];
             vClipCenter = corners.br_outer - border.radii[1].xy;
             vClipSign = vec2(-1.0, -1.0);
-            set_radii(border.radii[1].xy,
-                      border.widths.zw);
+            set_radii(border.style.z,
+                      border.radii[1].xy,
+                      border.widths.zw,
+                      adjusted_widths.zw);
             set_edge_line(border.widths.zw,
                           corners.br_outer,
                           vec2(-1.0, -1.0));
+            style = int(border.style.z);
+            edge_distances = vec4(p1.x - adjusted_widths.z,
+                                  p1.y - adjusted_widths.w,
+                                  p1.x - border.widths.z + adjusted_widths.z,
+                                  p1.y - border.widths.w + adjusted_widths.w);
+            color_delta = vec2(-1.0);
             break;
         }
         case 3: {
-            segment_rect.p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
-            segment_rect.size = vec2(corners.bl_inner.x - corners.bl_outer.x,
-                                     corners.bl_outer.y - corners.bl_inner.y);
-            vColor0 = border.colors[3];
-            vColor1 = border.colors[0];
+            p0 = vec2(corners.bl_outer.x, corners.bl_inner.y);
+            p1 = vec2(corners.bl_inner.x, corners.bl_outer.y);
+            color0 = border.colors[3];
+            color1 = border.colors[0];
             vClipCenter = corners.bl_outer + vec2(border.radii[1].z, -border.radii[1].w);
             vClipSign = vec2(1.0, -1.0);
-            set_radii(border.radii[1].zw,
-                      border.widths.xw);
+            set_radii(border.style.w,
+                      border.radii[1].zw,
+                      border.widths.xw,
+                      adjusted_widths.xw);
             set_edge_line(border.widths.xw,
                           corners.bl_outer,
                           vec2(1.0, -1.0));
+            style = int(border.style.w);
+            edge_distances = vec4(p0.x + adjusted_widths.x,
+                                  p1.y - adjusted_widths.w,
+                                  p0.x + inv_adjusted_widths.x,
+                                  p1.y - border.widths.w + adjusted_widths.w);
+            color_delta = vec2(-1.0, 1.0);
             break;
         }
     }
 
+    switch (int(style)) {
+        case BORDER_STYLE_DOUBLE: {
+            vEdgeDistance = edge_distances;
+            vAlphaSelect = 0.0;
+            vSDFSelect = 0.0;
+            break;
+        }
+        case BORDER_STYLE_GROOVE:
+        case BORDER_STYLE_RIDGE:
+            vEdgeDistance = vec4(edge_distances.xy, 0.0, 0.0);
+            vAlphaSelect = 1.0;
+            vSDFSelect = 1.0;
+            break;
+        default: {
+            vEdgeDistance = vec4(0.0);
+            vAlphaSelect = 1.0;
+            vSDFSelect = 0.0;
+            break;
+        }
+    }
+
+    write_color(color0, color1, style, color_delta);
+
+    RectWithSize segment_rect;
+    segment_rect.p0 = p0;
+    segment_rect.size = p1 - p0;
+
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
                                                     prim.task,
                                                     prim.local_rect.p0);
     vLocalPos = vi.local_pos;
--- a/gfx/webrender/res/ps_border_edge.fs.glsl
+++ b/gfx/webrender/res/ps_border_edge.fs.glsl
@@ -1,15 +1,47 @@
+#line 1
+
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 void main(void) {
     float alpha = 1.0;
 #ifdef WR_FEATURE_TRANSFORM
     alpha = 0.0;
-    init_transform_fs(vLocalPos, vLocalRect, alpha);
+    vec2 local_pos = init_transform_fs(vLocalPos, vLocalRect, alpha);
+#else
+    vec2 local_pos = vLocalPos;
 #endif
 
     alpha = min(alpha, do_clip());
 
-    oFragColor = vColor * vec4(1.0, 1.0, 1.0, alpha);
+    // Find the appropriate distance to apply the step over.
+    vec2 fw = fwidth(local_pos);
+
+    // Applies the math necessary to draw a style: double
+    // border. In the case of a solid border, the vertex
+    // shader sets interpolator values that make this have
+    // no effect.
+
+    // Select the x/y coord, depending on which axis this edge is.
+    float pos = mix(local_pos.x, local_pos.y, vAxisSelect);
+
+    // Get signed distance from each of the inner edges.
+    float d0 = pos - vEdgeDistance.x;
+    float d1 = vEdgeDistance.y - pos;
+
+    // SDF union to select both outer edges.
+    float d = min(d0, d1);
+
+    // Select fragment on/off based on signed distance.
+    // No AA here, since we know we're on a straight edge
+    // and the width is rounded to a whole CSS pixel.
+    alpha = min(alpha, mix(vAlphaSelect, 1.0, d < 0.0));
+
+    // Mix color based on first distance.
+    // TODO(gw): Support AA for groove/ridge border edge with transforms.
+    vec4 color = mix(vColor0, vColor1, bvec4(d0 * vEdgeDistance.y > 0.0));
+
+    //oFragColor = vec4(d0 * vEdgeDistance.y, -d0 * vEdgeDistance.y, 0, 1.0);
+    oFragColor = color * vec4(1.0, 1.0, 1.0, alpha);
 }
--- a/gfx/webrender/res/ps_border_edge.glsl
+++ b/gfx/webrender/res/ps_border_edge.glsl
@@ -1,10 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-flat varying vec4 vColor;
+flat varying vec4 vColor0;
+flat varying vec4 vColor1;
+flat varying vec2 vEdgeDistance;
+flat varying float vAxisSelect;
+flat varying float vAlphaSelect;
 
 #ifdef WR_FEATURE_TRANSFORM
 varying vec3 vLocalPos;
 flat varying RectWithSize vLocalRect;
+#else
+varying vec2 vLocalPos;
 #endif
--- a/gfx/webrender/res/ps_border_edge.vs.glsl
+++ b/gfx/webrender/res/ps_border_edge.vs.glsl
@@ -1,38 +1,103 @@
 #line 1
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+void write_edge_distance(float p0,
+                         float original_width,
+                         float adjusted_width,
+                         float style,
+                         float axis_select,
+                         float sign_adjust) {
+    switch (int(style)) {
+        case BORDER_STYLE_DOUBLE:
+            vEdgeDistance = vec2(p0 + adjusted_width,
+                                 p0 + original_width - adjusted_width);
+            break;
+        case BORDER_STYLE_GROOVE:
+        case BORDER_STYLE_RIDGE:
+            vEdgeDistance = vec2(p0 + adjusted_width, sign_adjust);
+            break;
+        default:
+            vEdgeDistance = vec2(0.0);
+            break;
+    }
+
+    vAxisSelect = axis_select;
+}
+
+void write_alpha_select(float style) {
+    switch (int(style)) {
+        case BORDER_STYLE_DOUBLE:
+            vAlphaSelect = 0.0;
+            break;
+        default:
+            vAlphaSelect = 1.0;
+            break;
+    }
+}
+
+void write_color(vec4 color, float style, bool flip) {
+    vec2 modulate;
+
+    switch (int(style)) {
+        case BORDER_STYLE_GROOVE:
+            modulate = flip ? vec2(1.3, 0.7) : vec2(0.7, 1.3);
+            break;
+        case BORDER_STYLE_RIDGE:
+            modulate = flip ? vec2(0.7, 1.3) : vec2(1.3, 0.7);
+            break;
+        default:
+            modulate = vec2(1.0);
+            break;
+    }
+
+    vColor0 = vec4(color.rgb * modulate.x, color.a);
+    vColor1 = vec4(color.rgb * modulate.y, color.a);
+}
+
 void main(void) {
     Primitive prim = load_primitive();
     Border border = fetch_border(prim.prim_index);
     int sub_part = prim.sub_index;
     BorderCorners corners = get_border_corners(border, prim.local_rect);
-
-    vColor = border.colors[sub_part];
+    vec4 adjusted_widths = get_effective_border_widths(border);
+    vec4 color = border.colors[sub_part];
 
     RectWithSize segment_rect;
     switch (sub_part) {
         case 0:
             segment_rect.p0 = vec2(corners.tl_outer.x, corners.tl_inner.y);
             segment_rect.size = vec2(border.widths.x, corners.bl_inner.y - corners.tl_inner.y);
+            write_edge_distance(segment_rect.p0.x, border.widths.x, adjusted_widths.x, border.style.x, 0.0, 1.0);
+            write_alpha_select(border.style.x);
+            write_color(color, border.style.x, false);
             break;
         case 1:
             segment_rect.p0 = vec2(corners.tl_inner.x, corners.tl_outer.y);
             segment_rect.size = vec2(corners.tr_inner.x - corners.tl_inner.x, border.widths.y);
+            write_edge_distance(segment_rect.p0.y, border.widths.y, adjusted_widths.y, border.style.y, 1.0, 1.0);
+            write_alpha_select(border.style.y);
+            write_color(color, border.style.y, false);
             break;
         case 2:
             segment_rect.p0 = vec2(corners.tr_outer.x - border.widths.z, corners.tr_inner.y);
             segment_rect.size = vec2(border.widths.z, corners.br_inner.y - corners.tr_inner.y);
+            write_edge_distance(segment_rect.p0.x, border.widths.z, adjusted_widths.z, border.style.z, 0.0, -1.0);
+            write_alpha_select(border.style.z);
+            write_color(color, border.style.z, true);
             break;
         case 3:
             segment_rect.p0 = vec2(corners.bl_inner.x, corners.bl_outer.y - border.widths.w);
             segment_rect.size = vec2(corners.br_inner.x - corners.bl_inner.x, border.widths.w);
+            write_edge_distance(segment_rect.p0.y, border.widths.w, adjusted_widths.w, border.style.w, 1.0, -1.0);
+            write_alpha_select(border.style.w);
+            write_color(color, border.style.w, true);
             break;
     }
 
 #ifdef WR_FEATURE_TRANSFORM
     TransformVertexInfo vi = write_transform_vertex(segment_rect,
                                                     prim.local_clip_rect,
                                                     prim.z,
                                                     prim.layer,
@@ -42,12 +107,13 @@ void main(void) {
     vLocalRect = segment_rect;
 #else
     VertexInfo vi = write_vertex(segment_rect,
                                  prim.local_clip_rect,
                                  prim.z,
                                  prim.layer,
                                  prim.task,
                                  prim.local_rect.p0);
+    vLocalPos = vi.local_pos;
 #endif
 
     write_clip(vi.screen_pos, prim.clip_area);
 }
--- a/gfx/webrender/res/ps_composite.fs.glsl
+++ b/gfx/webrender/res/ps_composite.fs.glsl
@@ -166,29 +166,29 @@ const int MixBlendMode_Hue         = 12;
 const int MixBlendMode_Saturation  = 13;
 const int MixBlendMode_Color       = 14;
 const int MixBlendMode_Luminosity  = 15;
 
 void main(void) {
     vec4 Cb = texture(sCacheRGBA8, vUv0);
     vec4 Cs = texture(sCacheRGBA8, vUv1);
 
+    // The mix-blend-mode functions assume no premultiplied alpha
+    Cb.rgb /= Cb.a;
+    Cs.rgb /= Cs.a;
+
     if (Cb.a == 0.0) {
         oFragColor = Cs;
         return;
     }
     if (Cs.a == 0.0) {
         oFragColor = vec4(0.0, 0.0, 0.0, 0.0);
         return;
     }
 
-    // The mix-blend-mode functions assume no premultiplied alpha
-    Cb.rgb /= Cb.a;
-    Cs.rgb /= Cs.a;
-
     // Return yellow if none of the branches match (shouldn't happen).
     vec4 result = vec4(1.0, 1.0, 0.0, 1.0);
 
     switch (vOp) {
         case MixBlendMode_Multiply:
             result.rgb = Multiply(Cb.rgb, Cs.rgb);
             break;
         case MixBlendMode_Screen:
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -16,16 +16,17 @@ pub enum BorderCornerKind {
     Clip,
     Unhandled,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum BorderEdgeKind {
     None,
     Solid,
+    Clip,
     Unhandled,
 }
 
 pub trait NormalBorderHelpers {
     fn get_corner(&self,
                   edge0: &BorderSide,
                   width0: f32,
                   edge1: &BorderSide,
@@ -67,17 +68,20 @@ impl NormalBorderHelpers for NormalBorde
                 } else {
                     BorderCornerKind::Clip
                 }
             }
 
             // Inset / outset borders just modtify the color of edges, so can be
             // drawn with the normal border corner shader.
             (BorderStyle::Outset, BorderStyle::Outset) |
-            (BorderStyle::Inset, BorderStyle::Inset) => BorderCornerKind::Clip,
+            (BorderStyle::Inset, BorderStyle::Inset) |
+            (BorderStyle::Double, BorderStyle::Double) |
+            (BorderStyle::Groove, BorderStyle::Groove) |
+            (BorderStyle::Ridge, BorderStyle::Ridge) => BorderCornerKind::Clip,
 
             // Assume complex for these cases.
             // TODO(gw): There are some cases in here that can be handled with a fast path.
             // For example, with inset/outset borders, two of the four corners are solid.
             (BorderStyle::Dotted, _) | (_, BorderStyle::Dotted) => BorderCornerKind::Unhandled,
             (BorderStyle::Dashed, _) | (_, BorderStyle::Dashed) => BorderCornerKind::Unhandled,
             (BorderStyle::Double, _) | (_, BorderStyle::Double) => BorderCornerKind::Unhandled,
             (BorderStyle::Groove, _) | (_, BorderStyle::Groove) => BorderCornerKind::Unhandled,
@@ -98,20 +102,21 @@ impl NormalBorderHelpers for NormalBorde
             BorderStyle::None |
             BorderStyle::Hidden => (BorderEdgeKind::None, 0.0),
 
             BorderStyle::Solid |
             BorderStyle::Inset |
             BorderStyle::Outset => (BorderEdgeKind::Solid, width),
 
             BorderStyle::Double |
+            BorderStyle::Groove |
+            BorderStyle::Ridge => (BorderEdgeKind::Clip, width),
+
             BorderStyle::Dotted |
-            BorderStyle::Dashed |
-            BorderStyle::Groove |
-            BorderStyle::Ridge => (BorderEdgeKind::Unhandled, width),
+            BorderStyle::Dashed => (BorderEdgeKind::Unhandled, width),
         }
     }
 }
 
 impl FrameBuilder {
     fn add_normal_border_primitive(&mut self,
                                    rect: &LayerRect,
                                    border: &NormalBorder,
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -311,17 +311,18 @@ impl Frame {
         let background_color = root_pipeline.background_color.and_then(|color| {
             if color.a > 0.0 {
                 Some(color)
             } else {
                 None
             }
         });
 
-        let mut frame_builder = FrameBuilder::new(window_size,
+        let mut frame_builder = FrameBuilder::new(self.frame_builder.take(),
+                                                  window_size,
                                                   background_color,
                                                   self.frame_builder_config);
 
         {
             let mut context = FlattenContext::new(scene, &mut frame_builder, resource_cache);
 
             let clip_id = context.builder.push_root(root_pipeline_id,
                                                     &root_pipeline.viewport_size,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -19,17 +19,17 @@ use resource_cache::ResourceCache;
 use clip_scroll_node::{ClipInfo, ClipScrollNode, NodeType};
 use clip_scroll_tree::ClipScrollTree;
 use std::{cmp, f32, i32, mem, usize};
 use euclid::SideOffsets2D;
 use tiling::StackingContextIndex;
 use tiling::{AuxiliaryListsMap, ClipScrollGroup, ClipScrollGroupIndex, CompositeOps, Frame};
 use tiling::{PackedLayer, PackedLayerIndex, PrimitiveFlags, PrimitiveRunCmd, RenderPass};
 use tiling::{RenderTargetContext, RenderTaskCollection, ScrollbarPrimitive, StackingContext};
-use util::{self, pack_as_float, subtract_rect};
+use util::{self, pack_as_float, subtract_rect, recycle_vec};
 use util::RectHelpers;
 use webrender_traits::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipId, ClipRegion};
 use webrender_traits::{ColorF, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect};
 use webrender_traits::{DeviceUintSize, ExtendMode, FontKey, FontRenderMode, GlyphOptions};
 use webrender_traits::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
 use webrender_traits::{LayerToScrollTransform, PipelineId, RepeatMode, TileOffset, TransformStyle};
 use webrender_traits::{WebGLContextId, YuvColorSpace};
 
@@ -119,31 +119,51 @@ pub struct FrameBuilder {
     reference_frame_stack: Vec<ClipId>,
 
     /// A stack of stacking contexts used for creating ClipScrollGroups as
     /// primitives are added to the frame.
     stacking_context_stack: Vec<StackingContextIndex>,
 }
 
 impl FrameBuilder {
-    pub fn new(screen_size: DeviceUintSize,
+    pub fn new(previous: Option<FrameBuilder>,
+               screen_size: DeviceUintSize,
                background_color: Option<ColorF>,
                config: FrameBuilderConfig) -> FrameBuilder {
-        FrameBuilder {
-            screen_size: screen_size,
-            background_color: background_color,
-            stacking_context_store: Vec::new(),
-            clip_scroll_group_store: Vec::new(),
-            prim_store: PrimitiveStore::new(),
-            cmds: Vec::new(),
-            packed_layers: Vec::new(),
-            scrollbar_prims: Vec::new(),
-            config: config,
-            reference_frame_stack: Vec::new(),
-            stacking_context_stack: Vec::new(),
+        match previous {
+            Some(prev) => {
+                FrameBuilder {
+                    stacking_context_store: recycle_vec(prev.stacking_context_store),
+                    clip_scroll_group_store: recycle_vec(prev.clip_scroll_group_store),
+                    cmds: recycle_vec(prev.cmds),
+                    packed_layers: recycle_vec(prev.packed_layers),
+                    scrollbar_prims: recycle_vec(prev.scrollbar_prims),
+                    reference_frame_stack: recycle_vec(prev.reference_frame_stack),
+                    stacking_context_stack: recycle_vec(prev.stacking_context_stack),
+                    prim_store: prev.prim_store.recycle(),
+                    screen_size: screen_size,
+                    background_color: background_color,
+                    config: config,
+                }
+            }
+            None => {
+                FrameBuilder {
+                    stacking_context_store: Vec::new(),
+                    clip_scroll_group_store: Vec::new(),
+                    cmds: Vec::new(),
+                    packed_layers: Vec::new(),
+                    scrollbar_prims: Vec::new(),
+                    reference_frame_stack: Vec::new(),
+                    stacking_context_stack: Vec::new(),
+                    prim_store: PrimitiveStore::new(),
+                    screen_size: screen_size,
+                    background_color: background_color,
+                    config: config,
+                }
+            }
         }
     }
 
     pub fn add_primitive(&mut self,
                          clip_id: ClipId,
                          rect: &LayerRect,
                          clip_region: &ClipRegion,
                          extra_clips: &[ClipSource],
@@ -716,28 +736,24 @@ impl FrameBuilder {
 
         self.add_primitive(clip_id,
                            &rect,
                            clip_region,
                            &[],
                            PrimitiveContainer::TextRun(prim_cpu, prim_gpu));
     }
 
-    pub fn add_box_shadow_no_blur(&mut self,
-                                  clip_id: ClipId,
-                                  box_bounds: &LayerRect,
-                                  clip_region: &ClipRegion,
-                                  box_offset: &LayerPoint,
-                                  color: &ColorF,
-                                  spread_radius: f32,
-                                  border_radius: f32,
-                                  clip_mode: BoxShadowClipMode) {
-        assert!(spread_radius == 0.0); // TODO: fix cases where this isn't true.
-        let bs_rect = box_bounds.translate(box_offset);
-
+    pub fn fill_box_shadow_rect(&mut self,
+                                clip_id: ClipId,
+                                box_bounds: &LayerRect,
+                                bs_rect: LayerRect,
+                                clip_region: &ClipRegion,
+                                color: &ColorF,
+                                border_radius: f32,
+                                clip_mode: BoxShadowClipMode) {
         // We can draw a rectangle instead with the proper border radius clipping.
         let (bs_clip_mode, rect_to_draw) = match clip_mode {
             BoxShadowClipMode::Outset |
             BoxShadowClipMode::None => (ClipMode::Clip, bs_rect),
             BoxShadowClipMode::Inset => (ClipMode::ClipOut, *box_bounds),
         };
 
         let box_clip_mode = !bs_clip_mode;
@@ -797,24 +813,23 @@ impl FrameBuilder {
                                      box_bounds,
                                      clip_region,
                                      color,
                                      PrimitiveFlags::None);
             return;
         }
 
         if blur_radius == 0.0 && spread_radius == 0.0 && border_radius != 0.0 {
-            self.add_box_shadow_no_blur(clip_id,
-                                        box_bounds,
-                                        clip_region,
-                                        box_offset,
-                                        color,
-                                        spread_radius,
-                                        border_radius,
-                                        clip_mode);
+            self.fill_box_shadow_rect(clip_id,
+                                      box_bounds,
+                                      bs_rect,
+                                      clip_region,
+                                      color,
+                                      border_radius,
+                                      clip_mode);
             return;
         }
 
         // Get the outer rectangle, based on the blur radius.
         let outside_edge_size = 2.0 * blur_radius;
         let inside_edge_size = outside_edge_size.max(border_radius);
         let edge_size = outside_edge_size + inside_edge_size;
         let outer_rect = bs_rect.inflate(outside_edge_size, outside_edge_size);
@@ -880,16 +895,26 @@ impl FrameBuilder {
                     self.add_solid_rectangle(clip_id,
                                              rect,
                                              clip_region,
                                              color,
                                              PrimitiveFlags::None)
                 }
             }
             BoxShadowKind::Shadow(rects) => {
+                if clip_mode == BoxShadowClipMode::Inset {
+                    self.fill_box_shadow_rect(clip_id,
+                                              box_bounds,
+                                              bs_rect,
+                                              clip_region,
+                                              color,
+                                              border_radius,
+                                              clip_mode);
+                }
+
                 let inverted = match clip_mode {
                     BoxShadowClipMode::Outset | BoxShadowClipMode::None => 0.0,
                     BoxShadowClipMode::Inset => 1.0,
                 };
 
                 // Outset box shadows with border radius
                 // need a clip out of the center box.
                 let extra_clip_mode = match clip_mode {
@@ -1071,17 +1096,16 @@ impl FrameBuilder {
         profile_scope!("build_render_task");
 
         let mut next_z = 0;
         let mut next_task_index = RenderTaskIndex(0);
 
         let mut sc_stack = Vec::new();
         let mut current_task = RenderTask::new_alpha_batch(next_task_index,
                                                            DeviceIntPoint::zero(),
-                                                           false,
                                                            RenderTaskLocation::Fixed);
         next_task_index.0 += 1;
         let mut alpha_task_stack = Vec::new();
 
         for cmd in &self.cmds {
             match *cmd {
                 PrimitiveRunCmd::PushStackingContext(stacking_context_index) => {
                     let stacking_context = &self.stacking_context_store[stacking_context_index.0];
@@ -1093,28 +1117,26 @@ impl FrameBuilder {
 
                     let stacking_context_rect = &stacking_context.bounding_rect;
                     let composite_count = stacking_context.composite_ops.count();
 
                     if composite_count == 0 && stacking_context.should_isolate {
                         let location = RenderTaskLocation::Dynamic(None, stacking_context_rect.size);
                         let new_task = RenderTask::new_alpha_batch(next_task_index,
                                                                    stacking_context_rect.origin,
-                                                                   stacking_context.should_isolate,
                                                                    location);
                         next_task_index.0 += 1;
                         let prev_task = mem::replace(&mut current_task, new_task);
                         alpha_task_stack.push(prev_task);
                     }
 
                     for _ in 0..composite_count {
                         let location = RenderTaskLocation::Dynamic(None, stacking_context_rect.size);
                         let new_task = RenderTask::new_alpha_batch(next_task_index,
                                                                    stacking_context_rect.origin,
-                                                                   stacking_context.should_isolate,
                                                                    location);
                         next_task_index.0 += 1;
                         let prev_task = mem::replace(&mut current_task, new_task);
                         alpha_task_stack.push(prev_task);
                     }
                 }
                 PrimitiveRunCmd::PopStackingContext => {
                     let stacking_context_index = sc_stack.pop().unwrap();
--- a/gfx/webrender/src/gpu_store.rs
+++ b/gfx/webrender/src/gpu_store.rs
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use device::TextureFilter;
 use std::marker::PhantomData;
 use std::mem;
 use std::ops::Add;
+use util::recycle_vec;
 use webrender_traits::ImageFormat;
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
 pub struct GpuStoreAddress(pub i32);
 
 
 impl Add<i32> for GpuStoreAddress {
     type Output = GpuStoreAddress;
@@ -72,16 +73,23 @@ impl<T: Clone + Default, L: GpuStoreLayo
     pub fn new() -> GpuStore<T, L> {
         GpuStore {
             data: Vec::new(),
             layout: PhantomData,
             //free_list: Vec::new(),
         }
     }
 
+    pub fn recycle(self) -> Self {
+        GpuStore {
+            data: recycle_vec(self.data),
+            layout: PhantomData,
+        }
+    }
+
     pub fn push<E>(&mut self, data: E) -> GpuStoreAddress where T: From<E> {
         let address = GpuStoreAddress(self.data.len() as i32);
         self.data.push(T::from(data));
         address
     }
 
     // TODO(gw): Change this to do incremental updates, which means
     // there is no need to copy all this data during every scroll!
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -280,16 +280,17 @@ pub enum TextureUpdateOp {
         stride: Option<u32>,
         offset: u32,
     },
     UpdateForExternalBuffer {
         rect: DeviceUintRect,
         id: ExternalImageId,
         channel_index: u8,
         stride: Option<u32>,
+        offset: u32,
     },
     Grow {
         width: u32,
         height: u32,
         format: ImageFormat,
         filter: TextureFilter,
         mode: RenderTargetMode,
     },
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -7,17 +7,17 @@ use euclid::{Size2D};
 use gpu_store::GpuStoreAddress;
 use internal_types::{SourceTexture, PackedTexel};
 use mask_cache::{ClipMode, ClipSource, MaskCacheInfo};
 use renderer::{VertexDataStore, GradientDataStore};
 use render_task::{RenderTask, RenderTaskLocation};
 use resource_cache::{CacheItem, ImageProperties, ResourceCache};
 use std::mem;
 use std::usize;
-use util::TransformedRect;
+use util::{TransformedRect, recycle_vec};
 use webrender_traits::{AuxiliaryLists, ColorF, ImageKey, ImageRendering, YuvColorSpace};
 use webrender_traits::{ClipRegion, ComplexClipRegion, ItemRange, GlyphKey};
 use webrender_traits::{FontKey, FontRenderMode, WebGLContextId};
 use webrender_traits::{device_length, DeviceIntRect, DeviceIntSize};
 use webrender_traits::{DeviceRect, DevicePoint, DeviceSize};
 use webrender_traits::{LayerRect, LayerSize, LayerPoint, LayoutPoint};
 use webrender_traits::{LayerToWorldTransform, GlyphInstance, GlyphOptions};
 use webrender_traits::{ExtendMode, GradientStop, TileOffset};
@@ -595,24 +595,45 @@ impl PrimitiveStore {
             cpu_metadata: Vec::new(),
             cpu_bounding_rects: Vec::new(),
             cpu_text_runs: Vec::new(),
             cpu_images: Vec::new(),
             cpu_yuv_images: Vec::new(),
             cpu_gradients: Vec::new(),
             cpu_radial_gradients: Vec::new(),
             cpu_borders: Vec::new(),
+            prims_to_resolve: Vec::new(),
             gpu_geometry: VertexDataStore::new(),
             gpu_data16: VertexDataStore::new(),
             gpu_data32: VertexDataStore::new(),
             gpu_data64: VertexDataStore::new(),
             gpu_data128: VertexDataStore::new(),
             gpu_gradient_data: GradientDataStore::new(),
             gpu_resource_rects: VertexDataStore::new(),
-            prims_to_resolve: Vec::new(),
+        }
+    }
+
+    pub fn recycle(self) -> Self {
+        PrimitiveStore {
+            cpu_metadata: recycle_vec(self.cpu_metadata),
+            cpu_bounding_rects: recycle_vec(self.cpu_bounding_rects),
+            cpu_text_runs: recycle_vec(self.cpu_text_runs),
+            cpu_images: recycle_vec(self.cpu_images),
+            cpu_yuv_images: recycle_vec(self.cpu_yuv_images),
+            cpu_gradients: recycle_vec(self.cpu_gradients),
+            cpu_radial_gradients: recycle_vec(self.cpu_radial_gradients),
+            cpu_borders: recycle_vec(self.cpu_borders),
+            prims_to_resolve: recycle_vec(self.prims_to_resolve),
+            gpu_geometry: self.gpu_geometry.recycle(),
+            gpu_data16: self.gpu_data16.recycle(),
+            gpu_data32: self.gpu_data32.recycle(),
+            gpu_data64: self.gpu_data64.recycle(),
+            gpu_data128: self.gpu_data128.recycle(),
+            gpu_gradient_data: self.gpu_gradient_data.recycle(),
+            gpu_resource_rects: self.gpu_resource_rects.recycle(),
         }
     }
 
     pub fn populate_clip_data(data: &mut [GpuBlock32], clip: ClipData) {
         data[0] = GpuBlock32::from(clip.rect);
         data[1] = GpuBlock32::from(clip.top_left);
         data[2] = GpuBlock32::from(clip.top_right);
         data[3] = GpuBlock32::from(clip.bottom_left);
--- a/gfx/webrender/src/profiler.rs
+++ b/gfx/webrender/src/profiler.rs
@@ -153,16 +153,20 @@ impl TimeProfileCounter {
         let t0 = precise_time_ns();
         let val = callback();
         let t1 = precise_time_ns();
         let ns = t1 - t0;
         self.nanoseconds += ns;
         val
     }
 
+    pub fn inc(&mut self, ns: u64) {
+        self.nanoseconds += ns;
+    }
+
     pub fn get(&self) -> u64 {
         self.nanoseconds
     }
 }
 
 impl ProfileCounter for TimeProfileCounter {
     fn description(&self) -> &'static str {
         self.description
@@ -296,59 +300,61 @@ pub struct BackendProfileCounters {
 pub struct ResourceProfileCounters {
     pub font_templates: ResourceProfileCounter,
     pub image_templates: ResourceProfileCounter,
     pub texture_cache: TextureCacheProfileCounters,
 }
 
 #[derive(Clone)]
 pub struct IpcProfileCounters {
-    pub serialize_time: TimeProfileCounter,
-    pub deserialize_time: TimeProfileCounter,
+    pub build_time: TimeProfileCounter,
+    pub consume_time: TimeProfileCounter,
     pub send_time: TimeProfileCounter,
     pub total_time: TimeProfileCounter,
-    pub display_len: IntProfileCounter,
-    pub aux_len: IntProfileCounter,
+    pub display_lists: ResourceProfileCounter,
 }
 
 impl IpcProfileCounters {
-    pub fn set(&mut self, serial_start: u64, serial_end: u64, 
-                              deserial_start: u64, deserial_end: u64,
-                              display_len: usize, aux_len: usize) {
-        self.serialize_time.set(serial_end - serial_start);
-        self.deserialize_time.set(deserial_end - deserial_start);
-        self.send_time.set(deserial_start - serial_end);
-        self.total_time.set(deserial_end - serial_start);
-        self.display_len.set(display_len);
-        self.aux_len.set(aux_len);
+    pub fn set(&mut self, build_start: u64, build_end: u64, 
+                              consume_start: u64, consume_end: u64,
+                              display_len: usize) {
+        self.build_time.inc(build_end - build_start);
+        self.consume_time.inc(consume_end - consume_start);
+        self.send_time.inc(consume_start - build_end);
+        self.total_time.inc(consume_end - build_start);
+        self.display_lists.inc(display_len);
     }
 }
 
 impl BackendProfileCounters {
     pub fn new() -> BackendProfileCounters {
         BackendProfileCounters {
             total_time: TimeProfileCounter::new("Backend CPU Time", false),
             resources: ResourceProfileCounters {
                 font_templates: ResourceProfileCounter::new("Font Templates"),
                 image_templates: ResourceProfileCounter::new("Image Templates"),
                 texture_cache: TextureCacheProfileCounters::new(),
             },
             ipc: IpcProfileCounters {
-                serialize_time: TimeProfileCounter::new("IPC Serialize Time", false),
-                deserialize_time: TimeProfileCounter::new("IPC Deserialize Time", false),
-                send_time: TimeProfileCounter::new("IPC Send Time", false),
-                total_time: TimeProfileCounter::new("IPC Time", false),
-                display_len: IntProfileCounter::new("IPC Display List Len"),
-                aux_len: IntProfileCounter::new("IPC Aux List Len"),
+                build_time: TimeProfileCounter::new("Display List Build Time", false),
+                consume_time: TimeProfileCounter::new("Display List Consume Time", false),
+                send_time: TimeProfileCounter::new("Display List Send Time", false),
+                total_time: TimeProfileCounter::new("Total Display List Time", false),
+                display_lists: ResourceProfileCounter::new("Display Lists Sent"),
             }
         }
     }
 
     pub fn reset(&mut self) {
         self.total_time.reset();
+        self.ipc.total_time.reset();
+        self.ipc.build_time.reset();
+        self.ipc.consume_time.reset();
+        self.ipc.send_time.reset();
+        self.ipc.display_lists.reset();
     }
 }
 
 pub struct RendererProfileCounters {
     pub frame_counter: IntProfileCounter,
     pub frame_time: AverageTimeProfileCounter,
     pub draw_calls: IntProfileCounter,
     pub vertices: IntProfileCounter,
@@ -725,22 +731,21 @@ impl Profiler {
         self.draw_counters(&[
             &backend_profile.resources.texture_cache.pages_a8,
             &backend_profile.resources.texture_cache.pages_rgb8,
             &backend_profile.resources.texture_cache.pages_rgba8,
             &backend_profile.resources.texture_cache.pages_rg8,
         ], debug_renderer, true);
 
         self.draw_counters(&[
-            &backend_profile.ipc.serialize_time,
+            &backend_profile.ipc.build_time,
             &backend_profile.ipc.send_time,
-            &backend_profile.ipc.deserialize_time,
+            &backend_profile.ipc.consume_time,
             &backend_profile.ipc.total_time,
-            &backend_profile.ipc.display_len,
-            &backend_profile.ipc.aux_len,
+            &backend_profile.ipc.display_lists,
         ], debug_renderer, true);
 
         self.draw_counters(&[
             &renderer_profile.draw_calls,
             &renderer_profile.vertices,
         ], debug_renderer, true);
 
         self.draw_counters(&[
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -8,16 +8,17 @@ use internal_types::{FontTemplate, Sourc
 use profiler::{BackendProfileCounters, TextureCacheProfileCounters};
 use record::ApiRecordingReceiver;
 use resource_cache::ResourceCache;
 use scene::Scene;
 use std::collections::HashMap;
 use std::sync::{Arc, Mutex};
 use std::sync::mpsc::Sender;
 use texture_cache::TextureCache;
+use time::precise_time_ns;
 use thread_profiler::register_thread_with_profiler;
 use threadpool::ThreadPool;
 use webgl_types::{GLContextHandleWrapper, GLContextWrapper};
 use webrender_traits::{DeviceIntPoint, DeviceUintPoint, DeviceUintRect, DeviceUintSize, LayerPoint};
 use webrender_traits::{ApiMsg, AuxiliaryLists, BuiltDisplayList, IdNamespace, ImageData};
 use webrender_traits::{PipelineId, RenderNotifier, RenderDispatcher, WebGLCommand, WebGLContextId};
 use webrender_traits::channel::{PayloadSenderHelperMethods, PayloadReceiverHelperMethods, PayloadReceiver, PayloadSender, MsgReceiver};
 use webrender_traits::{BlobImageRenderer, VRCompositorCommand, VRCompositorHandler};
@@ -208,27 +209,40 @@ impl RenderBackend {
                             let auxiliary_lists =
                                 AuxiliaryLists::from_data(auxiliary_data.auxiliary_lists_data,
                                                           auxiliary_lists_descriptor);
 
                             if !preserve_frame_state {
                                 self.discard_frame_state_for_pipeline(pipeline_id);
                             }
                             
-                            let counters = &mut profile_counters.ipc;
+                            let display_list_len = built_display_list.data().len();
+                            let aux_list_len = auxiliary_lists.data().len();
+                            let (builder_start_time, builder_finish_time) = built_display_list.times();
+
+                            let display_list_received_time = precise_time_ns();
+                            
                             profile_counters.total_time.profile(|| {
                                 self.scene.set_display_list(pipeline_id,
                                                             epoch,
                                                             built_display_list,
                                                             background_color,
                                                             viewport_size,
-                                                            auxiliary_lists,
-                                                            counters);
+                                                            auxiliary_lists);
                                 self.build_scene();
                             });
+
+                            // Note: this isn't quite right as auxiliary values will be 
+                            // pulled out somewhere in the prim_store, but aux values are
+                            // really simple and cheap to access, so it's not a big deal.
+                            let display_list_consumed_time = precise_time_ns();
+
+                            profile_counters.ipc.set(builder_start_time, builder_finish_time, 
+                                                     display_list_received_time, display_list_consumed_time,
+                                                     display_list_len + aux_list_len);
                         }
                         ApiMsg::SetRootPipeline(pipeline_id) => {
                             profile_scope!("SetRootPipeline");
                             self.scene.set_root_pipeline_id(pipeline_id);
 
                             if self.scene.display_lists.get(&pipeline_id).is_none() {
                                 continue;
                             }
@@ -305,16 +319,19 @@ impl RenderBackend {
                                     Some(Box::new(WebRenderGLDispatcher {
                                         dispatcher: Arc::clone(&self.main_thread_dispatcher)
                                     }))
                                 } else {
                                     None
                                 };
 
                                 let result = wrapper.new_context(size, attributes, dispatcher);
+                                // Creating a new GLContext may make the current bound context_id dirty.
+                                // Clear it to ensure that  make_current() is called in subsequent commands.
+                                self.current_bound_webgl_context_id = None;
 
                                 match result {
                                     Ok(ctx) => {
                                         let id = WebGLContextId(self.next_webgl_id);
                                         self.next_webgl_id += 1;
 
                                         let (real_size, texture_id, limits) = ctx.get_info();
 
@@ -331,17 +348,20 @@ impl RenderBackend {
                                     }
                                 }
                             } else {
                                 tx.send(Err("Not implemented yet".to_owned())).unwrap();
                             }
                         }
                         ApiMsg::ResizeWebGLContext(context_id, size) => {
                             let ctx = self.webgl_contexts.get_mut(&context_id).unwrap();
-                            ctx.make_current();
+                            if Some(context_id) != self.current_bound_webgl_context_id {
+                                ctx.make_current();
+                                self.current_bound_webgl_context_id = Some(context_id);
+                            }
                             match ctx.resize(&size) {
                                 Ok(_) => {
                                     // Update webgl texture size. Texture id may change too.
                                     let (real_size, texture_id, _) = ctx.get_info();
                                     self.resource_cache
                                         .update_webgl_texture(context_id, SourceTexture::WebGL(texture_id),
                                                               real_size);
                                 },
@@ -349,22 +369,28 @@ impl RenderBackend {
                                     error!("Error resizing WebGLContext: {}", msg);
                                 }
                             }
                         }
                         ApiMsg::WebGLCommand(context_id, command) => {
                             // TODO: Buffer the commands and only apply them here if they need to
                             // be synchronous.
                             let ctx = &self.webgl_contexts[&context_id];
-                            ctx.make_current();
+                            if Some(context_id) != self.current_bound_webgl_context_id {
+                                ctx.make_current();
+                                self.current_bound_webgl_context_id = Some(context_id);
+                            }
                             ctx.apply_command(command);
-                            self.current_bound_webgl_context_id = Some(context_id);
                         },
 
                         ApiMsg::VRCompositorCommand(context_id, command) => {
+                            if Some(context_id) != self.current_bound_webgl_context_id {
+                                self.webgl_contexts[&context_id].make_current();
+                                self.current_bound_webgl_context_id = Some(context_id);
+                            }
                             self.handle_vr_compositor_command(context_id, command);
                         }
                         ApiMsg::GenerateFrame(property_bindings) => {
                             profile_scope!("GenerateFrame");
 
                             // Ideally, when there are property bindings present,
                             // we won't need to rebuild the entire frame here.
                             // However, to avoid conflicts with the ongoing work to
@@ -502,17 +528,20 @@ impl RenderBackend {
         let mut notifier = self.notifier.lock();
         notifier.as_mut().unwrap().as_mut().unwrap().new_scroll_frame_ready(composite_needed);
     }
 
     fn handle_vr_compositor_command(&mut self, ctx_id: WebGLContextId, cmd: VRCompositorCommand) {
         let texture = match cmd {
             VRCompositorCommand::SubmitFrame(..) => {
                     match self.resource_cache.get_webgl_texture(&ctx_id).texture_id {
-                        SourceTexture::WebGL(texture_id) => Some(texture_id),
+                        SourceTexture::WebGL(texture_id) => {
+                            let size = self.resource_cache.get_webgl_texture_size(&ctx_id);
+                            Some((texture_id, size))
+                        },
                         _=> None
                     }
             },
             _ => None
         };
         let mut handler = self.vr_compositor_handler.lock();
         handler.as_mut().unwrap().as_mut().unwrap().handle(cmd, texture);
     }
--- a/gfx/webrender/src/render_task.rs
+++ b/gfx/webrender/src/render_task.rs
@@ -56,17 +56,16 @@ pub enum AlphaRenderItem {
     Composite(StackingContextIndex, RenderTaskId, RenderTaskId, MixBlendMode, i32),
     HardwareComposite(StackingContextIndex, RenderTaskId, HardwareCompositeOp, i32),
 }
 
 #[derive(Debug, Clone)]
 pub struct AlphaRenderTask {
     screen_origin: DeviceIntPoint,
     pub items: Vec<AlphaRenderItem>,
-    pub isolate_clear: bool,
 }
 
 #[derive(Debug, Copy, Clone)]
 #[repr(C)]
 pub enum MaskSegment {
     // This must match the SEGMENT_ values in clip_shared.glsl!
     All = 0,
     TopLeftCorner,
@@ -139,26 +138,24 @@ pub struct RenderTask {
     pub location: RenderTaskLocation,
     pub children: Vec<RenderTask>,
     pub kind: RenderTaskKind,
 }
 
 impl RenderTask {
     pub fn new_alpha_batch(task_index: RenderTaskIndex,
                            screen_origin: DeviceIntPoint,
-                           isolate_clear: bool,
                            location: RenderTaskLocation) -> RenderTask {
         RenderTask {
             id: RenderTaskId::Static(task_index),
             children: Vec::new(),
             location: location,
             kind: RenderTaskKind::Alpha(AlphaRenderTask {
                 screen_origin: screen_origin,
                 items: Vec::new(),
-                isolate_clear: isolate_clear,
             }),
         }
     }
 
     pub fn new_prim_cache(key: PrimitiveCacheKey,
                           size: DeviceIntSize,
                           prim_index: PrimitiveIndex) -> RenderTask {
         RenderTask {
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -1271,31 +1271,32 @@ impl Renderer {
                     TextureUpdateOp::Update { page_pos_x, page_pos_y, width, height, data, stride, offset } => {
                         let texture_id = self.cache_texture_id_map[update.id.0];
                         self.device.update_texture(texture_id,
                                                    page_pos_x,
                                                    page_pos_y,
                                                    width, height, stride,
                                                    &data[offset as usize..]);
                     }
-                    TextureUpdateOp::UpdateForExternalBuffer { rect, id, channel_index, stride } => {
+                    TextureUpdateOp::UpdateForExternalBuffer { rect, id, channel_index, stride, offset } => {
                         let handler = self.external_image_handler
                                           .as_mut()
                                           .expect("Found external image, but no handler set!");
                         let device = &mut self.device;
                         let cached_id = self.cache_texture_id_map[update.id.0];
 
                         match handler.lock(id, channel_index).source {
                             ExternalImageSource::RawData(data) => {
                                 device.update_texture(cached_id,
                                                       rect.origin.x,
                                                       rect.origin.y,
                                                       rect.size.width,
                                                       rect.size.height,
-                                                      stride, data);
+                                                      stride,
+                                                      &data[offset as usize..]);
                             }
                             _ => panic!("No external buffer found"),
                         };
                         handler.unlock(id, channel_index);
                     }
                     TextureUpdateOp::Free => {
                         let texture_id = self.cache_texture_id_map[update.id.0];
                         self.device.deinit_texture(texture_id);
@@ -1510,23 +1511,16 @@ impl Renderer {
                                                   Some(1.0),
                                                   target.used_rect());
                 }
                 None => {
                     self.device.clear_target(clear_color, Some(1.0));
                 }
             }
 
-            let isolate_clear_color = Some([0.0, 0.0, 0.0, 0.0]);
-            for isolate_clear in &target.isolate_clears {
-                self.device.clear_target_rect(isolate_clear_color,
-                                              None,
-                                              *isolate_clear);
-            }
-
             self.device.disable_depth_write();
         }
 
         // Draw any blurs for this target.
         // Blurs are rendered as a standard 2-pass
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
@@ -1843,17 +1837,17 @@ impl Renderer {
                     projection = Matrix4D::ortho(0.0,
                                                  size.width as f32,
                                                  size.height as f32,
                                                  0.0,
                                                  ORTHO_NEAR_PLANE,
                                                  ORTHO_FAR_PLANE)
                 } else {
                     size = &frame.cache_size;
-                    clear_color = Some([1.0, 1.0, 1.0, 0.0]);
+                    clear_color = Some([0.0, 0.0, 0.0, 0.0]);
                     projection = Matrix4D::ortho(0.0,
                                                  size.width as f32,
                                                  0.0,
                                                  size.height as f32,
                                                  ORTHO_NEAR_PLANE,
                                                  ORTHO_FAR_PLANE);
                 }
 
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -549,16 +549,20 @@ impl ResourceCache {
         let webgl_texture = &self.webgl_textures[context_id];
         CacheItem {
             texture_id: webgl_texture.id,
             uv0: DevicePoint::new(0.0, webgl_texture.size.height as f32),
             uv1: DevicePoint::new(webgl_texture.size.width as f32, 0.0),
         }
     }
 
+    pub fn get_webgl_texture_size(&self, context_id: &WebGLContextId) -> DeviceIntSize {
+        self.webgl_textures[context_id].size
+    }
+
     pub fn expire_old_resources(&mut self, frame_id: FrameId) {
         self.cached_images.expire_old_resources(&mut self.texture_cache, frame_id);
 
         let cached_glyphs = self.cached_glyphs.as_mut().unwrap();
         cached_glyphs.expire_old_resources(&mut self.texture_cache, frame_id);
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
--- a/gfx/webrender/src/scene.rs
+++ b/gfx/webrender/src/scene.rs
@@ -1,18 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use fnv::FnvHasher;
-use profiler::IpcProfileCounters;
 use std::collections::HashMap;
 use std::hash::BuildHasherDefault;
 use tiling::AuxiliaryListsMap;
-use time::precise_time_ns;
 use webrender_traits::{AuxiliaryLists, BuiltDisplayList, PipelineId, Epoch, ColorF};
 use webrender_traits::{DisplayItem, DynamicProperties, LayerSize, LayoutTransform};
 use webrender_traits::{PropertyBinding, PropertyBindingId};
 
 /// Stores a map of the animated property bindings for the current display list. These
 /// can be used to animate the transform and/or opacity of a display list without
 /// re-submitting the display list itself.
 pub struct SceneProperties {
@@ -116,34 +114,21 @@ impl Scene {
     }
 
     pub fn set_display_list(&mut self,
                             pipeline_id: PipelineId,
                             epoch: Epoch,
                             built_display_list: BuiltDisplayList,
                             background_color: Option<ColorF>,
                             viewport_size: LayerSize,
-                            auxiliary_lists: AuxiliaryLists,
-                            profile_counters: &mut IpcProfileCounters) {
-
-        let display_list_len = built_display_list.data().len();
-        let aux_list_len = auxiliary_lists.data().len();
-        let (serial_start_time, serial_end_time) = built_display_list.serialization_times();
-
-        let deserial_start_time = precise_time_ns();
+                            auxiliary_lists: AuxiliaryLists) {
 
         self.pipeline_auxiliary_lists.insert(pipeline_id, auxiliary_lists);
         self.display_lists.insert(pipeline_id, built_display_list.into_display_items());
-
-        let deserial_end_time = precise_time_ns();
-
-        profile_counters.set(serial_start_time, serial_end_time, 
-                             deserial_start_time, deserial_end_time,
-                             display_list_len, aux_list_len);
-
+        
         let new_pipeline = ScenePipeline {
             pipeline_id: pipeline_id,
             epoch: epoch,
             viewport_size: viewport_size,
             background_color: background_color,
         };
 
         self.pipeline_map.insert(pipeline_id, new_pipeline);
--- a/gfx/webrender/src/texture_cache.rs
+++ b/gfx/webrender/src/texture_cache.rs
@@ -798,16 +798,17 @@ impl TextureCache {
                             ExternalImageType::ExternalBuffer => {
                                 let update_op = TextureUpdate {
                                     id: result.item.texture_id,
                                     op: TextureUpdateOp::UpdateForExternalBuffer {
                                         rect: result.item.allocated_rect,
                                         id: ext_image.id,
                                         channel_index: ext_image.channel_index,
                                         stride: stride,
+                                        offset: descriptor.offset,
                                     },
                                 };
 
                                 self.pending_updates.push(update_op);
                             }
                         }
                     }
                     ImageData::Blob(..) => {
--- a/gfx/webrender/src/tiling.rs
+++ b/gfx/webrender/src/tiling.rs
@@ -484,19 +484,34 @@ impl AlphaRenderItem {
                             // Select a generic primitive shader that can blit the
                             // results of the cached text blur to the framebuffer,
                             // applying tile clipping etc.
                             AlphaBatchKind::CacheImage
                         };
                         let key = AlphaBatchKey::new(batch_kind, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
 
+                        let cache_task_index = match prim_metadata.render_task {
+                            Some(ref task) => {
+                                let cache_task_id = task.id;
+                                render_tasks.get_task_index(&cache_task_id,
+                                                            child_pass_index).0 as i32
+                            }
+                            None => 0,
+                        };
+
                         for glyph_index in 0..prim_metadata.gpu_data_count {
+                            let user_data0 = match batch_kind {
+                                AlphaBatchKind::TextRun => text_cpu.resource_address.0 + glyph_index,
+                                AlphaBatchKind::CacheImage => cache_task_index,
+                                _ => unreachable!(),
+                            };
+
                             batch.add_instance(base_instance.build(prim_metadata.gpu_data_address.0 + glyph_index,
-                                                                   text_cpu.resource_address.0 + glyph_index,
+                                                                   user_data0,
                                                                    0));
                         }
                     }
                     PrimitiveKind::AlignedGradient => {
                         let key = AlphaBatchKey::new(AlphaBatchKind::AlignedGradient, flags, blend_mode, textures);
                         let batch = batch_list.get_suitable_batch(&key, item_bounding_rect);
                         for part_index in 0..(prim_metadata.gpu_data_count - 1) {
                             batch.add_instance(base_instance.build(prim_metadata.gpu_data_address.0 + part_index, 0, 0));
@@ -800,17 +815,16 @@ pub struct ColorRenderTarget {
     //           cache changes land, this restriction will
     //           be removed anyway.
     pub text_run_cache_prims: Vec<PrimitiveInstance>,
     pub text_run_textures: BatchTextures,
     // List of blur operations to apply for this render target.
     pub vertical_blurs: Vec<BlurCommand>,
     pub horizontal_blurs: Vec<BlurCommand>,
     pub readbacks: Vec<DeviceIntRect>,
-    pub isolate_clears: Vec<DeviceIntRect>,
     allocator: TextureAllocator,
 }
 
 impl RenderTarget for ColorRenderTarget {
     fn allocate(&mut self, size: DeviceUintSize) -> Option<DeviceUintPoint> {
         self.allocator.allocate(&size)
     }
 
@@ -818,17 +832,16 @@ impl RenderTarget for ColorRenderTarget 
         ColorRenderTarget {
             alpha_batcher: AlphaBatcher::new(),
             box_shadow_cache_prims: Vec::new(),
             text_run_cache_prims: Vec::new(),
             text_run_textures: BatchTextures::no_texture(),
             vertical_blurs: Vec::new(),
             horizontal_blurs: Vec::new(),
             readbacks: Vec::new(),
-            isolate_clears: Vec::new(),
             allocator: TextureAllocator::new(size),
         }
     }
 
     fn used_rect(&self) -> DeviceIntRect {
         self.allocator.used_rect
     }
 
@@ -847,26 +860,16 @@ impl RenderTarget for ColorRenderTarget 
                 render_tasks: &RenderTaskCollection,
                 pass_index: RenderPassIndex) {
         match task.kind {
             RenderTaskKind::Alpha(info) => {
                 self.alpha_batcher.add_task(AlphaBatchTask {
                     task_id: task.id,
                     items: info.items,
                 });
-
-                if info.isolate_clear {
-                    let location = match task.location {
-                        RenderTaskLocation::Dynamic(origin, size) => {
-                            DeviceIntRect::new(origin.unwrap().0, size)
-                        }
-                        RenderTaskLocation::Fixed => panic!()
-                    };
-                    self.isolate_clears.push(location);
-                }
             }
             RenderTaskKind::VerticalBlur(_, prim_index) => {
                 // Find the child render task that we are applying
                 // a vertical blur on.
                 // TODO(gw): Consider a simpler way for render tasks to find
                 //           their child tasks than having to construct the
                 //           correct id here.
                 let child_pass_index = RenderPassIndex(pass_index.0 - 1);
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -311,8 +311,23 @@ fn extract_inner_rect_impl<U>(rect: &Typ
 
     if xl <= xr && yt <= yb {
         Some(TypedRect::new(TypedPoint2D::new(rect.origin.x + xl, rect.origin.y + yt),
              TypedSize2D::new(xr-xl, yb-yt)))
     } else {
         None
     }
 }
+
+/// Consumes the old vector and returns a new one that may reuse the old vector's allocated
+/// memory.
+pub fn recycle_vec<T>(mut old_vec: Vec<T>) -> Vec<T> {
+    if old_vec.capacity() > 2 * old_vec.len() {
+        // Avoid reusing the buffer if it is a lot larger than it needs to be. This prevents
+        // a frame with exceptionally large allocations to cause subsequent frames to retain
+        // more memory than they need.
+        return Vec::with_capacity(old_vec.len());
+    }
+
+    old_vec.clear();
+
+    return old_vec;
+}
--- a/gfx/webrender_traits/Cargo.toml
+++ b/gfx/webrender_traits/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "webrender_traits"
-version = "0.35.0"
+version = "0.36.0"
 authors = ["Glenn Watson <gw@intuitionlibrary.com>"]
 license = "MPL-2.0"
 repository = "https://github.com/servo/webrender"
 
 [features]
 nightly = ["euclid/unstable", "serde/unstable"]
 ipc = ["ipc-channel"]
 webgl = ["offscreen_gl_context"]
--- a/gfx/webrender_traits/src/api.rs
+++ b/gfx/webrender_traits/src/api.rs
@@ -575,19 +575,19 @@ pub type VRCompositorId = u64;
 pub enum VRCompositorCommand {
     Create(VRCompositorId),
     SyncPoses(VRCompositorId, f64, f64, MsgSender<Result<Vec<u8>,()>>),
     SubmitFrame(VRCompositorId, [f32; 4], [f32; 4]),
     Release(VRCompositorId)
 }
 
 // Trait object that handles WebVR commands.
-// Receives the texture_id associated to the WebGLContext.
+// Receives the texture id and size associated to the WebGLContext.
 pub trait VRCompositorHandler: Send {
-    fn handle(&mut self, command: VRCompositorCommand, texture_id: Option<u32>);
+    fn handle(&mut self, command: VRCompositorCommand, texture: Option<(u32, DeviceIntSize)>);
 }
 
 pub trait RenderNotifier: Send {
     fn new_frame_ready(&mut self);
     fn new_scroll_frame_ready(&mut self, composite_needed: bool);
     fn external_event(&mut self, _evt: ExternalEvent) { unimplemented!() }
     fn shut_down(&mut self) {}
 }
--- a/gfx/webrender_traits/src/display_item.rs
+++ b/gfx/webrender_traits/src/display_item.rs
@@ -243,17 +243,16 @@ pub struct RadialGradientDisplayItem {
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct PushStackingContextDisplayItem {
     pub stacking_context: StackingContext,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct StackingContext {
     pub scroll_policy: ScrollPolicy,
-    pub z_index: i32,
     pub transform: Option<PropertyBinding<LayoutTransform>>,
     pub transform_style: TransformStyle,
     pub perspective: Option<LayoutTransform>,
     pub mix_blend_mode: MixBlendMode,
     pub filters: ItemRange,
 }
 
 #[repr(u32)]
@@ -362,27 +361,25 @@ pub struct ComplexClipRegion {
     /// The boundaries of the rectangle.
     pub rect: LayoutRect,
     /// Border radii of this rectangle.
     pub radii: BorderRadius,
 }
 
 impl StackingContext {
     pub fn new(scroll_policy: ScrollPolicy,
-               z_index: i32,
                transform: Option<PropertyBinding<LayoutTransform>>,
                transform_style: TransformStyle,
                perspective: Option<LayoutTransform>,
                mix_blend_mode: MixBlendMode,
                filters: Vec<FilterOp>,
                auxiliary_lists_builder: &mut AuxiliaryListsBuilder)
                -> StackingContext {
         StackingContext {
             scroll_policy: scroll_policy,
-            z_index: z_index,
             transform: transform,
             transform_style: transform_style,
             perspective: perspective,
             mix_blend_mode: mix_blend_mode,
             filters: auxiliary_lists_builder.add_filters(&filters),
         }
     }
 }
--- a/gfx/webrender_traits/src/display_list.rs
+++ b/gfx/webrender_traits/src/display_list.rs
@@ -48,19 +48,19 @@ pub struct BuiltDisplayList {
 /// A display list consists of some number of display list items, followed by a number of display
 /// items.
 #[repr(C)]
 #[derive(Copy, Clone, Deserialize, Serialize)]
 pub struct BuiltDisplayListDescriptor {
     /// The size in bytes of the display list items in this display list.
     display_list_items_size: usize,
     /// The first IPC time stamp: before any work has been done
-    serialization_start_time: u64,
+    builder_start_time: u64,
     /// The second IPC time stamp: after serialization
-    serialization_end_time: u64,
+    builder_finish_time: u64,
 }
 
 impl BuiltDisplayListDescriptor {
     pub fn size(&self) -> usize {
         self.display_list_items_size
     }
 }
 
@@ -91,40 +91,43 @@ impl BuiltDisplayList {
     }
 
     pub fn into_display_items(self) -> Vec<DisplayItem> {
         unsafe {
             convert_vec_blob_to_pod(self.data)
         }
     }
 
-    pub fn serialization_times(&self) -> (u64, u64) {
-      (self.descriptor.serialization_start_time, self.descriptor.serialization_end_time)
+    pub fn times(&self) -> (u64, u64) {
+      (self.descriptor.builder_start_time, self.descriptor.builder_finish_time)
     }
 }
 
 #[derive(Clone)]
 pub struct DisplayListBuilder {
     pub list: Vec<DisplayItem>,
     auxiliary_lists_builder: AuxiliaryListsBuilder,
     pub pipeline_id: PipelineId,
     clip_stack: Vec<ClipId>,
     next_clip_id: u64,
+    builder_start_time: u64,
 }
 
 impl DisplayListBuilder {
     pub fn new(pipeline_id: PipelineId) -> DisplayListBuilder {
+        let start_time = precise_time_ns();
         DisplayListBuilder {
             list: Vec::new(),
             auxiliary_lists_builder: AuxiliaryListsBuilder::new(),
             pipeline_id: pipeline_id,
             clip_stack: vec![ClipId::root_scroll_node(pipeline_id)],
 
             // We start at 1 here, because the root scroll id is always 0.
             next_clip_id: 1,
+            builder_start_time: start_time,
         }
     }
 
     pub fn print_display_list(&mut self) {
         for item in &self.list {
             println!("{:?}", item);
         }
     }
@@ -427,26 +430,24 @@ impl DisplayListBuilder {
         });
 
         self.push_item(item, rect, clip);
     }
 
     pub fn push_stacking_context(&mut self,
                                  scroll_policy: ScrollPolicy,
                                  bounds: LayoutRect,
-                                 z_index: i32,
                                  transform: Option<PropertyBinding<LayoutTransform>>,
                                  transform_style: TransformStyle,
                                  perspective: Option<LayoutTransform>,
                                  mix_blend_mode: MixBlendMode,
                                  filters: Vec<FilterOp>) {
         let item = SpecificDisplayItem::PushStackingContext(PushStackingContextDisplayItem {
             stacking_context: StackingContext {
                 scroll_policy: scroll_policy,
-                z_index: z_index,
                 transform: transform,
                 transform_style: transform_style,
                 perspective: perspective,
                 mix_blend_mode: mix_blend_mode,
                 filters: self.auxiliary_lists_builder.add_filters(&filters),
             }
         });
 
@@ -545,29 +546,27 @@ impl DisplayListBuilder {
                            complex: Vec<ComplexClipRegion>,
                            image_mask: Option<ImageMask>)
                            -> ClipRegion {
         ClipRegion::new(rect, complex, image_mask, &mut self.auxiliary_lists_builder)
     }
 
     pub fn finalize(self) -> (PipelineId, BuiltDisplayList, AuxiliaryLists) {
         unsafe {
-            let serialization_start_time = precise_time_ns();
-
             let blob = convert_vec_pod_to_blob(self.list);
             let aux_list = self.auxiliary_lists_builder.finalize();
 
-            let serialization_end_time = precise_time_ns();
+            let end_time = precise_time_ns();
 
             (self.pipeline_id,
              BuiltDisplayList {
                  descriptor: BuiltDisplayListDescriptor {
                     display_list_items_size: blob.len(),
-                    serialization_start_time: serialization_start_time,
-                    serialization_end_time: serialization_end_time,
+                    builder_start_time: self.builder_start_time,
+                    builder_finish_time: end_time,
                  },
                  data: blob,
              },
              aux_list)
         }
     }
 }
 
--- a/gfx/webrender_traits/src/webgl.rs
+++ b/gfx/webrender_traits/src/webgl.rs
@@ -607,19 +607,20 @@ impl WebGLCommand {
             WebGLCommand::Finish(sender) =>
                 Self::finish(ctx.gl(), sender),
             WebGLCommand::Flush =>
                 ctx.gl().flush(),
             WebGLCommand::GenerateMipmap(target) =>
                 ctx.gl().generate_mipmap(target),
         }
 
-        // FIXME: Use debug_assertions once tests are run with them
-        let error = ctx.gl().get_error();
-        assert!(error == gl::NO_ERROR, "Unexpected WebGL error: 0x{:x} ({})", error, error);
+        if cfg!(debug_assertions) {
+            let error = ctx.gl().get_error();
+            assert!(error == gl::NO_ERROR, "Unexpected WebGL error: 0x{:x} ({})", error, error);
+        }
     }
 
     fn read_pixels(gl: &gl::Gl, x: i32, y: i32, width: i32, height: i32, format: u32, pixel_type: u32,
                    chan: MsgSender<Vec<u8>>) {
       let result = gl.read_pixels(x, y, width, height, format, pixel_type);
       chan.send(result).unwrap()
     }