Bug 1397831 - Update webrender to commit 6cf9cd4075efdf7467bad71b372170f626a8fce4. r=jrmuizel draft
authorRyan Hunt <rhunt@eqrion.net>
Fri, 08 Sep 2017 11:53:34 -0500
changeset 661518 4718328943014ef62e7cccc566e0fce4c8199073
parent 661321 50857982881ae7803ceb438fee90650a282f7f05
child 730610 5da9d3feb04508f63249eaf35f4ca2e355368227
push id78803
push userbmo:rhunt@eqrion.net
push dateFri, 08 Sep 2017 16:56:30 +0000
reviewersjrmuizel
bugs1397831
milestone57.0a1
Bug 1397831 - Update webrender to commit 6cf9cd4075efdf7467bad71b372170f626a8fce4. r=jrmuizel MozReview-Commit-ID: 8SHrjpb5bKs
gfx/doc/README.webrender
gfx/webrender/src/border.rs
gfx/webrender/src/clip.rs
gfx/webrender/src/clip_scroll_node.rs
gfx/webrender/src/clip_scroll_tree.rs
gfx/webrender/src/debug_server.rs
gfx/webrender/src/device.rs
gfx/webrender/src/frame.rs
gfx/webrender/src/frame_builder.rs
gfx/webrender/src/internal_types.rs
gfx/webrender/src/mask_cache.rs
gfx/webrender/src/prim_store.rs
gfx/webrender/src/print_tree.rs
gfx/webrender/src/render_backend.rs
gfx/webrender/src/renderer.rs
gfx/webrender/src/util.rs
gfx/webrender_api/src/api.rs
gfx/webrender_api/src/display_item.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: dbffdc219fa394ab1225cebc71fe5998b9337cb2
+Latest Commit: 6cf9cd4075efdf7467bad71b372170f626a8fce4
--- a/gfx/webrender/src/border.rs
+++ b/gfx/webrender/src/border.rs
@@ -223,17 +223,17 @@ impl NormalBorderHelpers for NormalBorde
 impl FrameBuilder {
     fn add_normal_border_primitive(&mut self,
                                    rect: &LayerRect,
                                    border: &NormalBorder,
                                    widths: &BorderWidths,
                                    clip_and_scroll: ClipAndScrollInfo,
                                    local_clip: &LocalClip,
                                    corner_instances: [BorderCornerInstance; 4],
-                                   extra_clips: &[ClipSource]) {
+                                   clip_sources: Vec<ClipSource>) {
         let radius = &border.radius;
         let left = &border.left;
         let right = &border.right;
         let top = &border.top;
         let bottom = &border.bottom;
 
         // These colors are used during inset/outset scaling.
         let left_color      = left.border_color(1.0, 2.0/3.0, 0.3, 0.7);
@@ -269,17 +269,17 @@ impl FrameBuilder {
                   radius.bottom_left.width,
                   radius.bottom_left.height ].into(),
             ],
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            local_clip,
-                           extra_clips,
+                           clip_sources,
                            PrimitiveContainer::Border(prim_cpu));
     }
 
     // TODO(gw): This allows us to move border types over to the
     // simplified shader model one at a time. Once all borders
     // are converted, this can be removed, along with the complex
     // border code path.
     pub fn add_normal_border(&mut self,
@@ -420,17 +420,17 @@ impl FrameBuilder {
             }
 
             self.add_normal_border_primitive(rect,
                                              border,
                                              widths,
                                              clip_and_scroll,
                                              local_clip,
                                              corner_instances,
-                                             &extra_clips);
+                                             extra_clips);
         }
     }
 }
 
 pub trait BorderSideHelpers {
     fn border_color(&self,
                     scale_factor_0: f32,
                     scale_factor_1: f32,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ComplexClipRegion, ImageMask, ImageRendering};
+use api::{BorderRadius, ComplexClipRegion, ImageMask, ImageRendering};
 use api::{LayerPoint, LayerRect, LayerToWorldTransform, LocalClip};
 use border::BorderCornerClipSource;
 use gpu_cache::GpuCache;
 use mask_cache::MaskCacheInfo;
 use resource_cache::ResourceCache;
 use std::ops::Not;
 
 #[derive(Clone, Debug)]
@@ -44,30 +44,16 @@ impl ClipRegion {
 
     pub fn create_for_clip_node_with_local_clip(local_clip: &LocalClip) -> ClipRegion {
         let complex_clips = match local_clip {
             &LocalClip::Rect(_) => Vec::new(),
             &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
         };
         ClipRegion::create_for_clip_node(*local_clip.clip_rect(), complex_clips, None)
     }
-
-    pub fn create_for_local_clip(local_clip: &LocalClip) -> ClipRegion {
-        let complex_clips = match local_clip {
-            &LocalClip::Rect(_) => Vec::new(),
-            &LocalClip::RoundedRect(_, ref region) => vec![region.clone()],
-        };
-
-        ClipRegion {
-            origin: LayerPoint::zero(),
-            main: *local_clip.clip_rect(),
-            image_mask: None,
-            complex_clips,
-        }
-    }
 }
 
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum ClipMode {
     Clip,           // Pixels inside the region are visible.
     ClipOut,        // Pixels outside the region are visible.
 }
@@ -78,27 +64,46 @@ impl Not for ClipMode {
     fn not(self) -> ClipMode {
         match self {
             ClipMode::Clip => ClipMode::ClipOut,
             ClipMode::ClipOut => ClipMode::Clip
         }
     }
 }
 
-#[derive(Clone, Debug)]
+#[derive(Debug)]
 pub enum ClipSource {
-    Complex(LayerRect, f32, ClipMode),
-    Region(ClipRegion),
+    Rectangle(LayerRect),
+    RoundedRectangle(LayerRect, BorderRadius, ClipMode),
+    Image(ImageMask),
     /// TODO(gw): This currently only handles dashed style
     /// clips, where the border style is dashed for both
     /// adjacent border edges. Expand to handle dotted style
     /// and different styles per edge.
     BorderCorner(BorderCornerClipSource),
 }
 
+impl From<ClipRegion> for ClipSources {
+    fn from(region: ClipRegion) -> ClipSources {
+        let mut clips = Vec::new();
+
+        if let Some(info) = region.image_mask {
+            clips.push(ClipSource::Image(info));
+        }
+
+        clips.push(ClipSource::Rectangle(region.main));
+
+        for complex in region.complex_clips {
+            clips.push(ClipSource::RoundedRectangle(complex.rect, complex.radii, ClipMode::Clip));
+        }
+
+        ClipSources::new(clips)
+    }
+}
+
 #[derive(Debug)]
 pub struct ClipSources {
     clips: Vec<ClipSource>,
     mask_cache_info: MaskCacheInfo,
 }
 
 impl ClipSources {
     pub fn new(clips: Vec<ClipSource>) -> ClipSources {
@@ -125,17 +130,17 @@ impl ClipSources {
 
         self.mask_cache_info
             .update(&self.clips,
                     layer_transform,
                     gpu_cache,
                     device_pixel_ratio);
 
         for clip in &self.clips {
-            if let ClipSource::Region(ClipRegion{ image_mask: Some(ref mask), .. }, ..) = *clip {
+            if let ClipSource::Image(ref mask) = *clip {
                 resource_cache.request_image(mask.image,
                                              ImageRendering::Auto,
                                              None,
                                              gpu_cache);
             }
         }
     }
 
--- a/gfx/webrender/src/clip_scroll_node.rs
+++ b/gfx/webrender/src/clip_scroll_node.rs
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ClipId, DeviceIntRect, LayerPixel, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerToWorldTransform, LayerVector2D, PipelineId};
 use api::{ScrollClamping, ScrollEventPhase, ScrollLocation, ScrollSensitivity, StickyFrameInfo};
 use api::WorldPoint;
-use clip::{ClipRegion, ClipSource, ClipSources};
+use clip::{ClipRegion, ClipSources};
 use clip_scroll_tree::TransformUpdateState;
 use geometry::ray_intersects_rect;
 use spring::{DAMPING, STIFFNESS, Spring};
 use tiling::PackedLayerIndex;
 use util::{MatrixHelpers, TransformedRectKind};
 
 #[cfg(target_os = "macos")]
 const CAN_OVERSCROLL: bool = true;
@@ -37,17 +37,17 @@ pub struct ClipInfo {
     /// frame relative coordinates (with no scroll offsets).
     pub clip_rect: LayerRect,
 }
 
 impl ClipInfo {
     pub fn new(clip_region: ClipRegion, packed_layer_index: PackedLayerIndex) -> ClipInfo {
         let clip_rect = LayerRect::new(clip_region.origin, clip_region.main.size);
         ClipInfo {
-            clip_sources: ClipSources::new(vec![ClipSource::Region(clip_region)]),
+            clip_sources: ClipSources::from(clip_region),
             packed_layer_index,
             screen_bounding_rect: None,
             clip_rect: clip_rect,
         }
     }
 }
 
 #[derive(Debug)]
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use clip_scroll_node::{ClipScrollNode, NodeType, ScrollingState};
 use internal_types::{FastHashSet, FastHashMap};
-use print_tree::PrintTree;
+use print_tree::{PrintTree, PrintTreePrinter};
 use api::{ClipId, LayerPoint, LayerRect, LayerToScrollTransform, LayerToWorldTransform};
 use api::{LayerVector2D, PipelineId, ScrollClamping, ScrollEventPhase, ScrollLayerState};
 use api::{ScrollLocation, StickyFrameInfo, WorldPoint};
 
 pub type ScrollStates = FastHashMap<ClipId, ScrollingState>;
 
 pub struct ClipScrollTree {
     pub nodes: FastHashMap<ClipId, ClipScrollNode>,
@@ -365,17 +365,17 @@ impl ClipScrollTree {
         self.pipelines_to_discard.insert(pipeline_id);
 
         match self.currently_scrolling_node_id {
             Some(id) if id.pipeline_id() == pipeline_id => self.currently_scrolling_node_id = None,
             _ => {}
         }
     }
 
-    fn print_node(&self, id: &ClipId, pt: &mut PrintTree) {
+    fn print_node<T: PrintTreePrinter>(&self, id: &ClipId, pt: &mut T) {
         let node = self.nodes.get(id).unwrap();
 
         match node.node_type {
             NodeType::Clip(ref info) => {
                 pt.new_level("Clip".to_owned());
                 pt.add_item(format!("screen_bounding_rect: {:?}", info.screen_bounding_rect));
 
                 let clips = info.clip_sources.clips();
@@ -411,13 +411,19 @@ impl ClipScrollTree {
 
         pt.end_level();
     }
 
     #[allow(dead_code)]
     pub fn print(&self) {
         if !self.nodes.is_empty() {
             let mut pt = PrintTree::new("clip_scroll tree");
-            self.print_node(&self.root_reference_frame_id, &mut pt);
+            self.print_with(&mut pt);
+        }
+    }
+
+    pub fn print_with<T: PrintTreePrinter>(&self, pt: &mut T) {
+        if !self.nodes.is_empty() {
+            self.print_node(&self.root_reference_frame_id, pt);
         }
     }
 }
 
--- a/gfx/webrender/src/debug_server.rs
+++ b/gfx/webrender/src/debug_server.rs
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{ApiMsg, DebugCommand};
 use api::channel::MsgSender;
 use std::sync::mpsc::{channel, Receiver};
 use std::thread;
 use std::sync::mpsc::Sender;
 
+use print_tree::PrintTreePrinter;
 use ws;
 
 // Messages that are sent from the render backend to the renderer
 // debug command queue. These are sent in a separate queue so
 // that none of these types are exposed to the RenderApi interfaces.
 // We can't use select!() as it's not stable...
 enum DebugMsg {
     AddSender(ws::Sender),
@@ -60,16 +61,19 @@ impl ws::Handler for Server {
                         DebugCommand::EnableRenderTargetDebug(false)
                     }
                     "fetch_passes" => {
                         DebugCommand::FetchPasses
                     }
                     "fetch_documents" => {
                         DebugCommand::FetchDocuments
                     }
+                    "fetch_clipscrolltree" => {
+                        DebugCommand::FetchClipScrollTree
+                    }
                     msg => {
                         println!("unknown msg {}", msg);
                         return Ok(());
                     }
                 };
 
                 let msg = ApiMsg::DebugCommand(cmd);
                 self.api_tx.send(msg).unwrap();
@@ -276,8 +280,71 @@ impl DocumentList {
             root: TreeNode::new("root"),
         }
     }
 
     pub fn add(&mut self, item: TreeNode) {
         self.root.add_child(item);
     }
 }
+
+// A serializable list of debug information about clip-scroll trees
+// that can be sent to the client
+
+#[derive(Serialize)]
+pub struct ClipScrollTreeList {
+    kind: &'static str,
+    root: TreeNode,
+}
+
+impl ClipScrollTreeList {
+    pub fn new() -> ClipScrollTreeList {
+        ClipScrollTreeList {
+            kind: "clipscrolltree",
+            root: TreeNode::new("root"),
+        }
+    }
+
+    pub fn add(&mut self, item: TreeNode) {
+        self.root.add_child(item);
+    }
+}
+
+// A TreeNode-based PrintTreePrinter to serialize pretty-printed
+// trees as json
+pub struct TreeNodeBuilder {
+    levels: Vec<TreeNode>,
+}
+
+impl TreeNodeBuilder {
+    pub fn new(root: TreeNode) -> TreeNodeBuilder {
+        TreeNodeBuilder {
+            levels: vec![root]
+        }
+    }
+
+    fn current_level_mut(&mut self) -> &mut TreeNode {
+        assert!(!self.levels.is_empty());
+        self.levels.last_mut().unwrap()
+    }
+
+    pub fn build(mut self) -> TreeNode {
+        assert!(self.levels.len() == 1);
+        self.levels.pop().unwrap()
+    }
+}
+
+impl PrintTreePrinter for TreeNodeBuilder {
+    fn new_level(&mut self, title: String) {
+         let level = TreeNode::new(&title);
+         self.levels.push(level);
+    }
+
+    fn end_level(&mut self) {
+         assert!(!self.levels.is_empty());
+         let last_level = self.levels.pop().unwrap();
+         self.current_level_mut().add_child(last_level);
+    }
+
+    fn add_item(&mut self, text: String) {
+         self.current_level_mut().add_item(&text);
+    }
+}
--- a/gfx/webrender/src/device.rs
+++ b/gfx/webrender/src/device.rs
@@ -393,16 +393,27 @@ impl Texture {
 
     pub fn get_render_target_layer_count(&self) -> usize {
         self.fbo_ids.len()
     }
 
     pub fn get_layer_count(&self) -> i32 {
         self.layer_count
     }
+
+    pub fn get_bpp(&self) -> u32 {
+        match self.format {
+            ImageFormat::A8 => 1,
+            ImageFormat::RGB8 => 3,
+            ImageFormat::BGRA8 => 4,
+            ImageFormat::RG8 => 2,
+            ImageFormat::RGBAF32 => 16,
+            ImageFormat::Invalid => unreachable!(),
+        }
+    }
 }
 
 impl Drop for Texture {
     fn drop(&mut self) {
         debug_assert!(thread::panicking() || self.id == 0);
     }
 }
 
@@ -428,31 +439,38 @@ pub struct VAO {
 }
 
 impl Drop for VAO {
     fn drop(&mut self) {
         debug_assert!(thread::panicking() || self.id == 0, "renderer::deinit not called");
     }
 }
 
+pub struct PBO {
+    id: gl::GLuint,
+}
+
+impl Drop for PBO {
+    fn drop(&mut self) {
+        debug_assert!(thread::panicking() || self.id == 0, "renderer::deinit not called");
+    }
+}
+
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct FBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct RBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 pub struct VBOId(gl::GLuint);
 
 #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
 struct IBOId(gl::GLuint);
 
-#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)]
-pub struct PBOId(gl::GLuint);
-
 const MAX_TIMERS_PER_FRAME: usize = 256;
 const MAX_SAMPLERS_PER_FRAME: usize = 16;
 const MAX_PROFILE_FRAMES: usize = 4;
 
 pub trait NamedTag {
     fn get_label(&self) -> &str;
 }
 
@@ -754,17 +772,17 @@ pub enum ShaderError {
 }
 
 pub struct Device {
     gl: Rc<gl::Gl>,
     // device state
     bound_textures: [gl::GLuint; 16],
     bound_program: gl::GLuint,
     bound_vao: gl::GLuint,
-    bound_pbo: PBOId,
+    bound_pbo: gl::GLuint,
     bound_read_fbo: FBOId,
     bound_draw_fbo: FBOId,
     default_read_fbo: gl::GLuint,
     default_draw_fbo: gl::GLuint,
     device_pixel_ratio: f32,
 
     // HW or API capabilties
     capabilities: Capabilities,
@@ -798,17 +816,17 @@ impl Device {
 
             capabilities: Capabilities {
                 supports_multisampling: false, //TODO
             },
 
             bound_textures: [0; 16],
             bound_program: 0,
             bound_vao: 0,
-            bound_pbo: PBOId(0),
+            bound_pbo: 0,
             bound_read_fbo: FBOId(0),
             bound_draw_fbo: FBOId(0),
             default_read_fbo: 0,
             default_draw_fbo: 0,
 
             max_texture_size,
             frame_id: FrameId(0),
         }
@@ -828,17 +846,17 @@ impl Device {
 
     pub fn get_capabilities(&self) -> &Capabilities {
         &self.capabilities
     }
 
     pub fn reset_state(&mut self) {
         self.bound_textures = [0; 16];
         self.bound_vao = 0;
-        self.bound_pbo = PBOId(0);
+        self.bound_pbo = 0;
         self.bound_read_fbo = FBOId(0);
         self.bound_draw_fbo = FBOId(0);
     }
 
     pub fn compile_shader(gl: &gl::Gl,
                           name: &str,
                           shader_type: gl::GLenum,
                           source: String)
@@ -886,17 +904,17 @@ impl Device {
         self.gl.bind_vertex_array(0);
 
         // FBO state
         self.bound_read_fbo = FBOId(self.default_read_fbo);
         self.bound_draw_fbo = FBOId(self.default_draw_fbo);
 
         // Pixel op state
         self.gl.pixel_store_i(gl::UNPACK_ALIGNMENT, 1);
-        self.bound_pbo = PBOId(0);
+        self.bound_pbo = 0;
         self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0);
 
         // Default is sampler 0, always
         self.gl.active_texture(gl::TEXTURE0);
 
         self.frame_id
     }
 
@@ -1171,17 +1189,17 @@ impl Device {
                                   dest_rect.origin.x + dest_rect.size.width,
                                   dest_rect.origin.y + dest_rect.size.height,
                                   gl::COLOR_BUFFER_BIT,
                                   gl::LINEAR);
     }
 
     pub fn free_texture_storage(&mut self, texture: &mut Texture) {
         debug_assert!(self.inside_frame);
-        debug_assert_eq!(self.bound_pbo, PBOId(0));
+        debug_assert_eq!(self.bound_pbo, 0);
 
         if texture.format == ImageFormat::Invalid {
             return;
         }
 
         self.bind_texture(DEFAULT_TEXTURE, texture);
 
         let (internal_format, gl_format) = gl_texture_formats_for_image_format(&*self.gl, texture.format);
@@ -1347,49 +1365,52 @@ impl Device {
                         transform: &Transform3D<f32>) {
         debug_assert!(self.inside_frame);
         self.gl.uniform_matrix_4fv(program.u_transform,
                                    false,
                                    &transform.to_row_major_array());
         self.gl.uniform_1f(program.u_device_pixel_ratio, self.device_pixel_ratio);
     }
 
-    pub fn create_pbo(&mut self) -> PBOId {
+    pub fn create_pbo(&mut self) -> PBO {
         let id = self.gl.gen_buffers(1)[0];
-        PBOId(id)
+        PBO {
+            id
+        }
     }
 
-    pub fn destroy_pbo(&mut self, id: PBOId) {
-        self.gl.delete_buffers(&[id.0]);
+    pub fn delete_pbo(&mut self, mut pbo: PBO) {
+        self.gl.delete_buffers(&[pbo.id]);
+        pbo.id = 0;
     }
 
-    pub fn bind_pbo(&mut self, pbo_id: Option<PBOId>) {
+    pub fn bind_pbo(&mut self, pbo: Option<&PBO>) {
         debug_assert!(self.inside_frame);
-        let pbo_id = pbo_id.unwrap_or(PBOId(0));
+        let pbo_id = pbo.map_or(0, |pbo| pbo.id);
 
         if self.bound_pbo != pbo_id {
             self.bound_pbo = pbo_id;
 
-            self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo_id.0);
+            self.gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, pbo_id);
         }
     }
 
     pub fn update_pbo_data<T>(&mut self, data: &[T]) {
         debug_assert!(self.inside_frame);
-        debug_assert_ne!(self.bound_pbo, PBOId(0));
+        debug_assert_ne!(self.bound_pbo, 0);
 
         gl::buffer_data(&*self.gl,
                         gl::PIXEL_UNPACK_BUFFER,
                         data,
                         gl::STREAM_DRAW);
     }
 
     pub fn orphan_pbo(&mut self, new_size: usize) {
         debug_assert!(self.inside_frame);
-        debug_assert_ne!(self.bound_pbo, PBOId(0));
+        debug_assert_ne!(self.bound_pbo, 0);
 
         self.gl.buffer_data_untyped(gl::PIXEL_UNPACK_BUFFER,
                                     new_size as isize,
                                     ptr::null(),
                                     gl::STREAM_DRAW);
     }
 
     pub fn update_texture_from_pbo(&mut self,
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -184,16 +184,56 @@ impl<'a> FlattenContext<'a> {
 pub struct Frame {
     pub clip_scroll_tree: ClipScrollTree,
     pub pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
     id: FrameId,
     frame_builder_config: FrameBuilderConfig,
     frame_builder: Option<FrameBuilder>,
 }
 
+trait FilterOpHelpers {
+    fn resolve(self, properties: &SceneProperties) -> FilterOp;
+    fn is_noop(&self) -> bool;
+}
+
+impl FilterOpHelpers for FilterOp {
+    fn resolve(self, properties: &SceneProperties) -> FilterOp {
+        match self {
+            FilterOp::Opacity(ref value) => {
+                let amount = properties.resolve_float(value, 1.0);
+                FilterOp::Opacity(PropertyBinding::Value(amount))
+            }
+            _ => self
+        }
+    }
+
+    fn is_noop(&self) -> bool {
+        match *self {
+            FilterOp::Blur(length) => length == 0.0,
+            FilterOp::Brightness(amount) => amount == 1.0,
+            FilterOp::Contrast(amount) => amount == 1.0,
+            FilterOp::Grayscale(amount) => amount == 0.0,
+            FilterOp::HueRotate(amount) => amount == 0.0,
+            FilterOp::Invert(amount) => amount == 0.0,
+            FilterOp::Opacity(value) => {
+                match value {
+                    PropertyBinding::Value(amount) => {
+                        amount == 1.0
+                    }
+                    PropertyBinding::Binding(..) => {
+                        panic!("bug: binding value should be resolved");
+                    }
+                }
+            }
+            FilterOp::Saturate(amount) => amount == 1.0,
+            FilterOp::Sepia(amount) => amount == 0.0,
+        }
+    }
+}
+
 trait StackingContextHelpers {
     fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
     fn filter_ops_for_compositing(&self,
                                   display_list: &BuiltDisplayList,
                                   input_filters: ItemRange<FilterOp>,
                                   properties: &SceneProperties) -> Vec<FilterOp>;
 }
 
@@ -206,25 +246,21 @@ impl StackingContextHelpers for Stacking
     }
 
     fn filter_ops_for_compositing(&self,
                                   display_list: &BuiltDisplayList,
                                   input_filters: ItemRange<FilterOp>,
                                   properties: &SceneProperties) -> Vec<FilterOp> {
         let mut filters = vec![];
         for filter in display_list.get(input_filters) {
+            let filter = filter.resolve(properties);
             if filter.is_noop() {
                 continue;
             }
-            if let FilterOp::Opacity(ref value) = filter {
-                let amount = properties.resolve_float(value, 1.0);
-                filters.push(FilterOp::Opacity(PropertyBinding::Value(amount)));
-            } else {
-                filters.push(filter);
-            }
+            filters.push(filter);
         }
         filters
     }
 }
 
 impl Frame {
     pub fn new(config: FrameBuilderConfig) -> Frame {
         Frame {
@@ -562,26 +598,32 @@ impl Frame {
                 context.builder.add_yuv_image(clip_and_scroll,
                                               item_rect_with_offset,
                                               &clip_with_offset,
                                               info.yuv_data,
                                               info.color_space,
                                               info.image_rendering);
             }
             SpecificDisplayItem::Text(ref text_info) => {
-                let instance = context.resource_cache.get_font_instance(text_info.font_key).unwrap();
-                context.builder.add_text(clip_and_scroll,
-                                         reference_frame_relative_offset,
-                                         item_rect_with_offset,
-                                         &clip_with_offset,
-                                         instance,
-                                         &text_info.color,
-                                         item.glyphs(),
-                                         item.display_list().get(item.glyphs()).count(),
-                                         text_info.glyph_options);
+                match context.resource_cache.get_font_instance(text_info.font_key) {
+                    Some(instance) => {
+                        context.builder.add_text(clip_and_scroll,
+                                                 reference_frame_relative_offset,
+                                                 item_rect_with_offset,
+                                                 &clip_with_offset,
+                                                 instance,
+                                                 &text_info.color,
+                                                 item.glyphs(),
+                                                 item.display_list().get(item.glyphs()).count(),
+                                                 text_info.glyph_options);
+                    }
+                    None => {
+                        warn!("Unknown font instance key: {:?}", text_info.font_key);
+                    }
+                }
             }
             SpecificDisplayItem::Rectangle(ref info) => {
                 if !self.try_to_add_rectangle_splitting_on_clip(context,
                                                                 &item_rect_with_offset,
                                                                 &clip_with_offset,
                                                                 &info.color,
                                                                 &clip_and_scroll) {
                     context.builder.add_solid_rectangle(clip_and_scroll,
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderDetails, BorderDisplayItem, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
+use api::{BorderDetails, BorderDisplayItem, BorderRadius, BoxShadowClipMode, ClipAndScrollInfo, ClipId, ColorF};
 use api::{DeviceIntPoint, DeviceIntRect, DeviceIntSize, DeviceUintRect, DeviceUintSize};
 use api::{ExtendMode, FontInstance, FontRenderMode};
 use api::{GlyphInstance, GlyphOptions, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize};
 use api::{LayerToScrollTransform, LayerVector2D, LayoutVector2D, LineOrientation, LineStyle};
 use api::{LocalClip, PipelineId, RepeatMode, ScrollSensitivity, SubpixelDirection, TextShadow};
 use api::{TileOffset, TransformStyle, WorldPixel, YuvColorSpace, YuvData};
 use app_units::Au;
@@ -193,23 +193,23 @@ impl FrameBuilder {
 
     /// Create a primitive and add it to the prim store. This method doesn't
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     fn create_primitive(&mut self,
                         clip_and_scroll: ClipAndScrollInfo,
                         rect: &LayerRect,
                         local_clip: &LocalClip,
-                        extra_clips: &[ClipSource],
+                        mut clip_sources: Vec<ClipSource>,
                         container: PrimitiveContainer) -> PrimitiveIndex {
         self.create_clip_scroll_group_if_necessary(clip_and_scroll);
 
-        let mut clip_sources = extra_clips.to_vec();
-        if let &LocalClip::RoundedRect(_, _) = local_clip {
-            clip_sources.push(ClipSource::Region(ClipRegion::create_for_local_clip(local_clip)))
+        if let &LocalClip::RoundedRect(main, region) = local_clip {
+            clip_sources.push(ClipSource::Rectangle(main));
+            clip_sources.push(ClipSource::RoundedRectangle(region.rect, region.radii, ClipMode::Clip));
         }
 
         let clip_sources = ClipSources::new(clip_sources);
 
         let prim_index = self.prim_store.add_primitive(rect,
                                                        &local_clip.clip_rect(),
                                                        clip_sources,
                                                        container);
@@ -237,22 +237,22 @@ impl FrameBuilder {
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(&mut self,
                          clip_and_scroll: ClipAndScrollInfo,
                          rect: &LayerRect,
                          local_clip: &LocalClip,
-                         extra_clips: &[ClipSource],
+                         clip_sources: Vec<ClipSource>,
                          container: PrimitiveContainer) -> PrimitiveIndex {
         let prim_index = self.create_primitive(clip_and_scroll,
                                                rect,
                                                local_clip,
-                                               extra_clips,
+                                               clip_sources,
                                                container);
 
         self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
 
         prim_index
     }
 
     pub fn create_clip_scroll_group(&mut self, info: ClipAndScrollInfo) -> ClipScrollGroupIndex {
@@ -440,17 +440,17 @@ impl FrameBuilder {
 
         // Create an empty text-shadow primitive. Insert it into
         // the draw lists immediately so that it will be drawn
         // before any visual text elements that are added as
         // part of this text-shadow context.
         let prim_index = self.add_primitive(clip_and_scroll,
                                             &LayerRect::zero(),
                                             local_clip,
-                                            &[],
+                                            Vec::new(),
                                             PrimitiveContainer::TextShadow(prim));
 
         self.shadow_prim_stack.push(prim_index);
     }
 
     pub fn pop_text_shadow(&mut self) {
         let prim_index = self.shadow_prim_stack
                              .pop()
@@ -474,17 +474,17 @@ impl FrameBuilder {
                                flags: PrimitiveFlags) {
         let prim = RectanglePrimitive {
             color: *color,
         };
 
         let prim_index = self.add_primitive(clip_and_scroll,
                                             rect,
                                             local_clip,
-                                            &[],
+                                            Vec::new(),
                                             PrimitiveContainer::Rectangle(prim));
 
         match flags {
             PrimitiveFlags::None => {}
             PrimitiveFlags::Scrollbar(clip_id, border_radius) => {
                 self.scrollbar_prims.push(ScrollbarPrimitive {
                     prim_index,
                     clip_id,
@@ -530,24 +530,24 @@ impl FrameBuilder {
             }
         }
         for shadow in fast_text_shadow_prims {
             let mut line = line.clone();
             line.color = shadow.color;
             self.add_primitive(clip_and_scroll,
                                &new_rect.translate(&shadow.offset),
                                local_clip,
-                               &[],
+                               Vec::new(),
                                PrimitiveContainer::Line(line));
         }
 
         let prim_index = self.create_primitive(clip_and_scroll,
                                                &new_rect,
                                                local_clip,
-                                               &[],
+                                               Vec::new(),
                                                PrimitiveContainer::Line(line));
 
         if color.a > 0.0 {
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         for shadow_prim_index in &self.shadow_prim_stack {
             let shadow_metadata = &mut self.prim_store.cpu_metadata[shadow_prim_index.0];
@@ -825,17 +825,17 @@ impl FrameBuilder {
         };
 
         let prim = if aligned {
             PrimitiveContainer::AlignedGradient(gradient_cpu)
         } else {
             PrimitiveContainer::AngleGradient(gradient_cpu)
         };
 
-        self.add_primitive(clip_and_scroll, &rect, local_clip, &[], prim);
+        self.add_primitive(clip_and_scroll, &rect, local_clip, Vec::new(), prim);
     }
 
     pub fn add_radial_gradient(&mut self,
                                clip_and_scroll: ClipAndScrollInfo,
                                rect: LayerRect,
                                local_clip: &LocalClip,
                                start_center: LayerPoint,
                                start_radius: f32,
@@ -857,17 +857,17 @@ impl FrameBuilder {
                 [start_radius, end_radius, ratio_xy, pack_as_float(extend_mode as u32)].into(),
                 [tile_size.width, tile_size.height, tile_repeat.width, tile_repeat.height].into(),
             ],
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            local_clip,
-                           &[],
+                           Vec::new(),
                            PrimitiveContainer::RadialGradient(radial_gradient_cpu));
     }
 
     pub fn add_text(&mut self,
                     clip_and_scroll: ClipAndScrollInfo,
                     run_offset: LayoutVector2D,
                     rect: LayerRect,
                     local_clip: &LocalClip,
@@ -969,26 +969,26 @@ impl FrameBuilder {
                 text_prim.offset += shadow_prim.shadow.offset;
                 fast_text_shadow_prims.push(text_prim);
             }
         }
         for text_prim in fast_text_shadow_prims {
             self.add_primitive(clip_and_scroll,
                                &rect.translate(&text_prim.offset),
                                local_clip,
-                               &[],
+                               Vec::new(),
                                PrimitiveContainer::TextRun(text_prim));
         }
 
         // Create (and add to primitive store) the primitive that will be
         // used for both the visual element and also the shadow(s).
         let prim_index = self.create_primitive(clip_and_scroll,
                                                &rect,
                                                local_clip,
-                                               &[],
+                                               Vec::new(),
                                                PrimitiveContainer::TextRun(prim));
 
         // Only add a visual element if it can contribute to the scene.
         if color.a > 0.0 {
             self.add_primitive_to_draw_list(prim_index, clip_and_scroll);
         }
 
         // Now add this primitive index to all the currently active text shadow
@@ -1026,27 +1026,28 @@ impl FrameBuilder {
             BoxShadowClipMode::Outset |
             BoxShadowClipMode::None => (ClipMode::Clip, bs_rect),
             BoxShadowClipMode::Inset => (ClipMode::ClipOut, *box_bounds),
         };
 
         let box_clip_mode = !bs_clip_mode;
 
         // Clip the inside and then the outside of the box.
-        let extra_clips = [ClipSource::Complex(bs_rect, border_radius, bs_clip_mode),
-                           ClipSource::Complex(*box_bounds, border_radius, box_clip_mode)];
+        let border_radius = BorderRadius::uniform(border_radius);
+        let extra_clips = vec![ClipSource::RoundedRectangle(bs_rect, border_radius, bs_clip_mode),
+                               ClipSource::RoundedRectangle(*box_bounds, border_radius, box_clip_mode)];
 
         let prim = RectanglePrimitive {
             color: *color,
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect_to_draw,
                            local_clip,
-                           &extra_clips,
+                           extra_clips,
                            PrimitiveContainer::Rectangle(prim));
     }
 
     pub fn add_box_shadow(&mut self,
                           clip_and_scroll: ClipAndScrollInfo,
                           box_bounds: &LayerRect,
                           local_clip: &LocalClip,
                           box_offset: &LayerVector2D,
@@ -1187,36 +1188,36 @@ impl FrameBuilder {
                 // need a clip out of the center box.
                 let extra_clip_mode = match clip_mode {
                     BoxShadowClipMode::Outset | BoxShadowClipMode::None => ClipMode::ClipOut,
                     BoxShadowClipMode::Inset => ClipMode::Clip,
                 };
 
                 let mut extra_clips = Vec::new();
                 if border_radius >= 0.0 {
-                    extra_clips.push(ClipSource::Complex(*box_bounds,
-                                                border_radius,
-                                                extra_clip_mode));
+                    extra_clips.push(ClipSource::RoundedRectangle(*box_bounds,
+                                                                  BorderRadius::uniform(border_radius),
+                                                                  extra_clip_mode));
                 }
 
                 let prim_cpu = BoxShadowPrimitiveCpu {
                     src_rect: *box_bounds,
                     bs_rect,
                     color: *color,
                     blur_radius,
                     border_radius,
                     edge_size,
                     inverted,
                     rects,
                 };
 
                 self.add_primitive(clip_and_scroll,
                                    &outer_rect,
                                    local_clip,
-                                   extra_clips.as_slice(),
+                                   extra_clips,
                                    PrimitiveContainer::BoxShadow(prim_cpu));
             }
         }
     }
 
     pub fn add_image(&mut self,
                      clip_and_scroll: ClipAndScrollInfo,
                      rect: LayerRect,
@@ -1240,17 +1241,17 @@ impl FrameBuilder {
                             tile_spacing.height ].into(),
                             sub_rect_block,
                         ],
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            local_clip,
-                           &[],
+                           Vec::new(),
                            PrimitiveContainer::Image(prim_cpu));
     }
 
     pub fn add_yuv_image(&mut self,
                          clip_and_scroll: ClipAndScrollInfo,
                          rect: LayerRect,
                          clip_rect: &LocalClip,
                          yuv_data: YuvData,
@@ -1271,17 +1272,17 @@ impl FrameBuilder {
             color_space,
             image_rendering,
             gpu_block: [rect.size.width, rect.size.height, 0.0, 0.0].into(),
         };
 
         self.add_primitive(clip_and_scroll,
                            &rect,
                            clip_rect,
-                           &[],
+                           Vec::new(),
                            PrimitiveContainer::YuvImage(prim_cpu));
     }
 
     /// Compute the contribution (bounding rectangles, and resources) of layers and their
     /// primitives in screen space.
     fn build_layer_screen_rects_and_cull_layers(&mut self,
                                                 screen_rect: &DeviceIntRect,
                                                 clip_scroll_tree: &mut ClipScrollTree,
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -153,16 +153,17 @@ impl RendererFrame {
             layers_bouncing_back,
             frame,
         }
     }
 }
 
 pub enum DebugOutput {
     FetchDocuments(String),
+    FetchClipScrollTree(String),
 }
 
 pub enum ResultMsg {
     DebugCommand(DebugCommand),
     DebugOutput(DebugOutput),
     RefreshShader(PathBuf),
     NewFrame(DocumentId, RendererFrame, TextureUpdateList, BackendProfileCounters),
     UpdateResources { updates: TextureUpdateList, cancel_rendering: bool },
--- a/gfx/webrender/src/mask_cache.rs
+++ b/gfx/webrender/src/mask_cache.rs
@@ -1,19 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{BorderRadius, ComplexClipRegion, DeviceIntRect, ImageMask, LayerPoint, LayerRect};
+use api::{DeviceIntRect, ImageMask, LayerPoint, LayerRect};
 use api::{LayerSize, LayerToWorldTransform};
 use border::BorderCornerClipSource;
 use clip::{ClipMode, ClipSource};
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use prim_store::{CLIP_DATA_GPU_BLOCKS, ClipData, ImageMaskData};
-use util::{ComplexClipRegionHelpers, TransformedRect};
+use util::{extract_inner_rect_safe, TransformedRect};
 
 const MAX_CLIP: f32 = 1000000.0;
 
 #[derive(Debug, Copy, Clone)]
 pub struct ClipAddressRange {
     pub location: GpuCacheHandle,
     item_count: usize,
 }
@@ -109,27 +109,26 @@ impl MaskCacheInfo {
         let mut border_corners = Vec::new();
         let mut complex_clip_count = 0;
         let mut layer_clip_count = 0;
 
         // Work out how much clip data space we need to allocate
         // and if we have an image mask.
         for clip in clips {
             match *clip {
-                ClipSource::Complex(..) => {
+                ClipSource::RoundedRectangle(..) => {
                     complex_clip_count += 1;
                 }
-                ClipSource::Region(ref region) => {
-                    if let Some(info) = region.image_mask {
-                        debug_assert!(image.is_none());     // TODO(gw): Support >1 image mask!
-                        image = Some((info, GpuCacheHandle::new()));
-                    }
-                    complex_clip_count += region.complex_clips.len();
+                ClipSource::Rectangle(..) => {
                     layer_clip_count += 1;
                 }
+                ClipSource::Image(image_mask) => {
+                    debug_assert!(image.is_none());     // TODO(gw): Support >1 image mask!
+                    image = Some((image_mask, GpuCacheHandle::new()));
+                }
                 ClipSource::BorderCorner(ref source) => {
                     border_corners.push((source.clone(), GpuCacheHandle::new()));
                 }
             }
         }
 
         MaskCacheInfo {
             complex_clip_range: ClipAddressRange::new(complex_clip_count),
@@ -144,112 +143,89 @@ impl MaskCacheInfo {
     }
 
     pub fn update(&mut self,
                   sources: &[ClipSource],
                   transform: &LayerToWorldTransform,
                   gpu_cache: &mut GpuCache,
                   device_pixel_ratio: f32)
                   -> &MaskBounds {
-
         // Step[1] - compute the local bounds
         //TODO: move to initialization stage?
         if self.bounds.inner.is_none() {
             let mut local_rect = Some(LayerRect::new(LayerPoint::new(-MAX_CLIP, -MAX_CLIP),
                                                      LayerSize::new(2.0 * MAX_CLIP, 2.0 * MAX_CLIP)));
-            let mut local_inner: Option<LayerRect> = None;
+            let mut local_inner = local_rect;
             let mut has_clip_out = false;
             let has_border_clip = !self.border_corners.is_empty();
 
             for source in sources {
                 match *source {
-                    ClipSource::Complex(rect, radius, mode) => {
+                    ClipSource::Image(ref mask) => {
+                        if !mask.repeat {
+                            local_rect = local_rect.and_then(|r| r.intersection(&mask.rect));
+                        }
+                        local_inner = None;
+                    }
+                    ClipSource::Rectangle(rect) => {
+                        local_rect = local_rect.and_then(|r| r.intersection(&rect));
+                        local_inner = local_inner.and_then(|r| r.intersection(&rect));
+                    }
+                    ClipSource::RoundedRectangle(ref rect, ref radius, mode) => {
                         // Once we encounter a clip-out, we just assume the worst
                         // case clip mask size, for now.
                         if mode == ClipMode::ClipOut {
                             has_clip_out = true;
                             break;
                         }
-                        local_rect = local_rect.and_then(|r| r.intersection(&rect));
-                        local_inner = ComplexClipRegion::new(rect, BorderRadius::uniform(radius))
-                                                        .get_inner_rect_safe();
-                    }
-                    ClipSource::Region(ref region) => {
-                        local_rect = local_rect.and_then(|r| r.intersection(&region.main));
-                        local_inner = match region.image_mask {
-                            Some(ref mask) => {
-                                if !mask.repeat {
-                                    local_rect = local_rect.and_then(|r| r.intersection(&mask.rect));
-                                }
-                                None
-                            },
-                            None => local_rect,
-                        };
+
+                        local_rect = local_rect.and_then(|r| r.intersection(rect));
 
-                        for clip in &region.complex_clips {
-                            local_rect = local_rect.and_then(|r| r.intersection(&clip.rect));
-                            local_inner = local_inner.and_then(|r| clip.get_inner_rect_safe()
-                                                                       .and_then(|ref inner| r.intersection(inner)));
-                        }
+                        let inner_rect = extract_inner_rect_safe(rect, radius);
+                        local_inner = local_inner.and_then(|r| inner_rect.and_then(|ref inner| r.intersection(inner)));
                     }
                     ClipSource::BorderCorner{..} => {}
                 }
             }
 
             // Work out the type of mask geometry we have, based on the
             // list of clip sources above.
             self.bounds = if has_clip_out || has_border_clip {
                 // For clip-out, the mask rect is not known.
                 MaskBounds {
                     outer: None,
                     inner: Some(LayerRect::zero().into()),
                 }
             } else {
-                // TODO(gw): local inner is only valid if there's a single clip (for now).
-                // This can be improved in the future, with some proper
-                // rectangle region handling.
-                if sources.len() > 1 {
-                    local_inner = None;
-                }
-
                 MaskBounds {
                     outer: Some(local_rect.unwrap_or(LayerRect::zero()).into()),
                     inner: Some(local_inner.unwrap_or(LayerRect::zero()).into()),
                 }
             };
         }
 
         // Step[2] - update GPU cache data
 
         if let Some(block_count) = self.complex_clip_range.get_block_count() {
             if let Some(mut request) = gpu_cache.request(&mut self.complex_clip_range.location) {
                 for source in sources {
-                    match *source {
-                        ClipSource::Complex(rect, radius, mode) => {
-                            let data = ClipData::uniform(rect, radius, mode);
-                            data.write(&mut request);
-                        }
-                        ClipSource::Region(ref region) => {
-                            for clip in &region.complex_clips {
-                                let data = ClipData::from_clip_region(&clip);
-                                data.write(&mut request);
-                            }
-                        }
-                        ClipSource::BorderCorner{..} => {}
+                    if let ClipSource::RoundedRectangle(ref rect, ref radius, mode) = *source {
+                        let data = ClipData::rounded_rect(rect, radius, mode);
+                        data.write(&mut request);
                     }
                 }
                 assert_eq!(request.close(), block_count);
             }
         }
 
         if let Some(block_count) = self.layer_clip_range.get_block_count() {
             if let Some(mut request) = gpu_cache.request(&mut self.layer_clip_range.location) {
                 for source in sources {
-                    if let ClipSource::Region(ref region) = *source {
-                        let data = ClipData::uniform(region.main, 0.0, ClipMode::Clip);
+                    if let ClipSource::Rectangle(rect) = *source {
+                        let data = ClipData::uniform(rect, 0.0, ClipMode::Clip);
                         data.write(&mut request);
                     }
                 }
                 assert_eq!(request.close(), block_count);
             }
         }
 
         for &mut (ref mut border_source, ref mut gpu_location) in &mut self.border_corners {
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{BuiltDisplayList, ColorF, ComplexClipRegion, DeviceIntRect, DeviceIntSize, DevicePoint};
-use api::{ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
+use api::{BorderRadius, ExtendMode, FontRenderMode, GlyphInstance, GradientStop};
 use api::{ImageKey, ImageRendering, ItemRange, LayerPoint, LayerRect, LayerSize, TextShadow};
 use api::{GlyphKey, LayerToWorldTransform, TileOffset, YuvColorSpace, YuvFormat};
 use api::{device_length, FontInstance, LayerVector2D, LineOrientation, LineStyle};
 use app_units::Au;
 use border::BorderCornerInstance;
 use clip::{ClipMode, ClipSources};
 use euclid::{Size2D};
 use gpu_cache::{GpuCacheAddress, GpuBlockData, GpuCache, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
@@ -646,57 +646,56 @@ pub struct ClipData {
     rect: ClipRect,
     top_left: ClipCorner,
     top_right: ClipCorner,
     bottom_left: ClipCorner,
     bottom_right: ClipCorner,
 }
 
 impl ClipData {
-    pub fn from_clip_region(clip: &ComplexClipRegion) -> ClipData {
+    pub fn rounded_rect(rect: &LayerRect, radii: &BorderRadius, mode: ClipMode) -> ClipData {
         ClipData {
             rect: ClipRect {
-                rect: clip.rect,
-                // TODO(gw): Support other clip modes for regions?
-                mode: ClipMode::Clip as u32 as f32,
+                rect: *rect,
+                mode: mode as u32 as f32,
             },
             top_left: ClipCorner {
                 rect: LayerRect::new(
-                    LayerPoint::new(clip.rect.origin.x, clip.rect.origin.y),
-                    LayerSize::new(clip.radii.top_left.width, clip.radii.top_left.height)),
-                outer_radius_x: clip.radii.top_left.width,
-                outer_radius_y: clip.radii.top_left.height,
+                    LayerPoint::new(rect.origin.x, rect.origin.y),
+                    LayerSize::new(radii.top_left.width, radii.top_left.height)),
+                outer_radius_x: radii.top_left.width,
+                outer_radius_y: radii.top_left.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
             top_right: ClipCorner {
                 rect: LayerRect::new(
-                    LayerPoint::new(clip.rect.origin.x + clip.rect.size.width - clip.radii.top_right.width, clip.rect.origin.y),
-                    LayerSize::new(clip.radii.top_right.width, clip.radii.top_right.height)),
-                outer_radius_x: clip.radii.top_right.width,
-                outer_radius_y: clip.radii.top_right.height,
+                    LayerPoint::new(rect.origin.x + rect.size.width - radii.top_right.width, rect.origin.y),
+                    LayerSize::new(radii.top_right.width, radii.top_right.height)),
+                outer_radius_x: radii.top_right.width,
+                outer_radius_y: radii.top_right.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
             bottom_left: ClipCorner {
                 rect: LayerRect::new(
-                    LayerPoint::new(clip.rect.origin.x, clip.rect.origin.y + clip.rect.size.height - clip.radii.bottom_left.height),
-                    LayerSize::new(clip.radii.bottom_left.width, clip.radii.bottom_left.height)),
-                outer_radius_x: clip.radii.bottom_left.width,
-                outer_radius_y: clip.radii.bottom_left.height,
+                    LayerPoint::new(rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.height),
+                    LayerSize::new(radii.bottom_left.width, radii.bottom_left.height)),
+                outer_radius_x: radii.bottom_left.width,
+                outer_radius_y: radii.bottom_left.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
             bottom_right: ClipCorner {
                 rect: LayerRect::new(
-                    LayerPoint::new(clip.rect.origin.x + clip.rect.size.width - clip.radii.bottom_right.width,
-                                    clip.rect.origin.y + clip.rect.size.height - clip.radii.bottom_right.height),
-                    LayerSize::new(clip.radii.bottom_right.width, clip.radii.bottom_right.height)),
-                outer_radius_x: clip.radii.bottom_right.width,
-                outer_radius_y: clip.radii.bottom_right.height,
+                    LayerPoint::new(rect.origin.x + rect.size.width - radii.bottom_right.width,
+                                    rect.origin.y + rect.size.height - radii.bottom_right.height),
+                    LayerSize::new(radii.bottom_right.width, radii.bottom_right.height)),
+                outer_radius_x: radii.bottom_right.width,
+                outer_radius_y: radii.bottom_right.height,
                 inner_radius_x: 0.0,
                 inner_radius_y: 0.0,
             },
         }
     }
 
     pub fn uniform(rect: LayerRect, radius: f32, mode: ClipMode) -> ClipData {
         ClipData {
--- a/gfx/webrender/src/print_tree.rs
+++ b/gfx/webrender/src/print_tree.rs
@@ -8,58 +8,70 @@ pub struct PrintTree {
     /// The current level of recursion.
     level: u32,
 
     /// An item which is queued up, so that we can determine if we need
     /// a mid-tree prefix or a branch ending prefix.
     queued_item: Option<String>,
 }
 
+/// A trait that makes it easy to describe a pretty tree of data,
+/// regardless of the printing destination, to either print it
+/// directly to stdout, or serialize it as in the debugger
+pub trait PrintTreePrinter {
+    fn new_level(&mut self, title: String);
+    fn end_level(&mut self);
+    fn add_item(&mut self, text: String);
+}
+
 impl PrintTree {
     pub fn new(title: &str) -> PrintTree {
         println!("\u{250c} {}", title);
         PrintTree {
             level: 1,
             queued_item: None,
         }
     }
 
-    /// Descend one level in the tree with the given title.
-    pub fn new_level(&mut self, title: String) {
-        self.flush_queued_item("\u{251C}\u{2500}");
-
-        self.print_level_prefix();
-        println!("\u{251C}\u{2500} {}", title);
-
-        self.level = self.level + 1;
-    }
-
-    /// Ascend one level in the tree.
-    pub fn end_level(&mut self) {
-        self.flush_queued_item("\u{2514}\u{2500}");
-        self.level = self.level - 1;
-    }
-
-    /// Add an item to the current level in the tree.
-    pub fn add_item(&mut self, text: String) {
-        self.flush_queued_item("\u{251C}\u{2500}");
-        self.queued_item = Some(text);
-    }
-
     fn print_level_prefix(&self) {
         for _ in 0..self.level {
             print!("\u{2502}  ");
         }
     }
 
     fn flush_queued_item(&mut self, prefix: &str) {
         if let Some(queued_item) = self.queued_item.take() {
             self.print_level_prefix();
             println!("{} {}", prefix, queued_item);
         }
     }
 }
 
+// The default `println!` based printer
+impl PrintTreePrinter for PrintTree {
+    /// Descend one level in the tree with the given title.
+    fn new_level(&mut self, title: String) {
+        self.flush_queued_item("\u{251C}\u{2500}");
+
+        self.print_level_prefix();
+        println!("\u{251C}\u{2500} {}", title);
+
+        self.level = self.level + 1;
+    }
+
+    /// Ascend one level in the tree.
+    fn end_level(&mut self) {
+        self.flush_queued_item("\u{2514}\u{2500}");
+        self.level = self.level - 1;
+    }
+
+    /// Add an item to the current level in the tree.
+    fn add_item(&mut self, text: String) {
+        self.flush_queued_item("\u{251C}\u{2500}");
+        self.queued_item = Some(text);
+    }    
+}
+
 impl Drop for PrintTree {
     fn drop(&mut self) {
         self.flush_queued_item("\u{9492}\u{9472}");
     }
 }
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -474,16 +474,20 @@ impl RenderBackend {
                     self.notifier.lock().unwrap().as_mut().unwrap().new_frame_ready();
                 }
                 ApiMsg::DebugCommand(option) => {
                     let msg = match option {
                         DebugCommand::FetchDocuments => {
                             let json = self.get_docs_for_debugger();
                             ResultMsg::DebugOutput(DebugOutput::FetchDocuments(json))
                         }
+                        DebugCommand::FetchClipScrollTree => {
+                            let json = self.get_clip_scroll_tree_for_debugger();
+                            ResultMsg::DebugOutput(DebugOutput::FetchClipScrollTree(json))
+                        }
                         _ => {
                             ResultMsg::DebugCommand(option)
                         }
                     };
                     self.result_tx.send(msg).unwrap();
                     let notifier = self.notifier.lock();
                     notifier.unwrap()
                             .as_mut()
@@ -591,16 +595,36 @@ impl RenderBackend {
                 debug_doc.add_child(debug_dl);
             }
 
             docs.add(debug_doc);
         }
 
         serde_json::to_string(&docs).unwrap()
     }
+
+    #[cfg(not(feature = "debugger"))]
+    fn get_clip_scroll_tree_for_debugger(&self) -> String {
+        String::new()
+    }
+
+    #[cfg(feature = "debugger")]
+    fn get_clip_scroll_tree_for_debugger(&self) -> String {
+        let mut debug_root = debug_server::ClipScrollTreeList::new();
+
+        for (_, doc) in &self.documents {
+            let debug_node = debug_server::TreeNode::new("document clip_scroll tree");
+            let mut builder = debug_server::TreeNodeBuilder::new(debug_node);
+            doc.frame.clip_scroll_tree.print_with(&mut builder);
+
+            debug_root.add(builder.build());            
+        }
+
+        serde_json::to_string(&debug_root).unwrap()
+    }
 }
 
 #[cfg(feature = "debugger")]
 trait ToDebugString {
     fn debug_string(&self) -> String;
 }
 
 #[cfg(feature = "debugger")]
--- a/gfx/webrender/src/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -13,17 +13,17 @@
 use api::ApiMsg;
 #[cfg(not(feature = "debugger"))]
 use api::channel::MsgSender;
 use api::DebugCommand;
 use debug_colors;
 use debug_render::DebugRenderer;
 #[cfg(feature = "debugger")]
 use debug_server::{self, DebugServer};
-use device::{DepthFunction, Device, FrameId, Program, Texture, VertexDescriptor, GpuMarker, GpuProfiler, PBOId};
+use device::{DepthFunction, Device, FrameId, Program, Texture, VertexDescriptor, GpuMarker, GpuProfiler, PBO};
 use device::{GpuTimer, TextureFilter, VAO, VertexUsageHint, FileWatcherHandler, TextureTarget, ShaderError};
 use device::{ExternalTexture, get_gl_format_bgra, TextureSlot, VertexAttribute, VertexAttributeKind};
 use euclid::{Transform3D, rect};
 use frame_builder::FrameBuilderConfig;
 use gleam::gl;
 use gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList};
 use internal_types::{FastHashMap, CacheTextureId, RendererFrame, ResultMsg, TextureUpdateOp};
 use internal_types::{DebugOutput, TextureUpdateList, RenderTargetMode, TextureUpdateSource};
@@ -494,35 +494,36 @@ impl CacheRow {
             is_dirty: false,
         }
     }
 }
 
 /// The device-specific representation of the cache texture in gpu_cache.rs
 struct CacheTexture {
     texture: Texture,
-    pbo_id: PBOId,
+    pbo: PBO,
     rows: Vec<CacheRow>,
     cpu_blocks: Vec<GpuBlockData>,
 }
 
 impl CacheTexture {
     fn new(device: &mut Device) -> CacheTexture {
         let texture = device.create_texture(TextureTarget::Default);
-        let pbo_id = device.create_pbo();
+        let pbo = device.create_pbo();
 
         CacheTexture {
             texture,
-            pbo_id,
+            pbo,
             rows: Vec::new(),
             cpu_blocks: Vec::new(),
         }
     }
 
     fn deinit(self, device: &mut Device) {
+        device.delete_pbo(self.pbo);
         device.delete_texture(self.texture);
     }
 
     fn apply_patch(&mut self,
                    update: &GpuCacheUpdate,
                    blocks: &[GpuBlockData]) {
         match update {
             &GpuCacheUpdate::Copy { block_index, block_count, address } => {
@@ -579,17 +580,17 @@ impl CacheTexture {
         for update in &updates.updates {
             self.apply_patch(update, &updates.blocks);
         }
     }
 
     fn flush(&mut self, device: &mut Device) {
         // Bind a PBO to do the texture upload.
         // Updating the texture via PBO avoids CPU-side driver stalls.
-        device.bind_pbo(Some(self.pbo_id));
+        device.bind_pbo(Some(&self.pbo));
 
         for (row_index, row) in self.rows.iter_mut().enumerate() {
             if row.is_dirty {
                 // Get the data for this row and push to the PBO.
                 let block_index = row_index * MAX_VERTEX_TEXTURE_WIDTH;
                 let cpu_blocks = &self.cpu_blocks[block_index..(block_index + MAX_VERTEX_TEXTURE_WIDTH)];
                 device.update_pbo_data(cpu_blocks);
 
@@ -616,17 +617,17 @@ impl CacheTexture {
 
         // Ensure that other texture updates won't read from this PBO.
         device.bind_pbo(None);
     }
 }
 
 struct VertexDataTexture {
     texture: Texture,
-    pbo: PBOId,
+    pbo: PBO,
 }
 
 impl VertexDataTexture {
     fn new(device: &mut Device) -> VertexDataTexture {
         let texture = device.create_texture(TextureTarget::Default);
         let pbo = device.create_pbo();
 
         VertexDataTexture {
@@ -671,32 +672,33 @@ impl VertexDataTexture {
                                 TextureFilter::Nearest,
                                 RenderTargetMode::None,
                                 1,
                                 None);
         }
 
         // Bind a PBO to do the texture upload.
         // Updating the texture via PBO avoids CPU-side driver stalls.
-        device.bind_pbo(Some(self.pbo));
+        device.bind_pbo(Some(&self.pbo));
         device.update_pbo_data(data);
         device.update_texture_from_pbo(&self.texture,
                                        0,
                                        0,
                                        width,
                                        needed_height,
                                        0,
                                        None,
                                        0);
 
         // Ensure that other texture updates won't read from this PBO.
         device.bind_pbo(None);
     }
 
     fn deinit(self, device: &mut Device) {
+        device.delete_pbo(self.pbo);
         device.delete_texture(self.texture);
     }
 }
 
 const TRANSFORM_FEATURE: &str = "TRANSFORM";
 const SUBPIXEL_AA_FEATURE: &str = "SUBPIXEL_AA";
 const CLIP_FEATURE: &str = "CLIP";
 
@@ -997,17 +999,17 @@ pub struct Renderer {
     gpu_cache_texture: CacheTexture,
 
     pipeline_epoch_map: FastHashMap<PipelineId, Epoch>,
 
     // Manages and resolves source textures IDs to real texture IDs.
     texture_resolver: SourceTextureResolver,
 
     // A PBO used to do asynchronous texture cache uploads.
-    texture_cache_upload_pbo: PBOId,
+    texture_cache_upload_pbo: PBO,
 
     dither_matrix_texture: Option<Texture>,
 
     /// Optional trait object that allows the client
     /// application to provide external buffers for image data.
     external_image_handler: Option<Box<ExternalImageHandler>>,
 
     renderer_errors: Vec<RendererError>,
@@ -1614,17 +1616,18 @@ impl Renderer {
                         self.current_frame = None;
                     }
                 }
                 ResultMsg::RefreshShader(path) => {
                     self.pending_shader_updates.push(path);
                 }
                 ResultMsg::DebugOutput(output) => {
                     match output {
-                        DebugOutput::FetchDocuments(string) => {
+                        DebugOutput::FetchDocuments(string) |
+                        DebugOutput::FetchClipScrollTree(string) => {
                             self.debug_server.send(string);
                         }
                     }
                 }
                 ResultMsg::DebugCommand(command) => {
                     self.handle_debug_command(command);
                 }
             }
@@ -1713,16 +1716,17 @@ impl Renderer {
             DebugCommand::EnableRenderTargetDebug(enable) => {
                 if enable {
                     self.debug_flags.insert(RENDER_TARGET_DBG);
                 } else {
                     self.debug_flags.remove(RENDER_TARGET_DBG);
                 }
             }
             DebugCommand::FetchDocuments => {}
+            DebugCommand::FetchClipScrollTree => {}
             DebugCommand::FetchPasses => {
                 let json = self.get_passes_for_debugger();
                 self.debug_server.send(json);
             }
         }
     }
 
     /// Set a callback for handling external images.
@@ -1886,30 +1890,40 @@ impl Renderer {
                                                  layer_count,
                                                  None);
                     }
                     TextureUpdateOp::Update { rect, source, stride, layer_index, offset } => {
                         let texture = &self.texture_resolver.cache_texture_map[update.id.0];
 
                         // Bind a PBO to do the texture upload.
                         // Updating the texture via PBO avoids CPU-side driver stalls.
-                        self.device.bind_pbo(Some(self.texture_cache_upload_pbo));
+                        self.device.bind_pbo(Some(&self.texture_cache_upload_pbo));
 
                         match source {
                             TextureUpdateSource::Bytes { data }  => {
                                 self.device.update_pbo_data(&data[offset as usize..]);
                             }
                             TextureUpdateSource::External { id, channel_index } => {
                                 let handler = self.external_image_handler
                                                   .as_mut()
                                                   .expect("Found external image, but no handler set!");
                                 match handler.lock(id, channel_index).source {
                                     ExternalImageSource::RawData(data) => {
                                         self.device.update_pbo_data(&data[offset as usize..]);
                                     }
+                                    ExternalImageSource::Invalid => {
+                                        // Create a local buffer to fill the pbo.
+                                        let bpp = texture.get_bpp();
+                                        let width = stride.unwrap_or(rect.size.width * bpp);
+                                        let total_size = width * rect.size.height;
+                                        // WR haven't support RGBAF32 format in texture_cache, so
+                                        // we use u8 type here.
+                                        let dummy_data: Vec<u8> = vec![255; total_size as usize];
+                                        self.device.update_pbo_data(&dummy_data);
+                                    }
                                     _ => panic!("No external buffer found"),
                                 };
                                 handler.unlock(id, channel_index);
                             }
                         }
 
                         self.device.update_texture_from_pbo(texture,
                                                             rect.origin.x,
@@ -2425,16 +2439,21 @@ impl Renderer {
                 };
 
                 // In order to produce the handle, the external image handler may call into
                 // the GL context and change some states.
                 self.device.reset_state();
 
                 let texture = match image.source {
                     ExternalImageSource::NativeTexture(texture_id) => ExternalTexture::new(texture_id, texture_target),
+                    ExternalImageSource::Invalid => {
+                        warn!("Invalid ext-image for ext_id:{:?}, channel:{}.", ext_image.id, ext_image.channel_index);
+                        // Just use 0 as the gl handle for this failed case.
+                        ExternalTexture::new(0, texture_target)
+                    }
                     _ => panic!("No native texture found."),
                 };
 
                 self.texture_resolver
                     .external_images
                     .insert((ext_image.id, ext_image.channel_index), texture);
 
                 let update = GpuCacheUpdate::Copy {
@@ -2753,16 +2772,17 @@ impl Renderer {
         self.layer_texture.deinit(&mut self.device);
         self.render_task_texture.deinit(&mut self.device);
         for texture in self.alpha_render_targets {
             self.device.delete_texture(texture);
         }
         for texture in self.color_render_targets {
             self.device.delete_texture(texture);
         }
+        self.device.delete_pbo(self.texture_cache_upload_pbo);
         self.texture_resolver.deinit(&mut self.device);
         self.device.delete_vao(self.prim_vao);
         self.device.delete_vao(self.clip_vao);
         self.device.delete_vao(self.blur_vao);
         self.device.delete_vao(self.box_shadow_vao);
         self.debug.deinit(&mut self.device);
         self.cs_box_shadow.deinit(&mut self.device);
         self.cs_text_run.deinit(&mut self.device);
@@ -2798,17 +2818,18 @@ impl Renderer {
         self.ps_split_composite.deinit(&mut self.device);
         self.ps_composite.deinit(&mut self.device);
         self.device.end_frame();
     }
 }
 
 pub enum ExternalImageSource<'a> {
     RawData(&'a [u8]),      // raw buffers.
-    NativeTexture(u32),     // Is a gl::GLuint texture handle
+    NativeTexture(u32),     // It's a gl::GLuint texture handle
+    Invalid,
 }
 
 /// The data that an external client should provide about
 /// an external image. The timestamp is used to test if
 /// the renderer should upload new texture data this
 /// frame. For instance, if providing video frames, the
 /// application could call wr.render() whenever a new
 /// video frame is ready. If the callback increments
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -262,31 +262,22 @@ impl TransformedRect {
 
 #[inline(always)]
 pub fn pack_as_float(value: u32) -> f32 {
     value as f32 + 0.5
 }
 
 
 pub trait ComplexClipRegionHelpers {
-    /// Return an aligned rectangle that is inside the clip region and doesn't intersect
-    /// any of the bounding rectangles of the rounded corners.
-    fn get_inner_rect_safe(&self) -> Option<LayoutRect>;
     /// Return the approximately largest aligned rectangle that is fully inside
     /// the provided clip region.
     fn get_inner_rect_full(&self) -> Option<LayoutRect>;
 }
 
 impl ComplexClipRegionHelpers for ComplexClipRegion {
-    fn get_inner_rect_safe(&self) -> Option<LayoutRect> {
-        // value of `k==1.0` is used for extraction of the corner rectangles
-        // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
-        extract_inner_rect_impl(&self.rect, &self.radii, 1.0)
-    }
-
     fn get_inner_rect_full(&self) -> Option<LayoutRect> {
         // this `k` optimal for a simple case of all border radii being equal
         let k = 1.0 - 0.5 * FRAC_1_SQRT_2; // could be nicely approximated to `0.3`
         extract_inner_rect_impl(&self.rect, &self.radii, k)
     }
 }
 
 #[inline]
@@ -305,16 +296,25 @@ 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
     }
 }
 
+/// Return an aligned rectangle that is inside the clip region and doesn't intersect
+/// any of the bounding rectangles of the rounded corners.
+pub fn extract_inner_rect_safe<U>(rect: &TypedRect<f32, U>,
+                                  radii: &BorderRadius) -> Option<TypedRect<f32, U>> {
+    // value of `k==1.0` is used for extraction of the corner rectangles
+    // see `SEGMENT_CORNER_*` in `clip_shared.glsl`
+    extract_inner_rect_impl(rect, radii, 1.0)
+}
+
 /// 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());
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -181,16 +181,18 @@ pub enum DebugCommand {
     // Display all texture cache pages on screen.
     EnableTextureCacheDebug(bool),
     // Display intermediate render targets on screen.
     EnableRenderTargetDebug(bool),
     // Fetch current documents and display lists.
     FetchDocuments,
     // Fetch current passes and batches.
     FetchPasses,
+    // Fetch clip-scroll tree.
+    FetchClipScrollTree,
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub enum ApiMsg {
     /// Add/remove/update images and fonts.
     UpdateResources(ResourceUpdates),
     /// Gets the glyph dimensions
     GetGlyphDimensions(FontInstance, Vec<GlyphKey>, MsgSender<Vec<Option<GlyphDimensions>>>),
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -379,33 +379,16 @@ pub enum FilterOp {
     Grayscale(f32),
     HueRotate(f32),
     Invert(f32),
     Opacity(PropertyBinding<f32>),
     Saturate(f32),
     Sepia(f32),
 }
 
-impl FilterOp {
-    pub fn is_noop(&self) -> bool {
-        match *self {
-            FilterOp::Blur(length) if length == 0.0 => true,
-            FilterOp::Brightness(amount) if amount == 1.0 => true,
-            FilterOp::Contrast(amount) if amount == 1.0 => true,
-            FilterOp::Grayscale(amount) if amount == 0.0 => true,
-            FilterOp::HueRotate(amount) if amount == 0.0 => true,
-            FilterOp::Invert(amount) if amount == 0.0 => true,
-            FilterOp::Opacity(amount) if amount == PropertyBinding::Value(1.0) => true,
-            FilterOp::Saturate(amount) if amount == 1.0 => true,
-            FilterOp::Sepia(amount) if amount == 0.0 => true,
-            _ => false,
-        }
-    }
-}
-
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct IframeDisplayItem {
     pub pipeline_id: PipelineId,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageDisplayItem {
     pub image_key: ImageKey,