diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2014-03-28 12:55:29 -0700 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2014-04-03 14:50:56 -0700 |
commit | cd9d824c21167721fca40a4eff9563b8316d6c1a (patch) | |
tree | 842382875b8155467b5bb08ea347ad0131760f6c /src | |
parent | f8e3e50db5041fd753d0cb83a7cb1464725cc976 (diff) | |
download | servo-cd9d824c21167721fca40a4eff9563b8316d6c1a.tar.gz servo-cd9d824c21167721fca40a4eff9563b8316d6c1a.zip |
servo: Implement stacking contexts and allow multiple layers per
pipeline. This handles fixed positioning mostly correctly.
Diffstat (limited to 'src')
20 files changed, 1717 insertions, 1133 deletions
diff --git a/src/components/gfx/display_list.rs b/src/components/gfx/display_list.rs index 34e3d278b3e..1f31909502e 100644 --- a/src/components/gfx/display_list.rs +++ b/src/components/gfx/display_list.rs @@ -18,64 +18,145 @@ use color::Color; use render_context::RenderContext; use text::TextRun; -use geom::{Point2D, Rect, Size2D, SideOffsets2D}; +use geom::{Point2D, Rect, SideOffsets2D, Size2D}; use servo_net::image::base::Image; use servo_util::geometry::Au; use servo_util::range::Range; +use servo_util::smallvec::{SmallVec, SmallVec0, SmallVecIterator}; use std::cast::transmute_region; +use std::cast; +use std::libc::uintptr_t; +use std::mem; use std::vec::Items; use style::computed_values::border_style; use sync::Arc; -pub struct DisplayListCollection<E> { - lists: ~[DisplayList<E>] -} - -impl<E> DisplayListCollection<E> { - pub fn new() -> DisplayListCollection<E> { - DisplayListCollection { - lists: ~[] +/// An opaque handle to a node. The only safe operation that can be performed on this node is to +/// compare it to another opaque handle or to another node. +/// +/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout +/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for +/// locality reasons. Using `OpaqueNode` enforces this invariant. +#[deriving(Clone, Eq)] +pub struct OpaqueNode(uintptr_t); + +impl OpaqueNode { + /// Returns the address of this node, for debugging purposes. + pub fn id(&self) -> uintptr_t { + unsafe { + cast::transmute_copy(self) } } +} - pub fn iter<'a>(&'a self) -> DisplayListIterator<'a,E> { - ParentDisplayListIterator(self.lists.iter()) - } +/// A stacking context. See CSS 2.1 § E.2. "Steps" below refer to steps in that section of the +/// specification. +/// +/// TODO(pcwalton): Outlines. +pub struct StackingContext { + /// The border and backgrounds for the root of this stacking context: steps 1 and 2. + background_and_borders: DisplayList, + /// Borders and backgrounds for block-level descendants: step 4. + block_backgrounds_and_borders: DisplayList, + /// Floats: step 5. These are treated as pseudo-stacking contexts. + floats: DisplayList, + /// All other content. + content: DisplayList, + + /// Positioned descendant stacking contexts, along with their `z-index` levels. + /// + /// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle + /// `auto`, not just an integer. In this case we should store an actual stacking context, not + /// a flattened display list. + positioned_descendants: SmallVec0<(int, DisplayList)>, +} - pub fn add_list(&mut self, list: DisplayList<E>) { - self.lists.push(list); +impl StackingContext { + pub fn new() -> StackingContext { + StackingContext { + background_and_borders: DisplayList::new(), + block_backgrounds_and_borders: DisplayList::new(), + floats: DisplayList::new(), + content: DisplayList::new(), + positioned_descendants: SmallVec0::new(), + } } - pub fn draw_lists_into_context(&self, render_context: &mut RenderContext) { - for list in self.lists.iter() { - list.draw_into_context(render_context); + pub fn list_for_background_and_border_level<'a>( + &'a mut self, + level: BackgroundAndBorderLevel) + -> &'a mut DisplayList { + match level { + RootOfStackingContextLevel => &mut self.background_and_borders, + BlockLevel => &mut self.block_backgrounds_and_borders, + ContentLevel => &mut self.content, } - debug!("{:?}", self.dump()); } - fn dump(&self) { - let mut index = 0; - for list in self.lists.iter() { - debug!("dumping display list {:d}:", index); - list.dump(); - index = index + 1; + /// Flattens a stacking context into a display list according to the steps in CSS 2.1 § E.2. + pub fn flatten(self) -> DisplayList { + // Steps 1 and 2: Borders and background for the root. + let StackingContext { + background_and_borders: mut result, + block_backgrounds_and_borders, + floats, + content, + positioned_descendants: mut positioned_descendants + } = self; + + // TODO(pcwalton): Sort positioned children according to z-index. + + // Step 3: Positioned descendants with negative z-indices. + for &(ref mut z_index, ref mut list) in positioned_descendants.mut_iter() { + if *z_index < 0 { + result.push_all_move(mem::replace(list, DisplayList::new())) + } + } + + // Step 4: Block backgrounds and borders. + result.push_all_move(block_backgrounds_and_borders); + + // Step 5: Floats. + result.push_all_move(floats); + + // TODO(pcwalton): Step 6: Inlines that generate stacking contexts. + + // Step 7: Content. + result.push_all_move(content); + + // Steps 8 and 9: Positioned descendants with nonnegative z-indices. + for &(ref mut z_index, ref mut list) in positioned_descendants.mut_iter() { + if *z_index >= 0 { + result.push_all_move(mem::replace(list, DisplayList::new())) + } } + + // TODO(pcwalton): Step 10: Outlines. + + result } } +/// Which level to place backgrounds and borders in. +pub enum BackgroundAndBorderLevel { + RootOfStackingContextLevel, + BlockLevel, + ContentLevel, +} + /// A list of rendering operations to be performed. -pub struct DisplayList<E> { - list: ~[DisplayItem<E>] +pub struct DisplayList { + list: SmallVec0<DisplayItem>, } -pub enum DisplayListIterator<'a,E> { +pub enum DisplayListIterator<'a> { EmptyDisplayListIterator, - ParentDisplayListIterator(Items<'a,DisplayList<E>>), + ParentDisplayListIterator(Items<'a,DisplayList>), } -impl<'a,E> Iterator<&'a DisplayList<E>> for DisplayListIterator<'a,E> { +impl<'a> Iterator<&'a DisplayList> for DisplayListIterator<'a> { #[inline] - fn next(&mut self) -> Option<&'a DisplayList<E>> { + fn next(&mut self) -> Option<&'a DisplayList> { match *self { EmptyDisplayListIterator => None, ParentDisplayListIterator(ref mut subiterator) => subiterator.next(), @@ -83,11 +164,11 @@ impl<'a,E> Iterator<&'a DisplayList<E>> for DisplayListIterator<'a,E> { } } -impl<E> DisplayList<E> { +impl DisplayList { /// Creates a new display list. - pub fn new() -> DisplayList<E> { + pub fn new() -> DisplayList { DisplayList { - list: ~[] + list: SmallVec0::new(), } } @@ -98,60 +179,62 @@ impl<E> DisplayList<E> { } /// Appends the given item to the display list. - pub fn append_item(&mut self, item: DisplayItem<E>) { - // FIXME(Issue #150): crashes - //debug!("Adding display item {:u}: {}", self.len(), item); + pub fn push(&mut self, item: DisplayItem) { self.list.push(item) } + /// Appends the given display list to this display list, consuming the other display list in + /// the process. + pub fn push_all_move(&mut self, other: DisplayList) { + self.list.push_all_move(other.list) + } + /// Draws the display list into the given render context. pub fn draw_into_context(&self, render_context: &mut RenderContext) { debug!("Beginning display list."); for item in self.list.iter() { - // FIXME(Issue #150): crashes - //debug!("drawing {}", *item); item.draw_into_context(render_context) } debug!("Ending display list."); } /// Returns a preorder iterator over the given display list. - pub fn iter<'a>(&'a self) -> DisplayItemIterator<'a,E> { + pub fn iter<'a>(&'a self) -> DisplayItemIterator<'a> { ParentDisplayItemIterator(self.list.iter()) } } /// One drawing command in the list. -pub enum DisplayItem<E> { - SolidColorDisplayItemClass(~SolidColorDisplayItem<E>), - TextDisplayItemClass(~TextDisplayItem<E>), - ImageDisplayItemClass(~ImageDisplayItem<E>), - BorderDisplayItemClass(~BorderDisplayItem<E>), - LineDisplayItemClass(~LineDisplayItem<E>), - ClipDisplayItemClass(~ClipDisplayItem<E>) +pub enum DisplayItem { + SolidColorDisplayItemClass(~SolidColorDisplayItem), + TextDisplayItemClass(~TextDisplayItem), + ImageDisplayItemClass(~ImageDisplayItem), + BorderDisplayItemClass(~BorderDisplayItem), + LineDisplayItemClass(~LineDisplayItem), + ClipDisplayItemClass(~ClipDisplayItem) } /// Information common to all display items. -pub struct BaseDisplayItem<E> { +pub struct BaseDisplayItem { /// The boundaries of the display item. /// /// TODO: Which coordinate system should this use? bounds: Rect<Au>, - /// Extra data: either the originating flow (for hit testing) or nothing (for rendering). - extra: E, + /// The originating DOM node. + node: OpaqueNode, } /// Renders a solid color. -pub struct SolidColorDisplayItem<E> { - base: BaseDisplayItem<E>, +pub struct SolidColorDisplayItem { + base: BaseDisplayItem, color: Color, } /// Renders text. -pub struct TextDisplayItem<E> { +pub struct TextDisplayItem { /// Fields common to all display items. - base: BaseDisplayItem<E>, + base: BaseDisplayItem, /// The text run. text_run: Arc<~TextRun>, @@ -188,14 +271,19 @@ bitfield!(TextDisplayItemFlags, override_overline, set_override_overline, 0x02) bitfield!(TextDisplayItemFlags, override_line_through, set_override_line_through, 0x04) /// Renders an image. -pub struct ImageDisplayItem<E> { - base: BaseDisplayItem<E>, +pub struct ImageDisplayItem { + base: BaseDisplayItem, image: Arc<~Image>, + + /// The dimensions to which the image display item should be stretched. If this is smaller than + /// the bounds of this display item, then the image will be repeated in the appropriate + /// direction to tile the entire bounds. + stretch_size: Size2D<Au>, } /// Renders a border. -pub struct BorderDisplayItem<E> { - base: BaseDisplayItem<E>, +pub struct BorderDisplayItem { + base: BaseDisplayItem, /// The border widths border: SideOffsets2D<Au>, @@ -207,31 +295,31 @@ pub struct BorderDisplayItem<E> { style: SideOffsets2D<border_style::T> } -/// Renders a line segment -pub struct LineDisplayItem<E> { - base: BaseDisplayItem<E>, +/// Renders a line segment. +pub struct LineDisplayItem { + base: BaseDisplayItem, /// The line segment color. color: Color, - /// The line segemnt style. + /// The line segment style. style: border_style::T } -pub struct ClipDisplayItem<E> { - base: BaseDisplayItem<E>, - child_list: ~[DisplayItem<E>], +pub struct ClipDisplayItem { + base: BaseDisplayItem, + child_list: SmallVec0<DisplayItem>, need_clip: bool } -pub enum DisplayItemIterator<'a,E> { +pub enum DisplayItemIterator<'a> { EmptyDisplayItemIterator, - ParentDisplayItemIterator(Items<'a,DisplayItem<E>>), + ParentDisplayItemIterator(SmallVecIterator<'a,DisplayItem>), } -impl<'a,E> Iterator<&'a DisplayItem<E>> for DisplayItemIterator<'a,E> { +impl<'a> Iterator<&'a DisplayItem> for DisplayItemIterator<'a> { #[inline] - fn next(&mut self) -> Option<&'a DisplayItem<E>> { + fn next(&mut self) -> Option<&'a DisplayItem> { match *self { EmptyDisplayItemIterator => None, ParentDisplayItemIterator(ref mut subiterator) => subiterator.next(), @@ -239,7 +327,7 @@ impl<'a,E> Iterator<&'a DisplayItem<E>> for DisplayItemIterator<'a,E> { } } -impl<E> DisplayItem<E> { +impl DisplayItem { /// Renders this display item into the given render context. fn draw_into_context(&self, render_context: &mut RenderContext) { match *self { @@ -306,7 +394,22 @@ impl<E> DisplayItem<E> { ImageDisplayItemClass(ref image_item) => { debug!("Drawing image at {:?}.", image_item.base.bounds); - render_context.draw_image(image_item.base.bounds, image_item.image.clone()) + let mut y_offset = Au(0); + while y_offset < image_item.base.bounds.size.height { + let mut x_offset = Au(0); + while x_offset < image_item.base.bounds.size.width { + let mut bounds = image_item.base.bounds; + bounds.origin.x = bounds.origin.x + x_offset; + bounds.origin.y = bounds.origin.y + y_offset; + bounds.size = image_item.stretch_size; + + render_context.draw_image(bounds, image_item.image.clone()); + + x_offset = x_offset + image_item.stretch_size.width; + } + + y_offset = y_offset + image_item.stretch_size.height; + } } BorderDisplayItemClass(ref border) => { @@ -324,7 +427,7 @@ impl<E> DisplayItem<E> { } } - pub fn base<'a>(&'a self) -> &'a BaseDisplayItem<E> { + pub fn base<'a>(&'a self) -> &'a BaseDisplayItem { // FIXME(tkuehn): Workaround for Rust region bug. unsafe { match *self { @@ -342,7 +445,7 @@ impl<E> DisplayItem<E> { self.base().bounds } - pub fn children<'a>(&'a self) -> DisplayItemIterator<'a,E> { + pub fn children<'a>(&'a self) -> DisplayItemIterator<'a> { match *self { ClipDisplayItemClass(ref clip) => ParentDisplayItemIterator(clip.child_list.iter()), SolidColorDisplayItemClass(..) | diff --git a/src/components/gfx/render_context.rs b/src/components/gfx/render_context.rs index a33d8f1f085..44529c057e3 100644 --- a/src/components/gfx/render_context.rs +++ b/src/components/gfx/render_context.rs @@ -5,8 +5,8 @@ use font_context::FontContext; use style::computed_values::border_style; -use azure::azure_hl::{B8G8R8A8, Color, ColorPattern, DrawOptions}; -use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, Linear, StrokeOptions}; +use azure::azure_hl::{B8G8R8A8, Color, ColorPattern, DrawOptions, DrawSurfaceOptions, DrawTarget}; +use azure::azure_hl::{Linear, SourceOp, StrokeOptions}; use azure::AZ_CAP_BUTT; use azure::AzFloat; use geom::point::Point2D; @@ -45,7 +45,7 @@ impl<'a> RenderContext<'a> { pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) { self.draw_target.make_current(); - self.draw_target.fill_rect(&bounds.to_azure_rect(), &ColorPattern(color)); + self.draw_target.fill_rect(&bounds.to_azure_rect(), &ColorPattern(color), None); } pub fn draw_border(&self, @@ -121,13 +121,15 @@ impl<'a> RenderContext<'a> { } pub fn clear(&self) { - let pattern = ColorPattern(Color(1.0, 1.0, 1.0, 1.0)); + let pattern = ColorPattern(Color(0.0, 0.0, 0.0, 0.0)); let rect = Rect(Point2D(self.page_rect.origin.x as AzFloat, self.page_rect.origin.y as AzFloat), Size2D(self.screen_rect.size.width as AzFloat, self.screen_rect.size.height as AzFloat)); + let mut draw_options = DrawOptions(1.0, 0); + draw_options.set_composition_op(SourceOp); self.draw_target.make_current(); - self.draw_target.fill_rect(&rect, &pattern); + self.draw_target.fill_rect(&rect, &pattern, Some(&draw_options)); } fn draw_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: SideOffsets2D<Color>, style: SideOffsets2D<border_style::T>) { diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index bb9ed1f665a..d697b2166e2 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -2,7 +2,12 @@ * 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/. */ -// The task that handles all rendering/painting. +//! The task that handles all rendering/painting. + +use buffer_map::BufferMap; +use display_list::DisplayList; +use font_context::{FontContext, FontContextInfo}; +use render_context::RenderContext; use azure::azure_hl::{B8G8R8A8, Color, DrawTarget, StolenGLResources}; use azure::AzFloat; @@ -12,12 +17,14 @@ use geom::size::Size2D; use layers::platform::surface::{NativePaintingGraphicsContext, NativeSurface}; use layers::platform::surface::{NativeSurfaceMethods}; use layers; -use servo_msg::compositor_msg::{Epoch, IdleRenderState, LayerBuffer, LayerBufferSet}; -use servo_msg::compositor_msg::{RenderListener, RenderingRenderState}; +use servo_msg::compositor_msg::{Epoch, IdleRenderState, LayerBuffer}; +use servo_msg::compositor_msg::{LayerBufferSet, LayerId, LayerMetadata, RenderListener}; +use servo_msg::compositor_msg::{RenderingRenderState, ScrollPolicy}; use servo_msg::constellation_msg::{ConstellationChan, PipelineId, RendererReadyMsg}; use servo_msg::constellation_msg::{Failure, FailureMsg}; use servo_msg::platform::surface::NativeSurfaceAzureMethods; use servo_util::opts::Opts; +use servo_util::smallvec::{SmallVec, SmallVec1}; use servo_util::time::{ProfilerChan, profile}; use servo_util::time; use servo_util::task::send_on_failure; @@ -26,20 +33,23 @@ use std::comm::{Chan, Port}; use std::task; use sync::Arc; -use buffer_map::BufferMap; -use display_list::DisplayListCollection; -use font_context::{FontContext, FontContextInfo}; -use render_context::RenderContext; - -pub struct RenderLayer<T> { - display_list_collection: Arc<DisplayListCollection<T>>, - size: Size2D<uint>, - color: Color +/// Information about a layer that layout sends to the painting task. +pub struct RenderLayer { + /// A per-pipeline ID describing this layer that should be stable across reflows. + id: LayerId, + /// The display list describing the contents of this layer. + display_list: Arc<DisplayList>, + /// The position of the layer in pixels. + rect: Rect<uint>, + /// The color of the background in this layer. Used for unrendered content. + color: Color, + /// The scrolling policy of this layer. + scroll_policy: ScrollPolicy, } -pub enum Msg<T> { - RenderMsg(RenderLayer<T>), - ReRenderMsg(~[BufferRequest], f32, Epoch), +pub enum Msg { + RenderMsg(SmallVec1<RenderLayer>), + ReRenderMsg(~[BufferRequest], f32, LayerId, Epoch), UnusedBufferMsg(~[~LayerBuffer]), PaintPermissionGranted, PaintPermissionRevoked, @@ -63,22 +73,21 @@ pub fn BufferRequest(screen_rect: Rect<uint>, page_rect: Rect<f32>) -> BufferReq } } -// FIXME(rust#9155): this should be a newtype struct, but -// generic newtypes ICE when compiled cross-crate -pub struct RenderChan<T> { - chan: Chan<Msg<T>>, +// FIXME(pcwalton): This should be a newtype struct. +pub struct RenderChan { + chan: Chan<Msg>, } -impl<T: Send> Clone for RenderChan<T> { - fn clone(&self) -> RenderChan<T> { +impl Clone for RenderChan { + fn clone(&self) -> RenderChan { RenderChan { chan: self.chan.clone(), } } } -impl<T: Send> RenderChan<T> { - pub fn new() -> (Port<Msg<T>>, RenderChan<T>) { +impl RenderChan { + pub fn new() -> (Port<Msg>, RenderChan) { let (port, chan) = Chan::new(); let render_chan = RenderChan { chan: chan, @@ -86,11 +95,11 @@ impl<T: Send> RenderChan<T> { (port, render_chan) } - pub fn send(&self, msg: Msg<T>) { + pub fn send(&self, msg: Msg) { assert!(self.try_send(msg), "RenderChan.send: render port closed") } - pub fn try_send(&self, msg: Msg<T>) -> bool { + pub fn try_send(&self, msg: Msg) -> bool { self.chan.try_send(msg) } } @@ -102,9 +111,9 @@ enum GraphicsContext { GpuGraphicsContext, } -pub struct RenderTask<C,T> { +pub struct RenderTask<C> { id: PipelineId, - port: Port<Msg<T>>, + port: Port<Msg>, compositor: C, constellation_chan: ConstellationChan, font_ctx: ~FontContext, @@ -119,8 +128,8 @@ pub struct RenderTask<C,T> { /// The native graphics context. native_graphics_context: Option<NativePaintingGraphicsContext>, - /// The layer to be rendered - render_layer: Option<RenderLayer<T>>, + /// The layers to be rendered. + render_layers: SmallVec1<RenderLayer>, /// Permission to send paint messages to the compositor paint_permission: bool, @@ -140,9 +149,25 @@ macro_rules! native_graphics_context( ) ) -impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> { +fn initialize_layers<C:RenderListener>( + compositor: &mut C, + pipeline_id: PipelineId, + epoch: Epoch, + render_layers: &[RenderLayer]) { + let metadata = render_layers.iter().map(|render_layer| { + LayerMetadata { + id: render_layer.id, + rect: render_layer.rect, + color: render_layer.color, + scroll_policy: render_layer.scroll_policy, + } + }).collect(); + compositor.initialize_layers_for_pipeline(pipeline_id, metadata, epoch); +} + +impl<C: RenderListener + Send> RenderTask<C> { pub fn create(id: PipelineId, - port: Port<Msg<T>>, + port: Port<Msg>, compositor: C, constellation_chan: ConstellationChan, failure_msg: Failure, @@ -181,7 +206,7 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> { native_graphics_context: native_graphics_context, - render_layer: None, + render_layers: SmallVec1::new(), paint_permission: false, epoch: Epoch(0), @@ -207,20 +232,25 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> { loop { match self.port.recv() { - RenderMsg(render_layer) => { - if self.paint_permission { - self.epoch.next(); - self.compositor.set_layer_page_size_and_color(self.id, render_layer.size, self.epoch, render_layer.color); - } else { + RenderMsg(render_layers) => { + self.epoch.next(); + self.render_layers = render_layers; + + if !self.paint_permission { debug!("render_task: render ready msg"); let ConstellationChan(ref mut c) = self.constellation_chan; c.send(RendererReadyMsg(self.id)); + continue; } - self.render_layer = Some(render_layer); + + initialize_layers(&mut self.compositor, + self.id, + self.epoch, + self.render_layers.as_slice()); } - ReRenderMsg(tiles, scale, epoch) => { + ReRenderMsg(tiles, scale, layer_id, epoch) => { if self.epoch == epoch { - self.render(tiles, scale); + self.render(tiles, scale, layer_id); } else { debug!("renderer epoch mismatch: {:?} != {:?}", self.epoch, epoch); } @@ -233,12 +263,16 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> { } PaintPermissionGranted => { self.paint_permission = true; - match self.render_layer { - Some(ref render_layer) => { - self.epoch.next(); - self.compositor.set_layer_page_size_and_color(self.id, render_layer.size, self.epoch, render_layer.color); - } - None => {} + + // Here we assume that the main layer—the layer responsible for the page size— + // is the first layer. This is a pretty fragile assumption. It will be fixed + // once we use the layers-based scrolling infrastructure for all scrolling. + if self.render_layers.len() > 1 { + self.epoch.next(); + initialize_layers(&mut self.compositor, + self.id, + self.epoch, + self.render_layers.as_slice()); } } PaintPermissionRevoked => { @@ -253,138 +287,151 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> { } } - fn render(&mut self, tiles: ~[BufferRequest], scale: f32) { - if self.render_layer.is_none() { - return - } - - self.compositor.set_render_state(RenderingRenderState); + /// Renders one layer and sends the tiles back to the layer. + /// + /// FIXME(pcwalton): We will probably want to eventually send all layers belonging to a page in + /// one transaction, to avoid the user seeing inconsistent states. + fn render(&mut self, tiles: ~[BufferRequest], scale: f32, layer_id: LayerId) { time::profile(time::RenderingCategory, self.profiler_chan.clone(), || { // FIXME: Try not to create a new array here. let mut new_buffers = ~[]; + // Find the appropriate render layer. + let mut render_layer = None; + for layer in self.render_layers.iter() { + if layer.id == layer_id { + render_layer = Some(layer); + break + } + } + let render_layer = match render_layer { + Some(render_layer) => render_layer, + None => return, + }; + + self.compositor.set_render_state(RenderingRenderState); + // Divide up the layer into tiles. - time::profile(time::RenderingPrepBuffCategory, self.profiler_chan.clone(), || { - for tile in tiles.iter() { - let width = tile.screen_rect.size.width; - let height = tile.screen_rect.size.height; - - let size = Size2D(width as i32, height as i32); - let draw_target = match self.graphics_context { - CpuGraphicsContext => { - DrawTarget::new(self.opts.render_backend, size, B8G8R8A8) - } - GpuGraphicsContext => { - // FIXME(pcwalton): Cache the components of draw targets - // (texture color buffer, renderbuffers) instead of recreating them. - let draw_target = - DrawTarget::new_with_fbo(self.opts.render_backend, - native_graphics_context!(self), - size, - B8G8R8A8); - draw_target.make_current(); - draw_target - } + for tile in tiles.iter() { + let width = tile.screen_rect.size.width; + let height = tile.screen_rect.size.height; + + let size = Size2D(width as i32, height as i32); + let draw_target = match self.graphics_context { + CpuGraphicsContext => { + DrawTarget::new(self.opts.render_backend, size, B8G8R8A8) + } + GpuGraphicsContext => { + // FIXME(pcwalton): Cache the components of draw targets + // (texture color buffer, renderbuffers) instead of recreating them. + let draw_target = + DrawTarget::new_with_fbo(self.opts.render_backend, + native_graphics_context!(self), + size, + B8G8R8A8); + draw_target.make_current(); + draw_target + } + }; + + { + // Build the render context. + let mut ctx = RenderContext { + draw_target: &draw_target, + font_ctx: &mut self.font_ctx, + opts: &self.opts, + page_rect: tile.page_rect, + screen_rect: tile.screen_rect, }; - { - // Build the render context. - let mut ctx = RenderContext { - draw_target: &draw_target, - font_ctx: &mut self.font_ctx, - opts: &self.opts, - page_rect: tile.page_rect, - screen_rect: tile.screen_rect, - }; + // Apply the translation to render the tile we want. + let matrix: Matrix2D<AzFloat> = Matrix2D::identity(); + let matrix = matrix.scale(scale as AzFloat, scale as AzFloat); + let matrix = matrix.translate(-(tile.page_rect.origin.x) as AzFloat, + -(tile.page_rect.origin.y) as AzFloat); + let matrix = matrix.translate(-(render_layer.rect.origin.x as AzFloat), + -(render_layer.rect.origin.y as AzFloat)); + + ctx.draw_target.set_transform(&matrix); - // Apply the translation to render the tile we want. - let matrix: Matrix2D<AzFloat> = Matrix2D::identity(); - let matrix = matrix.scale(scale as AzFloat, scale as AzFloat); - let matrix = matrix.translate(-(tile.page_rect.origin.x) as AzFloat, - -(tile.page_rect.origin.y) as AzFloat); - - ctx.draw_target.set_transform(&matrix); - - // Clear the buffer. - ctx.clear(); - - // Draw the display list. - profile(time::RenderingDrawingCategory, self.profiler_chan.clone(), || { - let render_layer = self.render_layer.as_ref().unwrap(); - render_layer.display_list_collection.get().draw_lists_into_context(&mut ctx); - ctx.draw_target.flush(); - }); - } + // Clear the buffer. + ctx.clear(); + + // Draw the display list. + profile(time::RenderingDrawingCategory, self.profiler_chan.clone(), || { + render_layer.display_list.get().draw_into_context(&mut ctx); + ctx.draw_target.flush(); + }); + } - // Extract the texture from the draw target and place it into its slot in the - // buffer. If using CPU rendering, upload it first. - // - // FIXME(pcwalton): We should supply the texture and native surface *to* the - // draw target in GPU rendering mode, so that it doesn't have to recreate it. - let buffer = match self.graphics_context { - CpuGraphicsContext => { - let buffer = match self.buffer_map.find(tile.screen_rect.size) { - Some(buffer) => { - let mut buffer = buffer; - buffer.rect = tile.page_rect; - buffer.screen_pos = tile.screen_rect; - buffer.resolution = scale; - buffer.native_surface.mark_wont_leak(); - buffer - } - None => { - // Create an empty native surface. We mark it as not leaking - // in case it dies in transit to the compositor task. - let mut native_surface: NativeSurface = - layers::platform::surface::NativeSurfaceMethods::new( - native_graphics_context!(self), - Size2D(width as i32, height as i32), - width as i32 * 4); - native_surface.mark_wont_leak(); - - ~LayerBuffer { - native_surface: native_surface, - rect: tile.page_rect, - screen_pos: tile.screen_rect, - resolution: scale, - stride: (width * 4) as uint - } + // Extract the texture from the draw target and place it into its slot in the + // buffer. If using CPU rendering, upload it first. + // + // FIXME(pcwalton): We should supply the texture and native surface *to* the + // draw target in GPU rendering mode, so that it doesn't have to recreate it. + let buffer = match self.graphics_context { + CpuGraphicsContext => { + let buffer = match self.buffer_map.find(tile.screen_rect.size) { + Some(buffer) => { + let mut buffer = buffer; + buffer.rect = tile.page_rect; + buffer.screen_pos = tile.screen_rect; + buffer.resolution = scale; + buffer.native_surface.mark_wont_leak(); + buffer + } + None => { + // Create an empty native surface. We mark it as not leaking + // in case it dies in transit to the compositor task. + let mut native_surface: NativeSurface = + layers::platform::surface::NativeSurfaceMethods::new( + native_graphics_context!(self), + Size2D(width as i32, height as i32), + width as i32 * 4); + native_surface.mark_wont_leak(); + + ~LayerBuffer { + native_surface: native_surface, + rect: tile.page_rect, + screen_pos: tile.screen_rect, + resolution: scale, + stride: (width * 4) as uint } - }; + } + }; - draw_target.snapshot().get_data_surface().with_data(|data| { - buffer.native_surface.upload(native_graphics_context!(self), data); - debug!("RENDERER uploading to native surface {:d}", - buffer.native_surface.get_id() as int); - }); + draw_target.snapshot().get_data_surface().with_data(|data| { + buffer.native_surface.upload(native_graphics_context!(self), data); + debug!("RENDERER uploading to native surface {:d}", + buffer.native_surface.get_id() as int); + }); - buffer - } - GpuGraphicsContext => { - draw_target.make_current(); - let StolenGLResources { - surface: native_surface - } = draw_target.steal_gl_resources().unwrap(); - - // We mark the native surface as not leaking in case the surfaces - // die on their way to the compositor task. - let mut native_surface: NativeSurface = - NativeSurfaceAzureMethods::from_azure_surface(native_surface); - native_surface.mark_wont_leak(); - - ~LayerBuffer { - native_surface: native_surface, - rect: tile.page_rect, - screen_pos: tile.screen_rect, - resolution: scale, - stride: (width * 4) as uint - } + buffer + } + GpuGraphicsContext => { + draw_target.make_current(); + let StolenGLResources { + surface: native_surface + } = draw_target.steal_gl_resources().unwrap(); + + // We mark the native surface as not leaking in case the surfaces + // die on their way to the compositor task. + let mut native_surface: NativeSurface = + NativeSurfaceAzureMethods::from_azure_surface(native_surface); + native_surface.mark_wont_leak(); + + ~LayerBuffer { + native_surface: native_surface, + rect: tile.page_rect, + screen_pos: tile.screen_rect, + resolution: scale, + stride: (width * 4) as uint } - }; - - new_buffers.push(buffer); - } - }); + } + }; + + new_buffers.push(buffer); + } let layer_buffer_set = ~LayerBufferSet { buffers: new_buffers, @@ -392,7 +439,7 @@ impl<C: RenderListener + Send,T:Send+Freeze> RenderTask<C,T> { debug!("render_task: returning surface"); if self.paint_permission { - self.compositor.paint(self.id, layer_buffer_set, self.epoch); + self.compositor.paint(self.id, render_layer.id, layer_buffer_set, self.epoch); } else { debug!("render_task: RendererReadyMsg send"); let ConstellationChan(ref mut c) = self.constellation_chan; diff --git a/src/components/main/compositing/compositor.rs b/src/components/main/compositing/compositor.rs index 70655817fee..95d8836bc21 100644 --- a/src/components/main/compositing/compositor.rs +++ b/src/components/main/compositing/compositor.rs @@ -5,16 +5,13 @@ use constellation::SendableFrameTree; use compositing::compositor_layer::CompositorLayer; use compositing::*; - +use pipeline::CompositionPipeline; use platform::{Application, Window}; - -use windowing::{WindowEvent, WindowMethods, - WindowNavigateMsg, - IdleWindowEvent, RefreshWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, - MouseWindowEventClass, MouseWindowMoveEventClass,ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, - FinishedWindowEvent, QuitWindowEvent, - MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent}; - +use windowing::{FinishedWindowEvent, IdleWindowEvent, LoadUrlWindowEvent, MouseWindowClickEvent}; +use windowing::{MouseWindowEvent, MouseWindowEventClass, MouseWindowMouseDownEvent}; +use windowing::{MouseWindowMouseUpEvent, MouseWindowMoveEventClass, NavigationWindowEvent}; +use windowing::{QuitWindowEvent, RefreshWindowEvent, ResizeWindowEvent, ScrollWindowEvent}; +use windowing::{WindowEvent, WindowMethods, WindowNavigateMsg, ZoomWindowEvent}; use azure::azure_hl::{SourceSurfaceMethods, Color}; use azure::azure_hl; @@ -29,8 +26,10 @@ use layers::rendergl::RenderContext; use layers::scene::Scene; use opengles::gl2; use png; -use servo_msg::compositor_msg::{Blank, Epoch, FinishedLoading, IdleRenderState, LayerBufferSet, ReadyState, RenderState}; -use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, NavigateMsg, ResizedWindowMsg, LoadUrlMsg, PipelineId}; +use servo_msg::compositor_msg::{Blank, Epoch, FinishedLoading, IdleRenderState, LayerBufferSet}; +use servo_msg::compositor_msg::{LayerId, ReadyState, RenderState, ScrollPolicy, Scrollable}; +use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg, NavigateMsg}; +use servo_msg::constellation_msg::{PipelineId, ResizedWindowMsg}; use servo_msg::constellation_msg; use servo_util::opts::Opts; use servo_util::time::{profile, ProfilerChan, Timer}; @@ -55,6 +54,9 @@ pub struct IOCompositor { /// The root ContainerLayer. root_layer: Rc<ContainerLayer>, + /// The root pipeline. + root_pipeline: Option<CompositionPipeline>, + /// The canvas to paint a page. scene: Scene, @@ -110,7 +112,6 @@ pub struct IOCompositor { } impl IOCompositor { - pub fn new(app: &Application, opts: Opts, port: Port<Msg>, @@ -131,6 +132,7 @@ impl IOCompositor { opts: opts, context: rendergl::init_render_context(), root_layer: root_layer.clone(), + root_pipeline: None, scene: Scene(ContainerLayerKind(root_layer), window_size, identity()), window_size: Size2D(window_size.width as uint, window_size.height as uint), graphics_context: CompositorTask::create_graphics_context(), @@ -253,11 +255,10 @@ impl IOCompositor { self.change_render_state(render_state); } - (Data(SetUnRenderedColor(_id, color)), false) => { - self.set_unrendered_color(_id, color); + (Data(SetUnRenderedColor(pipeline_id, layer_id, color)), false) => { + self.set_unrendered_color(pipeline_id, layer_id, color); } - (Data(SetIds(frame_tree, response_chan, new_constellation_chan)), _) => { self.set_ids(frame_tree, response_chan, new_constellation_chan); } @@ -266,32 +267,44 @@ impl IOCompositor { chan.send(Some(azure_hl::current_graphics_metadata())); } - (Data(NewLayer(_id, new_size)), false) => { - self.create_new_layer(_id, new_size); + (Data(CreateRootCompositorLayerIfNecessary(pipeline_id, layer_id, size)), + false) => { + self.create_root_compositor_layer_if_necessary(pipeline_id, layer_id, size); } - (Data(SetLayerPageSize(id, new_size, epoch)), false) => { - self.set_layer_page_size(id, new_size, epoch); + (Data(CreateDescendantCompositorLayerIfNecessary(pipeline_id, + layer_id, + rect, + scroll_behavior)), + false) => { + self.create_descendant_compositor_layer_if_necessary(pipeline_id, + layer_id, + rect, + scroll_behavior); } - (Data(SetLayerClipRect(id, new_rect)), false) => { - self.set_layer_clip_rect(id, new_rect); + (Data(SetLayerPageSize(pipeline_id, layer_id, new_size, epoch)), false) => { + self.set_layer_page_size(pipeline_id, layer_id, new_size, epoch); } - (Data(DeleteLayer(id)), _) => { + (Data(SetLayerClipRect(pipeline_id, layer_id, new_rect)), false) => { + self.set_layer_clip_rect(pipeline_id, layer_id, new_rect); + } + + (Data(DeleteLayerGroup(id)), _) => { self.delete_layer(id); } - (Data(Paint(id, new_layer_buffer_set, epoch)), false) => { - self.paint(id, new_layer_buffer_set, epoch); + (Data(Paint(pipeline_id, layer_id, new_layer_buffer_set, epoch)), false) => { + self.paint(pipeline_id, layer_id, new_layer_buffer_set, epoch); } - (Data(InvalidateRect(id, rect)), false) => { - self.invalidate_rect(id, rect); + (Data(InvalidateRect(pipeline_id, layer_id, rect)), false) => { + self.invalidate_rect(pipeline_id, layer_id, rect); } - (Data(ScrollFragmentPoint(id, point)), false) => { - self.scroll_fragment_to_point(id, point); + (Data(ScrollFragmentPoint(pipeline_id, layer_id, point)), false) => { + self.scroll_fragment_to_point(pipeline_id, layer_id, point); } (Data(LoadComplete(..)), false) => { @@ -313,11 +326,10 @@ impl IOCompositor { } } - fn set_unrendered_color(&mut self, _id: PipelineId, color: Color) { + // FIXME(pcwalton): Take the pipeline ID and layer ID into account. + fn set_unrendered_color(&mut self, _: PipelineId, _: LayerId, color: Color) { match self.compositor_layer { - Some(ref mut layer) => { - layer.unrendered_color = color; - } + Some(ref mut layer) => layer.unrendered_color = color, None => {} } } @@ -328,31 +340,7 @@ impl IOCompositor { new_constellation_chan: ConstellationChan) { response_chan.send(()); - // This assumes there is at most one child, which should be the case. - // NOTE: work around borrowchk - { - let tmp = self.root_layer.borrow().first_child.borrow(); - match *tmp.get() { - Some(ref old_layer) => ContainerLayer::remove_child(self.root_layer.clone(), - old_layer.clone()), - None => {} - } - } - - let layer = CompositorLayer::from_frame_tree(frame_tree, - self.opts.tile_size, - Some(10000000u), - self.opts.cpu_painting); - ContainerLayer::add_child_start(self.root_layer.clone(), - ContainerLayerKind(layer.root_layer.clone())); - - // If there's already a root layer, destroy it cleanly. - match self.compositor_layer { - None => {} - Some(ref mut compositor_layer) => compositor_layer.clear_all(), - } - - self.compositor_layer = Some(layer); + self.root_pipeline = Some(frame_tree.pipeline.clone()); // Initialize the new constellation channel by sending it the root window size. let window_size = self.window.borrow().size(); @@ -366,39 +354,81 @@ impl IOCompositor { self.constellation_chan = new_constellation_chan; } - fn create_new_layer(&mut self, _id: PipelineId, new_size: Size2D<f32>) { - // FIXME: This should create an additional layer instead of replacing the current one. - // Once ResizeLayer messages are set up, we can switch to the new functionality. - - let p = match self.compositor_layer { - Some(ref compositor_layer) => compositor_layer.pipeline.clone(), - None => fail!("Compositor: Received new layer without initialized pipeline"), + // FIXME(pcwalton): Take the pipeline ID into account. + fn create_root_compositor_layer_if_necessary(&mut self, + _: PipelineId, + layer_id: LayerId, + size: Size2D<f32>) { + let (root_pipeline, root_layer_id) = match self.compositor_layer { + Some(ref compositor_layer) => { + (compositor_layer.pipeline.clone(), compositor_layer.id_of_first_child()) + } + None => { + match self.root_pipeline { + Some(ref root_pipeline) => (root_pipeline.clone(), LayerId::null()), + None => fail!("Compositor: Received new layer without initialized pipeline"), + } + } }; - let page_size = Size2D(new_size.width as f32, new_size.height as f32); - let new_layer = CompositorLayer::new(p, - Some(page_size), - self.opts.tile_size, - Some(10000000u), - self.opts.cpu_painting); - { - let current_child = self.root_layer.borrow().first_child.borrow(); - // This assumes there is at most one child, which should be the case. - match *current_child.get() { - Some(ref old_layer) => ContainerLayer::remove_child(self.root_layer.clone(), - old_layer.clone()), - None => {} + if layer_id != root_layer_id { + let root_pipeline_id = root_pipeline.id; + let mut new_layer = CompositorLayer::new_root(root_pipeline, + size, + self.opts.tile_size, + self.opts.cpu_painting); + + { + let current_child = self.root_layer.borrow().first_child.borrow(); + match *current_child.get() { + None => {} + Some(ref old_layer) => { + ContainerLayer::remove_child(self.root_layer.clone(), old_layer.clone()) + } + } } + + assert!(new_layer.add_child_if_necessary(self.root_layer.clone(), + root_pipeline_id, + new_layer.id, + layer_id, + Rect(Point2D(0f32, 0f32), size), + size, + Scrollable)); + + ContainerLayer::add_child_start(self.root_layer.clone(), + ContainerLayerKind(new_layer.root_layer.clone())); + self.compositor_layer = Some(new_layer); } - ContainerLayer::add_child_start(self.root_layer.clone(), - ContainerLayerKind(new_layer.root_layer.clone())); - self.compositor_layer = Some(new_layer); + + self.ask_for_tiles(); + } + + fn create_descendant_compositor_layer_if_necessary(&mut self, + pipeline_id: PipelineId, + layer_id: LayerId, + rect: Rect<f32>, + scroll_policy: ScrollPolicy) { + match self.compositor_layer { + Some(ref mut compositor_layer) => { + assert!(compositor_layer.add_child_if_necessary(self.root_layer.clone(), + pipeline_id, + compositor_layer.id, + layer_id, + rect, + compositor_layer.page_size + .unwrap(), + scroll_policy)) + } + None => fail!("Compositor: Received new layer without initialized pipeline"), + }; self.ask_for_tiles(); } fn set_layer_page_size(&mut self, - id: PipelineId, + pipeline_id: PipelineId, + layer_id: LayerId, new_size: Size2D<f32>, epoch: Epoch) { let (ask, move): (bool, bool) = match self.compositor_layer { @@ -407,8 +437,10 @@ impl IOCompositor { let world_zoom = self.world_zoom; let page_window = Size2D(window_size.width as f32 / world_zoom, window_size.height as f32 / world_zoom); - layer.resize(id, new_size, page_window, epoch); - let move = self.fragment_point.take().map_or(false, |point| layer.move(point, page_window)); + layer.resize(pipeline_id, layer_id, new_size, page_window, epoch); + let move = self.fragment_point.take().map_or(false, |point| { + layer.move(pipeline_id, layer_id, point, page_window) + }); (true, move) } @@ -421,10 +453,13 @@ impl IOCompositor { } } - fn set_layer_clip_rect(&mut self, id: PipelineId, new_rect: Rect<f32>) { + fn set_layer_clip_rect(&mut self, + pipeline_id: PipelineId, + layer_id: LayerId, + new_rect: Rect<f32>) { let ask: bool = match self.compositor_layer { Some(ref mut layer) => { - assert!(layer.set_clipping_rect(id, new_rect)); + assert!(layer.set_clipping_rect(pipeline_id, layer_id, new_rect)); true } None => { @@ -454,10 +489,11 @@ impl IOCompositor { } fn paint(&mut self, - id: PipelineId, + pipeline_id: PipelineId, + layer_id: LayerId, new_layer_buffer_set: ~LayerBufferSet, epoch: Epoch) { - debug!("osmain: received new frame"); + debug!("compositor received new frame"); // From now on, if we destroy the buffers, they will leak. let mut new_layer_buffer_set = new_layer_buffer_set; @@ -466,27 +502,29 @@ impl IOCompositor { match self.compositor_layer { Some(ref mut layer) => { assert!(layer.add_buffers(&self.graphics_context, - id, + pipeline_id, + layer_id, new_layer_buffer_set, epoch).is_none()); self.recomposite = true; } None => { - fail!("Compositor: given paint command with no CompositorLayer initialized"); + fail!("compositor given paint command with no CompositorLayer initialized"); } } + // TODO: Recycle the old buffers; send them back to the renderer to reuse if // it wishes. } - fn invalidate_rect(&mut self, id: PipelineId, rect: Rect<uint>) { + fn invalidate_rect(&mut self, pipeline_id: PipelineId, layer_id: LayerId, rect: Rect<uint>) { let ask: bool = match self.compositor_layer { Some(ref mut layer) => { let point = Point2D(rect.origin.x as f32, rect.origin.y as f32); let size = Size2D(rect.size.width as f32, rect.size.height as f32); - layer.invalidate_rect(id, Rect(point, size)); + layer.invalidate_rect(pipeline_id, layer_id, Rect(point, size)); true } None => { @@ -500,14 +538,18 @@ impl IOCompositor { } } - fn scroll_fragment_to_point(&mut self, id: PipelineId, point: Point2D<f32>) { + fn scroll_fragment_to_point(&mut self, + pipeline_id: PipelineId, + layer_id: LayerId, + point: Point2D<f32>) { let world_zoom = self.world_zoom; let page_window = Size2D(self.window_size.width as f32 / world_zoom, self.window_size.height as f32 / world_zoom); + let (ask, move): (bool, bool) = match self.compositor_layer { - Some(ref mut layer) if layer.pipeline.id == id && !layer.hidden => { + Some(ref mut layer) if layer.pipeline.id == pipeline_id && !layer.hidden => { - (true, layer.move(point, page_window)) + (true, layer.move(pipeline_id, layer_id, point, page_window)) } Some(_) | None => { self.fragment_point = Some(point); @@ -630,7 +672,7 @@ impl IOCompositor { self.window_size.height as f32 / world_zoom); let mut scroll = false; for layer in self.compositor_layer.mut_iter() { - scroll = layer.scroll(page_delta, page_cursor, page_window) || scroll; + scroll = layer.handle_scroll_event(page_delta, page_cursor, page_window) || scroll; } self.recomposite_if(scroll); self.ask_for_tiles(); @@ -656,7 +698,7 @@ impl IOCompositor { let page_window = Size2D(window_size.width as f32 / world_zoom, window_size.height as f32 / world_zoom); for layer in self.compositor_layer.mut_iter() { - layer.scroll(page_delta, page_cursor, page_window); + layer.handle_scroll_event(page_delta, page_cursor, page_window); } self.recomposite = true; @@ -679,7 +721,9 @@ impl IOCompositor { for layer in self.compositor_layer.mut_iter() { if !layer.hidden { let rect = Rect(Point2D(0f32, 0f32), window_size_page); - let recomposite = layer.get_buffer_request(&self.graphics_context, rect, world_zoom) || + let recomposite = layer.get_buffer_request(&self.graphics_context, + rect, + world_zoom) || self.recomposite; self.recomposite = recomposite; } else { @@ -756,3 +800,4 @@ impl IOCompositor { self.recomposite = result || self.recomposite; } } + diff --git a/src/components/main/compositing/compositor_layer.rs b/src/components/main/compositing/compositor_layer.rs index cfea4af009c..6702d8da5db 100644 --- a/src/components/main/compositing/compositor_layer.rs +++ b/src/components/main/compositing/compositor_layer.rs @@ -3,42 +3,56 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use compositing::quadtree::{Quadtree, Normal, Invalid, Hidden}; -use constellation::{SendableChildFrameTree, SendableFrameTree}; +use pipeline::CompositionPipeline; +use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent}; +use windowing::{MouseWindowMouseUpEvent}; + +use azure::azure_hl::Color; use geom::matrix::identity; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use gfx::render_task::{ReRenderMsg, UnusedBufferMsg}; +use gfx; use layers::layers::{ContainerLayerKind, ContainerLayer, Flip, NoFlip, TextureLayer}; use layers::layers::TextureLayerKind; -#[cfg(target_os="macos")] -#[cfg(target_os="android")] -use layers::layers::VerticalFlip; use layers::platform::surface::{NativeCompositingGraphicsContext, NativeSurfaceMethods}; use layers::texturegl::{Texture, TextureTarget}; -#[cfg(target_os="macos")] use layers::texturegl::TextureTargetRectangle; -use pipeline::CompositionPipeline; use script::dom::event::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent}; use script::script_task::{ScriptChan, SendEventMsg}; -use servo_msg::compositor_msg::{LayerBuffer, LayerBufferSet, Epoch, Tile}; +use servo_msg::compositor_msg::{Epoch, FixedPosition, LayerBuffer, LayerBufferSet, LayerId}; +use servo_msg::compositor_msg::{ScrollPolicy, Tile}; use servo_msg::constellation_msg::PipelineId; use std::cmp; use std::rc::Rc; -use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent}; -use windowing::{MouseWindowMouseUpEvent}; -use azure::azure_hl::Color; -use gfx; +#[cfg(target_os="macos")] +#[cfg(target_os="android")] +use layers::layers::VerticalFlip; #[cfg(not(target_os="macos"))] use layers::texturegl::TextureTarget2D; +#[cfg(target_os="macos")] +use layers::texturegl::TextureTargetRectangle; + +/// The amount of memory usage allowed per layer. +static MAX_TILE_MEMORY_PER_LAYER: uint = 10000000; /// The CompositorLayer represents an element on a page that has a unique scroll /// or animation behavior. This can include absolute positioned elements, iframes, etc. /// Each layer can also have child layers. +/// +/// FIXME(pcwalton): This should be merged with the concept of a layer in `rust-layers` and +/// ultimately removed, except as a set of helper methods on `rust-layers` layers. pub struct CompositorLayer { /// This layer's pipeline. BufferRequests and mouse events will be sent through this. pipeline: CompositionPipeline, + /// The ID of this layer within the pipeline. + id: LayerId, + + /// The bounds of this layer in terms of its parent (a.k.a. the scissor box). + bounds: Rect<f32>, + /// The size of the underlying page in page coordinates. This is an option /// because we may not know the size of the page until layout is finished completely. /// if we have no size yet, the layer is hidden until a size message is recieved. @@ -70,17 +84,20 @@ pub struct CompositorLayer { epoch: Epoch, /// The behavior of this layer when a scroll message is received. - scroll_behavior: ScrollBehavior, + wants_scroll_events: WantsScrollEventsFlag, + + /// Whether an ancestor layer that receives scroll events moves this layer. + scroll_policy: ScrollPolicy, /// True if CPU rendering is enabled, false if we're using GPU rendering. cpu_painting: bool, /// The color to use for the unrendered-content void - unrendered_color: Color + unrendered_color: Color, } /// Helper struct for keeping CompositorLayer children organized. -struct CompositorLayerChild { +pub struct CompositorLayerChild { /// The child itself. child: ~CompositorLayer, /// A ContainerLayer managed by the parent node. This deals with clipping and @@ -95,13 +112,28 @@ enum MaybeQuadtree { NoTree(uint, Option<uint>), } -/// Determines the behavior of the layer when a scroll message is recieved. -enum ScrollBehavior { - /// Normal scrolling behavior. - Scroll, - /// Scrolling messages targeted at this layer are ignored, but can be - /// passed on to child layers. - FixedPosition, +impl MaybeQuadtree { + fn tile_size(&self) -> uint { + match *self { + Tree(ref quadtree) => quadtree.max_tile_size, + NoTree(tile_size, _) => tile_size, + } + } +} + +#[deriving(Eq, Clone)] +pub enum WantsScrollEventsFlag { + WantsScrollEvents, + DoesntWantScrollEvents, +} + +fn create_container_layer_from_rect(rect: Rect<f32>) -> Rc<ContainerLayer> { + let container = Rc::new(ContainerLayer()); + container.borrow().scissor.set(Some(rect)); + container.borrow().common.with_mut(|common| { + common.transform = identity().translate(rect.origin.x, rect.origin.y, 0f32); + }); + container } trait Clampable { @@ -124,79 +156,155 @@ impl Clampable for f32 { impl CompositorLayer { - /// Creates a new CompositorLayer with an optional page size. If no page size is given, - /// the layer is initially hidden and initialized without a quadtree. - pub fn new(pipeline: CompositionPipeline, - page_size: Option<Size2D<f32>>, - tile_size: uint, - max_mem: Option<uint>, - cpu_painting: bool) - -> CompositorLayer { + /// Creates a new `CompositorLayer`. + fn new(pipeline: CompositionPipeline, + layer_id: LayerId, + bounds: Rect<f32>, + page_size: Option<Size2D<f32>>, + tile_size: uint, + cpu_painting: bool, + wants_scroll_events: WantsScrollEventsFlag, + scroll_policy: ScrollPolicy) + -> CompositorLayer { CompositorLayer { pipeline: pipeline, + id: layer_id, + bounds: bounds, page_size: page_size, scroll_offset: Point2D(0f32, 0f32), children: ~[], quadtree: match page_size { - None => NoTree(tile_size, max_mem), - Some(page_size) => Tree(Quadtree::new(Size2D(page_size.width as uint, - page_size.height as uint), - tile_size, - max_mem)), + None => NoTree(tile_size, Some(MAX_TILE_MEMORY_PER_LAYER)), + Some(page_size) => { + Tree(Quadtree::new(Size2D(page_size.width as uint, page_size.height as uint), + tile_size, + Some(MAX_TILE_MEMORY_PER_LAYER))) + } }, root_layer: Rc::new(ContainerLayer()), hidden: true, epoch: Epoch(0), - scroll_behavior: Scroll, + wants_scroll_events: wants_scroll_events, + scroll_policy: scroll_policy, cpu_painting: cpu_painting, unrendered_color: gfx::color::rgba(0.0, 0.0, 0.0, 0.0), } } - /// Constructs a CompositorLayer tree from a frame tree. - pub fn from_frame_tree(frame_tree: SendableFrameTree, - tile_size: uint, - max_mem: Option<uint>, - cpu_painting: bool) - -> CompositorLayer { - let SendableFrameTree { pipeline, children } = frame_tree; - let mut layer = CompositorLayer::new(pipeline, None, tile_size, max_mem, cpu_painting); - layer.children = (children.move_iter().map(|child| { - let SendableChildFrameTree { frame_tree, rect } = child; - let container = Rc::new(ContainerLayer()); - match rect { - Some(rect) => { - container.borrow().scissor.set(Some(rect)); - container.borrow().common.with_mut(|common| common.transform = identity().translate(rect.origin.x, - rect.origin.y, - 0f32)); - } - None => {} - } - - let child_layer = ~CompositorLayer::from_frame_tree(frame_tree, - tile_size, - max_mem, - cpu_painting); - ContainerLayer::add_child_start(container.clone(), ContainerLayerKind(child_layer.root_layer.clone())); + /// Creates a new root `CompositorLayer` bound to a composition pipeline with an optional page + /// size. If no page size is given, the layer is initially hidden and initialized without a + /// quadtree. + pub fn new_root(pipeline: CompositionPipeline, + page_size: Size2D<f32>, + tile_size: uint, + cpu_painting: bool) + -> CompositorLayer { + CompositorLayer { + pipeline: pipeline, + id: LayerId::null(), + bounds: Rect(Point2D(0f32, 0f32), page_size), + page_size: Some(page_size), + scroll_offset: Point2D(0f32, 0f32), + children: ~[], + quadtree: NoTree(tile_size, Some(MAX_TILE_MEMORY_PER_LAYER)), + root_layer: Rc::new(ContainerLayer()), + hidden: false, + epoch: Epoch(0), + wants_scroll_events: WantsScrollEvents, + scroll_policy: FixedPosition, + cpu_painting: cpu_painting, + unrendered_color: gfx::color::rgba(0.0, 0.0, 0.0, 0.0), + } + } + + /// Adds a child layer to the layer with the given ID and the given pipeline, if it doesn't + /// exist yet. The child layer will have the same pipeline, tile size, memory limit, and CPU + /// painting status as its parent. + /// + /// Returns: + /// * True if the layer was added; + /// * True if the layer was not added because it already existed; + /// * False if the layer could not be added because no suitable parent layer with the given + /// ID and pipeline could be found. + pub fn add_child_if_necessary(&mut self, + container_layer: Rc<ContainerLayer>, + pipeline_id: PipelineId, + parent_layer_id: LayerId, + child_layer_id: LayerId, + rect: Rect<f32>, + page_size: Size2D<f32>, + scroll_policy: ScrollPolicy) + -> bool { + if self.pipeline.id != pipeline_id || self.id != parent_layer_id { + return self.children.mut_iter().any(|kid_holder| { + kid_holder.child.add_child_if_necessary(kid_holder.container.clone(), + pipeline_id, + parent_layer_id, + child_layer_id, + rect, + page_size, + scroll_policy) + }) + } - CompositorLayerChild { - child: child_layer, - container: container, + // See if we've already made this child layer. + for kid_holder in self.children.iter() { + if kid_holder.child.pipeline.id == pipeline_id && + kid_holder.child.id == child_layer_id { + return true } - })).collect(); - layer.set_occlusions(); - layer + } + + let mut kid = ~CompositorLayer::new(self.pipeline.clone(), + child_layer_id, + rect, + Some(page_size), + self.quadtree.tile_size(), + self.cpu_painting, + DoesntWantScrollEvents, + scroll_policy); + + kid.hidden = false; + + // Place the kid's layer in a container... + let kid_container = create_container_layer_from_rect(rect); + ContainerLayer::add_child_start(kid_container.clone(), + ContainerLayerKind(kid.root_layer.clone())); + + // ...and add *that* container as a child of the container passed in. + ContainerLayer::add_child_end(container_layer, + ContainerLayerKind(kid_container.clone())); + + self.children.push(CompositorLayerChild { + child: kid, + container: kid_container, + }); + true } - // Move the layer by as relative specified amount in page coordinates. Does not change - // the position of the layer relative to its parent. This also takes in a cursor position - // to see if the mouse is over child layers first. If a layer successfully scrolled, returns - // true; otherwise returns false, so a parent layer can scroll instead. - pub fn scroll(&mut self, delta: Point2D<f32>, cursor: Point2D<f32>, window_size: Size2D<f32>) - -> bool { + /// Move the layer's descendants that don't want scroll events and scroll by a relative + /// specified amount in page coordinates. This also takes in a cursor position to see if the + /// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise + /// returns false, so a parent layer can scroll instead. + pub fn handle_scroll_event(&mut self, + delta: Point2D<f32>, + cursor: Point2D<f32>, + window_size: Size2D<f32>) + -> bool { + // If this layer is hidden, neither it nor its children will scroll. + if self.hidden { + return false + } + + // If this layer doesn't want scroll events, neither it nor its children can handle scroll + // events. + if self.wants_scroll_events != WantsScrollEvents { + return false + } + + // Allow children to scroll. let cursor = cursor - self.scroll_offset; - for child in self.children.mut_iter().filter(|x| !x.child.hidden) { + for child in self.children.mut_iter() { // NOTE: work around borrowchk let tmp = child.container.borrow().scissor.borrow(); match *tmp.get() { @@ -206,44 +314,84 @@ impl CompositorLayer { Some(rect) => { if cursor.x >= rect.origin.x && cursor.x < rect.origin.x + rect.size.width && cursor.y >= rect.origin.y && cursor.y < rect.origin.y + rect.size.height - && child.child.scroll(delta, cursor - rect.origin, rect.size) { - return true; + && child.child.handle_scroll_event(delta, + cursor - rect.origin, + rect.size) { + return true } } } } // This scroll event is mine! - match self.scroll_behavior { - Scroll => { - // Scroll this layer! - let old_origin = self.scroll_offset; - self.scroll_offset = self.scroll_offset + delta; - - // bounds checking - let page_size = match self.page_size { - Some(size) => size, - None => fail!("CompositorLayer: tried to scroll with no page size set"), - }; - let min_x = cmp::min(window_size.width - page_size.width, 0.0); - self.scroll_offset.x = self.scroll_offset.x.clamp(&min_x, &0.0); - let min_y = cmp::min(window_size.height - page_size.height, 0.0); - self.scroll_offset.y = self.scroll_offset.y.clamp(&min_y, &0.0); - - // check to see if we scrolled - if old_origin - self.scroll_offset == Point2D(0f32, 0f32) { - return false; - } + // Scroll this layer! + let old_origin = self.scroll_offset; + self.scroll_offset = self.scroll_offset + delta; + + // bounds checking + let page_size = match self.page_size { + Some(size) => size, + None => fail!("CompositorLayer: tried to scroll with no page size set"), + }; + let min_x = cmp::min(window_size.width - page_size.width, 0.0); + self.scroll_offset.x = self.scroll_offset.x.clamp(&min_x, &0.0); + let min_y = cmp::min(window_size.height - page_size.height, 0.0); + self.scroll_offset.y = self.scroll_offset.y.clamp(&min_y, &0.0); - self.root_layer.borrow().common.with_mut(|common| common.set_transform(identity().translate(self.scroll_offset.x, - self.scroll_offset.y, - 0.0))); - true + if old_origin - self.scroll_offset == Point2D(0f32, 0f32) { + return false + } + + self.scroll(self.scroll_offset) + } + + #[allow(dead_code)] + fn dump_layer_tree(&self, layer: Rc<ContainerLayer>, indent: ~str) { + { + let scissor = layer.borrow().scissor.borrow(); + println!("{}scissor {:?}", indent, *scissor.get()); + } + for kid in layer.borrow().children() { + match kid { + ContainerLayerKind(ref container_layer) => { + self.dump_layer_tree((*container_layer).clone(), indent + " "); + } + TextureLayerKind(_) => { + println!("{} (texture layer)", indent); + } } - FixedPosition => false, // Ignore this scroll event. } } + /// Actually scrolls the descendants of a layer that scroll. This is called by + /// `handle_scroll_event` above when it determines that a layer wants to scroll. + fn scroll(&mut self, scroll_offset: Point2D<f32>) -> bool { + let mut result = false; + + // Only scroll this layer if it's not fixed-positioned. + if self.scroll_policy != FixedPosition { + // Scroll this layer! + self.scroll_offset = scroll_offset; + + self.root_layer + .borrow() + .common + .with_mut(|common| { + common.set_transform(identity().translate(self.scroll_offset.x, + self.scroll_offset.y, + 0.0)) + }); + + result = true + } + + for kid_holder in self.children.mut_iter() { + result = kid_holder.child.scroll(scroll_offset) || result; + } + + result + } + // Takes in a MouseWindowEvent, determines if it should be passed to children, and // sends the event off to the appropriate pipeline. NB: the cursor position is in // page coordinates. @@ -282,43 +430,49 @@ impl CompositorLayer { chan.try_send(SendEventMsg(self.pipeline.id.clone(), message)); } - // Given the current window size, determine which tiles need to be (re)rendered - // and sends them off the the appropriate renderer. - // Returns a bool that is true if the scene should be repainted. + // Given the current window size, determine which tiles need to be (re-)rendered and sends them + // off the the appropriate renderer. Returns true if and only if the scene should be repainted. pub fn get_buffer_request(&mut self, graphics_context: &NativeCompositingGraphicsContext, window_rect: Rect<f32>, scale: f32) -> bool { - let rect = Rect(Point2D(-self.scroll_offset.x + window_rect.origin.x, - -self.scroll_offset.y + window_rect.origin.y), - window_rect.size); - let mut redisplay: bool; - { // block here to prevent double mutable borrow of self - let quadtree = match self.quadtree { - NoTree(..) => fail!("CompositorLayer: cannot get buffer request for {:?}, - no quadtree initialized", self.pipeline.id), - Tree(ref mut quadtree) => quadtree, - }; - let (request, unused) = quadtree.get_tile_rects_page(rect, scale); - redisplay = !unused.is_empty(); // workaround to make redisplay visible outside block - if redisplay { // send back unused tiles - self.pipeline.render_chan.try_send(UnusedBufferMsg(unused)); - } - if !request.is_empty() { // ask for tiles - self.pipeline.render_chan.try_send(ReRenderMsg(request, scale, self.epoch)); + let mut redisplay = false; + match self.quadtree { + NoTree(..) => {} + Tree(ref mut quadtree) => { + let (request, unused) = quadtree.get_tile_rects_page(window_rect, scale); + + // Workaround to make redisplay visible outside block. + redisplay = !unused.is_empty(); + if redisplay { + // Send back unused tiles. + self.pipeline.render_chan.try_send(UnusedBufferMsg(unused)); + } + if !request.is_empty() { + // Ask for tiles. + // + // FIXME(pcwalton): We may want to batch these up in the case in which one + // page has multiple layers, to avoid the user seeing inconsistent states. + let msg = ReRenderMsg(request, scale, self.id, self.epoch); + self.pipeline.render_chan.try_send(msg); + } } - } + }; + if redisplay { self.build_layer_tree(graphics_context); } + let transform = |x: &mut CompositorLayerChild| -> bool { // NOTE: work around borrowchk let tmp = x.container.borrow().scissor.borrow(); match *tmp.get() { Some(scissor) => { - let new_rect = rect.intersection(&scissor); - match new_rect { + let mut new_rect = window_rect; + new_rect.origin.x = new_rect.origin.x - x.child.scroll_offset.x; + new_rect.origin.y = new_rect.origin.y - x.child.scroll_offset.y; + match new_rect.intersection(&scissor) { Some(new_rect) => { // Child layers act as if they are rendered at (0,0), so we // subtract the layer's (x,y) coords in its containing page @@ -328,13 +482,11 @@ impl CompositorLayer { x.child.get_buffer_request(graphics_context, child_rect, scale) } None => { - false //Layer is offscreen + false // Layer is offscreen } } } - None => { - fail!("CompositorLayer: Child layer not clipped"); - } + None => fail!("child layer not clipped!"), } }; self.children.mut_iter().filter(|x| !x.child.hidden) @@ -347,9 +499,18 @@ impl CompositorLayer { // and clip the layer to the specified size in page coordinates. // If the layer is hidden and has a defined page size, unhide it. // This method returns false if the specified layer is not found. - pub fn set_clipping_rect(&mut self, pipeline_id: PipelineId, new_rect: Rect<f32>) -> bool { - match self.children.iter().position(|x| pipeline_id == x.child.pipeline.id) { + pub fn set_clipping_rect(&mut self, + pipeline_id: PipelineId, + layer_id: LayerId, + new_rect: Rect<f32>) + -> bool { + debug!("compositor_layer: starting set_clipping_rect()"); + match self.children.iter().position(|kid_holder| { + pipeline_id == kid_holder.child.pipeline.id && + layer_id == kid_holder.child.id + }) { Some(i) => { + debug!("compositor_layer: node found for set_clipping_rect()"); let child_node = &mut self.children[i]; child_node.container.borrow().common.with_mut(|common| common.set_transform(identity().translate(new_rect.origin.x, @@ -360,6 +521,7 @@ impl CompositorLayer { let tmp = child_node.container.borrow().scissor.borrow(); tmp.get().clone() }; + child_node.container.borrow().scissor.set(Some(new_rect)); match self.quadtree { NoTree(..) => {} // Nothing to do @@ -382,72 +544,94 @@ impl CompositorLayer { None => { // ID does not match any of our immediate children, so recurse on // descendents (including hidden children) - self.children.mut_iter().map(|x| &mut x.child).any(|x| x.set_clipping_rect(pipeline_id, new_rect)) + self.children + .mut_iter() + .map(|kid_holder| &mut kid_holder.child) + .any(|kid| kid.set_clipping_rect(pipeline_id, layer_id, new_rect)) } } } - // Set the layer's page size. This signals that the renderer is ready for BufferRequests. // If the layer is hidden and has a defined clipping rect, unhide it. // This method returns false if the specified layer is not found. - pub fn resize(&mut self, pipeline_id: PipelineId, new_size: Size2D<f32>, window_size: Size2D<f32>, epoch: Epoch) -> bool { - if self.pipeline.id == pipeline_id { - self.epoch = epoch; - self.page_size = Some(new_size); - match self.quadtree { - Tree(ref mut quadtree) => { - self.pipeline.render_chan.try_send(UnusedBufferMsg(quadtree.resize(new_size.width as uint, - new_size.height as uint))); - } - NoTree(tile_size, max_mem) => { - self.quadtree = Tree(Quadtree::new(Size2D(new_size.width as uint, - new_size.height as uint), - tile_size, - max_mem)) - } + pub fn resize(&mut self, + pipeline_id: PipelineId, + layer_id: LayerId, + new_size: Size2D<f32>, + window_size: Size2D<f32>, + epoch: Epoch) + -> bool { + debug!("compositor_layer: starting resize()"); + if self.pipeline.id != pipeline_id || self.id != layer_id { + return self.resize_helper(pipeline_id, layer_id, new_size, epoch) + } + + debug!("compositor_layer: layer found for resize()"); + self.epoch = epoch; + self.page_size = Some(new_size); + match self.quadtree { + Tree(ref mut quadtree) => { + self.pipeline + .render_chan + .try_send(UnusedBufferMsg(quadtree.resize(new_size.width as uint, + new_size.height as uint))); + } + NoTree(tile_size, max_mem) => { + self.quadtree = Tree(Quadtree::new(Size2D(new_size.width as uint, + new_size.height as uint), + tile_size, + max_mem)) } - // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the cursor position - // to make sure the scroll isn't propagated downwards. - self.scroll(Point2D(0f32, 0f32), Point2D(-1f32, -1f32), window_size); - self.hidden = false; - self.set_occlusions(); - true - } else { - self.resize_helper(pipeline_id, new_size, epoch) } + // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the cursor position + // to make sure the scroll isn't propagated downwards. + self.handle_scroll_event(Point2D(0f32, 0f32), Point2D(-1f32, -1f32), window_size); + self.hidden = false; + self.set_occlusions(); + true } - pub fn move(&mut self, origin: Point2D<f32>, window_size: Size2D<f32>) -> bool { - match self.scroll_behavior { - Scroll => { - // Scroll this layer! - let old_origin = self.scroll_offset; - self.scroll_offset = Point2D(0f32, 0f32) - origin; - - // bounds checking - let page_size = match self.page_size { - Some(size) => size, - None => fail!("CompositorLayer: tried to scroll with no page size set"), - }; - let min_x = cmp::min(window_size.width - page_size.width, 0.0); - self.scroll_offset.x = self.scroll_offset.x.clamp(&min_x, &0.0); - let min_y = cmp::min(window_size.height - page_size.height, 0.0); - self.scroll_offset.y = self.scroll_offset.y.clamp(&min_y, &0.0); - - // check to see if we scrolled - if old_origin - self.scroll_offset == Point2D(0f32, 0f32) { - return false; + pub fn move(&mut self, + pipeline_id: PipelineId, + layer_id: LayerId, + origin: Point2D<f32>, + window_size: Size2D<f32>) + -> bool { + // Search children for the right layer to move. + if self.pipeline.id != pipeline_id || self.id != layer_id { + for kid_holder in self.children.mut_iter() { + if kid_holder.child.move(pipeline_id, layer_id, origin, window_size) { + return true } - - self.root_layer.borrow().common.with_mut(|common| - common.set_transform(identity().translate(self.scroll_offset.x, - self.scroll_offset.y, - 0.0))); - true } - FixedPosition => false // Ignore this scroll event. + return false + } + + if self.wants_scroll_events != WantsScrollEvents { + return false } + + // Scroll this layer! + let old_origin = self.scroll_offset; + self.scroll_offset = Point2D(0f32, 0f32) - origin; + + // bounds checking + let page_size = match self.page_size { + Some(size) => size, + None => fail!("CompositorLayer: tried to scroll with no page size set"), + }; + let min_x = cmp::min(window_size.width - page_size.width, 0.0); + self.scroll_offset.x = self.scroll_offset.x.clamp(&min_x, &0.0); + let min_y = cmp::min(window_size.height - page_size.height, 0.0); + self.scroll_offset.y = self.scroll_offset.y.clamp(&min_y, &0.0); + + // check to see if we scrolled + if old_origin - self.scroll_offset == Point2D(0f32, 0f32) { + return false; + } + + self.scroll(self.scroll_offset) } // Returns whether the layer should be vertically flipped. @@ -479,9 +663,19 @@ impl CompositorLayer { } // A helper method to resize sublayers. - fn resize_helper(&mut self, pipeline_id: PipelineId, new_size: Size2D<f32>, epoch: Epoch) -> bool { - let found = match self.children.iter().position(|x| pipeline_id == x.child.pipeline.id) { + fn resize_helper(&mut self, + pipeline_id: PipelineId, + layer_id: LayerId, + new_size: Size2D<f32>, + epoch: Epoch) + -> bool { + debug!("compositor_layer: starting resize_helper()"); + let found = match self.children.iter().position(|kid_holder| { + pipeline_id == kid_holder.child.pipeline.id && + layer_id == kid_holder.child.id + }) { Some(i) => { + debug!("compositor_layer: layer found for resize_helper()"); let child_node = &mut self.children[i]; let child = &mut child_node.child; child.epoch = epoch; @@ -502,9 +696,11 @@ impl CompositorLayer { let tmp = child_node.container.borrow().scissor.borrow(); match *tmp.get() { Some(scissor) => { - // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the cursor position - // to make sure the scroll isn't propagated downwards. - child.scroll(Point2D(0f32, 0f32), Point2D(-1f32, -1f32), scissor.size); + // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the + // cursor position to make sure the scroll isn't propagated downwards. + child.handle_scroll_event(Point2D(0f32, 0f32), + Point2D(-1f32, -1f32), + scissor.size); child.hidden = false; } None => {} // Nothing to do @@ -516,11 +712,15 @@ impl CompositorLayer { if found { // Boolean flag to get around double borrow of self self.set_occlusions(); - true - } else { - // ID does not match ours, so recurse on descendents (including hidden children) - self.children.mut_iter().map(|x| &mut x.child).any(|x| x.resize_helper(pipeline_id, new_size, epoch)) + return true } + + // If we got here, the layer's ID does not match ours, so recurse on descendents (including + // hidden children). + self.children + .mut_iter() + .map(|kid_holder| &mut kid_holder.child) + .any(|kid_holder| kid_holder.resize_helper(pipeline_id, layer_id, new_size, epoch)) } // Collect buffers from the quadtree. This method IS NOT recursive, so child CompositorLayers @@ -577,7 +777,9 @@ impl CompositorLayer { buffer.native_surface.bind_to_texture(graphics_context, &texture, size); // Make a texture layer and add it. - texture_layer = Rc::new(TextureLayer::new(texture, buffer.screen_pos.size, flip)); + texture_layer = Rc::new(TextureLayer::new(texture, + buffer.screen_pos.size, + flip)); ContainerLayer::add_child_end(self.root_layer.clone(), TextureLayerKind(texture_layer.clone())); None @@ -632,57 +834,66 @@ impl CompositorLayer { pub fn add_buffers(&mut self, graphics_context: &NativeCompositingGraphicsContext, pipeline_id: PipelineId, + layer_id: LayerId, mut new_buffers: ~LayerBufferSet, epoch: Epoch) -> Option<~LayerBufferSet> { - if self.pipeline.id == pipeline_id { - if self.epoch != epoch { - debug!("compositor epoch mismatch: {:?} != {:?}, id: {:?}", - self.epoch, - epoch, - self.pipeline.id); - self.pipeline.render_chan.try_send(UnusedBufferMsg(new_buffers.buffers)); - return None; - } - - { - // Block here to prevent double mutable borrow of self. - let quadtree = match self.quadtree { - NoTree(..) => fail!("CompositorLayer: cannot add buffers, no quadtree initialized"), - Tree(ref mut quadtree) => quadtree, - }; - - let mut unused_tiles = ~[]; - // move_rev_iter is more efficient - for buffer in new_buffers.buffers.move_rev_iter() { - unused_tiles.push_all_move(quadtree.add_tile_pixel(buffer.screen_pos.origin.x, - buffer.screen_pos.origin.y, - buffer.resolution, buffer)); - } - if !unused_tiles.is_empty() { // send back unused buffers - self.pipeline.render_chan.try_send(UnusedBufferMsg(unused_tiles)); + debug!("compositor_layer: starting add_buffers()"); + if self.pipeline.id != pipeline_id || self.id != layer_id { + // ID does not match ours, so recurse on descendents (including hidden children). + for child_layer in self.children.mut_iter() { + match child_layer.child.add_buffers(graphics_context, + pipeline_id, + layer_id, + new_buffers, + epoch) { + None => return None, + Some(buffers) => new_buffers = buffers, } } - self.build_layer_tree(graphics_context); - return None; + + // Not found. Give the caller the buffers back. + return Some(new_buffers) } - // ID does not match ours, so recurse on descendents (including hidden children). - for child_layer in self.children.mut_iter() { - match child_layer.child.add_buffers(graphics_context, - pipeline_id, - new_buffers, - epoch) { - None => return None, - Some(buffers) => new_buffers = buffers, + debug!("compositor_layer: layers found for add_buffers()"); + + if self.epoch != epoch { + debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}", + self.epoch, + epoch, + self.pipeline.id); + self.pipeline.render_chan.try_send(UnusedBufferMsg(new_buffers.buffers)); + return None + } + + { + let quadtree = match self.quadtree { + NoTree(..) => { + fail!("CompositorLayer: cannot add buffers, no quadtree initialized") + } + Tree(ref mut quadtree) => quadtree, + }; + + let mut unused_tiles = ~[]; + // `move_rev_iter` is more efficient than `.move_iter().reverse()`. + for buffer in new_buffers.buffers.move_rev_iter() { + unused_tiles.push_all_move(quadtree.add_tile_pixel(buffer.screen_pos.origin.x, + buffer.screen_pos.origin.y, + buffer.resolution, + buffer)); + } + if !unused_tiles.is_empty() { // send back unused buffers + self.pipeline.render_chan.try_send(UnusedBufferMsg(unused_tiles)); } } - // Not found. Give the caller the buffers back. - Some(new_buffers) + self.build_layer_tree(graphics_context); + return None } - // Deletes a specified sublayer, including hidden children. Returns false if the layer is not found. + // Deletes a specified sublayer, including hidden children. Returns false if the layer is not + // found. pub fn delete(&mut self, graphics_context: &NativeCompositingGraphicsContext, pipeline_id: PipelineId) @@ -717,8 +928,11 @@ impl CompositorLayer { } } - pub fn invalidate_rect(&mut self, pipeline_id: PipelineId, rect: Rect<f32>) -> bool { - if self.pipeline.id == pipeline_id { + pub fn invalidate_rect(&mut self, pipeline_id: PipelineId, layer_id: LayerId, rect: Rect<f32>) + -> bool { + debug!("compositor_layer: starting invalidate_rect()"); + if self.pipeline.id == pipeline_id && layer_id == self.id { + debug!("compositor_layer: layer found for invalidate_rect()"); let quadtree = match self.quadtree { NoTree(..) => return true, // Nothing to do Tree(ref mut quadtree) => quadtree, @@ -727,7 +941,10 @@ impl CompositorLayer { true } else { // ID does not match ours, so recurse on descendents (including hidden children). - self.children.mut_iter().map(|x| &mut x.child).any(|x| x.invalidate_rect(pipeline_id, rect)) + self.children + .mut_iter() + .map(|kid_holder| &mut kid_holder.child) + .any(|kid| kid.invalidate_rect(pipeline_id, layer_id, rect)) } } @@ -773,16 +990,6 @@ impl CompositorLayer { } } - /// Destroys all quadtree tiles of all layers, including child layers, sending the buffers - /// back to the renderer to be destroyed or reused. - pub fn clear_all(&mut self) { - self.clear(); - - for kid in self.children.mut_iter() { - kid.child.clear_all() - } - } - /// Destroys all tiles of all layers, including children, *without* sending them back to the /// renderer. You must call this only when the render task is destined to be going down; /// otherwise, you will leak tiles. @@ -804,5 +1011,12 @@ impl CompositorLayer { kid.child.forget_all_tiles(); } } + + pub fn id_of_first_child(&self) -> LayerId { + for kid_holder in self.children.iter() { + return kid_holder.child.id + } + fail!("no first child!"); + } } diff --git a/src/components/main/compositing/compositor_task.rs b/src/components/main/compositing/compositor_task.rs index 6666fcf4456..cbad826c67a 100644 --- a/src/components/main/compositing/compositor_task.rs +++ b/src/components/main/compositing/compositor_task.rs @@ -13,8 +13,8 @@ use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use layers::platform::surface::{NativeCompositingGraphicsContext, NativeGraphicsMetadata}; -use servo_msg::compositor_msg::{Epoch, RenderListener, LayerBufferSet, RenderState, ReadyState}; -use servo_msg::compositor_msg::ScriptListener; +use servo_msg::compositor_msg::{Epoch, LayerBufferSet, LayerId, LayerMetadata, ReadyState}; +use servo_msg::compositor_msg::{RenderListener, RenderState, ScriptListener, ScrollPolicy}; use servo_msg::constellation_msg::{ConstellationChan, PipelineId}; use servo_util::opts::Opts; use servo_util::time::ProfilerChan; @@ -45,12 +45,15 @@ impl ScriptListener for CompositorChan { self.chan.send(msg); } - fn invalidate_rect(&self, id: PipelineId, rect: Rect<uint>) { - self.chan.send(InvalidateRect(id, rect)); + fn invalidate_rect(&self, pipeline_id: PipelineId, layer_id: LayerId, rect: Rect<uint>) { + self.chan.send(InvalidateRect(pipeline_id, layer_id, rect)); } - fn scroll_fragment_point(&self, id: PipelineId, point: Point2D<f32>) { - self.chan.send(ScrollFragmentPoint(id, point)); + fn scroll_fragment_point(&self, + pipeline_id: PipelineId, + layer_id: LayerId, + point: Point2D<f32>) { + self.chan.send(ScrollFragmentPoint(pipeline_id, layer_id, point)); } fn close(&self) { @@ -72,30 +75,64 @@ impl RenderListener for CompositorChan { port.recv() } - fn paint(&self, id: PipelineId, layer_buffer_set: ~LayerBufferSet, epoch: Epoch) { - self.chan.send(Paint(id, layer_buffer_set, epoch)) + fn paint(&self, + pipeline_id: PipelineId, + layer_id: LayerId, + layer_buffer_set: ~LayerBufferSet, + epoch: Epoch) { + self.chan.send(Paint(pipeline_id, layer_id, layer_buffer_set, epoch)) } - fn new_layer(&self, id: PipelineId, page_size: Size2D<uint>) { + fn create_layer_group_for_pipeline(&self, id: PipelineId, page_size: Size2D<uint>) { let Size2D { width, height } = page_size; - self.chan.send(NewLayer(id, Size2D(width as f32, height as f32))) + self.chan.send(CreateRootCompositorLayerIfNecessary(id, + LayerId::null(), + Size2D(width as f32, height as f32))) } - fn set_layer_page_size_and_color(&self, id: PipelineId, page_size: Size2D<uint>, epoch: Epoch, color: Color) { - let Size2D { width, height } = page_size; - self.chan.send(SetUnRenderedColor(id, color)); - self.chan.send(SetLayerPageSize(id, Size2D(width as f32, height as f32), epoch)) + + fn initialize_layers_for_pipeline(&self, + pipeline_id: PipelineId, + metadata: ~[LayerMetadata], + epoch: Epoch) { + // FIXME(pcwalton): This assumes that the first layer determines the page size, and that + // all other layers are immediate children of it. This is sufficient to handle + // `position: fixed` but will not be sufficient to handle `overflow: scroll` or transforms. + let mut first = true; + for metadata in metadata.iter() { + let origin = Point2D(metadata.rect.origin.x as f32, metadata.rect.origin.y as f32); + let size = Size2D(metadata.rect.size.width as f32, metadata.rect.size.height as f32); + let rect = Rect(origin, size); + if first { + self.chan.send(CreateRootCompositorLayerIfNecessary(pipeline_id, + metadata.id, + size)); + first = false + } else { + self.chan + .send(CreateDescendantCompositorLayerIfNecessary(pipeline_id, + metadata.id, + rect, + metadata.scroll_policy)); + } + + self.chan.send(SetUnRenderedColor(pipeline_id, metadata.id, metadata.color)); + self.chan.send(SetLayerPageSize(pipeline_id, metadata.id, size, epoch)); + } } - fn set_layer_clip_rect(&self, id: PipelineId, new_rect: Rect<uint>) { + fn set_layer_clip_rect(&self, + pipeline_id: PipelineId, + layer_id: LayerId, + new_rect: Rect<uint>) { let new_rect = Rect(Point2D(new_rect.origin.x as f32, new_rect.origin.y as f32), Size2D(new_rect.size.width as f32, new_rect.size.height as f32)); - self.chan.send(SetLayerClipRect(id, new_rect)) + self.chan.send(SetLayerClipRect(pipeline_id, layer_id, new_rect)) } - fn delete_layer(&self, id: PipelineId) { - self.chan.send(DeleteLayer(id)) + fn delete_layer_group(&self, id: PipelineId) { + self.chan.send(DeleteLayerGroup(id)) } fn set_render_state(&self, render_state: RenderState) { @@ -133,29 +170,32 @@ pub enum Msg { /// The headless compositor returns `None`. GetGraphicsMetadata(Chan<Option<NativeGraphicsMetadata>>), - /// Alerts the compositor that there is a new layer to be rendered. - NewLayer(PipelineId, Size2D<f32>), - /// Alerts the compositor that the specified layer's page has changed size. - SetLayerPageSize(PipelineId, Size2D<f32>, Epoch), + /// Tells the compositor to create the root layer for a pipeline if necessary (i.e. if no layer + /// with that ID exists). + CreateRootCompositorLayerIfNecessary(PipelineId, LayerId, Size2D<f32>), + /// Tells the compositor to create a descendant layer for a pipeline if necessary (i.e. if no + /// layer with that ID exists). + CreateDescendantCompositorLayerIfNecessary(PipelineId, LayerId, Rect<f32>, ScrollPolicy), + /// Alerts the compositor that the specified layer has changed size. + SetLayerPageSize(PipelineId, LayerId, Size2D<f32>, Epoch), /// Alerts the compositor that the specified layer's clipping rect has changed. - SetLayerClipRect(PipelineId, Rect<f32>), - /// Alerts the compositor that the specified layer has been deleted. - DeleteLayer(PipelineId), + SetLayerClipRect(PipelineId, LayerId, Rect<f32>), + /// Alerts the compositor that the specified pipeline has been deleted. + DeleteLayerGroup(PipelineId), /// Invalidate a rect for a given layer - InvalidateRect(PipelineId, Rect<uint>), + InvalidateRect(PipelineId, LayerId, Rect<uint>), /// Scroll a page in a window - ScrollFragmentPoint(PipelineId, Point2D<f32>), + ScrollFragmentPoint(PipelineId, LayerId, Point2D<f32>), /// Requests that the compositor paint the given layer buffer set for the given page size. - Paint(PipelineId, ~LayerBufferSet, Epoch), + Paint(PipelineId, LayerId, ~LayerBufferSet, Epoch), /// Alerts the compositor to the current status of page loading. ChangeReadyState(ReadyState), /// Alerts the compositor to the current status of rendering. ChangeRenderState(RenderState), /// Sets the channel to the current layout and render tasks, along with their id SetIds(SendableFrameTree, Chan<()>, ConstellationChan), - - SetUnRenderedColor(PipelineId, Color), - + /// Sets the color of unrendered content for a layer. + SetUnRenderedColor(PipelineId, LayerId, Color), /// The load of a page for a given URL has completed. LoadComplete(PipelineId, Url), } diff --git a/src/components/main/compositing/headless.rs b/src/components/main/compositing/headless.rs index 19c07bc639d..7acc2e2200a 100644 --- a/src/components/main/compositing/headless.rs +++ b/src/components/main/compositing/headless.rs @@ -77,10 +77,11 @@ impl NullCompositor { // we'll notice and think about whether it needs a response, like // SetIds. - NewLayer(..) | SetLayerPageSize(..) | SetLayerClipRect(..) | DeleteLayer(..) | - Paint(..) | InvalidateRect(..) | ChangeReadyState(..) | ChangeRenderState(..)| - ScrollFragmentPoint(..) | SetUnRenderedColor(..) | LoadComplete(..) - => () + CreateRootCompositorLayerIfNecessary(..) | + CreateDescendantCompositorLayerIfNecessary(..) | SetLayerPageSize(..) | + SetLayerClipRect(..) | DeleteLayerGroup(..) | Paint(..) | InvalidateRect(..) | + ChangeReadyState(..) | ChangeRenderState(..) | ScrollFragmentPoint(..) | + SetUnRenderedColor(..) | LoadComplete(..) => () } } } diff --git a/src/components/main/compositing/quadtree.rs b/src/components/main/compositing/quadtree.rs index 6c3cda6e2a4..cd082e6de4b 100644 --- a/src/components/main/compositing/quadtree.rs +++ b/src/components/main/compositing/quadtree.rs @@ -460,9 +460,13 @@ impl<T: Tile> QuadtreeNode<T> { /// treated as invalid as well. /// NOTE: this method will sometimes modify the tree by deleting tiles. /// See the QuadTree function description for more details. - fn get_tile_rects(&mut self, window: Rect<f32>, clip: Size2D<f32>, scale: f32, tile_size: f32, override: bool) -> - (~[BufferRequest], ~[T], int) { - + fn get_tile_rects(&mut self, + window: Rect<f32>, + clip: Size2D<f32>, + scale: f32, + tile_size: f32, + override: bool) + -> (~[BufferRequest], ~[T], int) { let w_x = window.origin.x; let w_y = window.origin.y; let w_width = window.size.width; @@ -470,11 +474,11 @@ impl<T: Tile> QuadtreeNode<T> { let s_x = self.origin.x; let s_y = self.origin.y; let s_size = self.size; - + // if window is outside of visible region, nothing to do if w_x + w_width < s_x || w_x > s_x + s_size - || w_y + w_height < s_y || w_y > s_y + s_size - || w_x >= clip.width || w_y >= clip.height { + || w_y + w_height < s_y || w_y > s_y + s_size + || w_x >= clip.width || w_y >= clip.height { return (~[], ~[], 0); } diff --git a/src/components/main/constellation.rs b/src/components/main/constellation.rs index 4cc8aa33ccf..4894f94aafd 100644 --- a/src/components/main/constellation.rs +++ b/src/components/main/constellation.rs @@ -14,6 +14,7 @@ use script::script_task::{ResizeMsg, ResizeInactiveMsg, ExitPipelineMsg}; use script::layout_interface; use script::layout_interface::LayoutChan; use script::script_task::ScriptChan; +use servo_msg::compositor_msg::LayerId; use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, FailureMsg, Failure, FrameRectMsg}; use servo_msg::constellation_msg::{IFrameSandboxState, IFrameUnsandboxed, InitLoadUrlMsg}; use servo_msg::constellation_msg::{LoadCompleteMsg, LoadIframeUrlMsg, LoadUrlMsg, Msg, NavigateMsg}; @@ -98,15 +99,6 @@ pub struct SendableChildFrameTree { rect: Option<Rect<f32>>, } -// impl SendableFrameTree { -// fn contains(&self, id: PipelineId) -> bool { -// self.pipeline.id == id || -// self.children.iter().any(|&SendableChildFrameTree { frame_tree: ref frame_tree, .. }| { -// frame_tree.contains(id) -// }) -// } -// } - enum ReplaceResult { ReplacedNode(Rc<FrameTree>), OriginalNode(Rc<FrameTree>), @@ -511,11 +503,12 @@ impl Constellation { == subpage_id }; + let frames = self.find_all(pipeline_id); + { // Update a child's frame rect and inform its script task of the change, // if it hasn't been already. Optionally inform the compositor if // resize happens immediately. - let compositor_chan = self.compositor_chan.clone(); let update_child_rect = |child_frame_tree: &mut ChildFrameTree, is_active: bool| { child_frame_tree.rect = Some(rect.clone()); // NOTE: work around borrowchk issues @@ -524,21 +517,19 @@ impl Constellation { let Size2D { width, height } = rect.size; if is_active { let pipeline = pipeline.get().borrow(); - let ScriptChan(ref chan) = pipeline.script_chan; - chan.send(ResizeMsg(pipeline.id, Size2D { + let ScriptChan(ref script_chan) = pipeline.script_chan; + script_chan.send(ResizeMsg(pipeline.id, Size2D { width: width as uint, height: height as uint })); - compositor_chan.send(SetLayerClipRect(pipeline.id, rect)); + self.compositor_chan.send(SetLayerClipRect(pipeline.id, + LayerId::null(), + rect)); } else { let pipeline = pipeline.get().borrow(); - let ScriptChan(ref chan) = pipeline.script_chan; - chan.send(ResizeInactiveMsg(pipeline.id, - Size2D(width as uint, height as uint))); + already_sent.insert(pipeline.id); } - let pipeline = pipeline.get().borrow(); - already_sent.insert(pipeline.id); - } + }; }; // If the subframe is in the current frame tree, the compositor needs the new size @@ -554,7 +545,6 @@ impl Constellation { } // Update all frames with matching pipeline- and subpage-ids - let frames = self.find_all(pipeline_id); for frame_tree in frames.iter() { // NOTE: work around borrowchk issues let mut tmp = frame_tree.borrow().children.borrow_mut(); diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index 53585c47df7..6622af8daa3 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -330,6 +330,36 @@ enum BlockType { FloatNonReplacedType, } +#[deriving(Clone, Eq)] +pub enum MarginsMayCollapseFlag { + MarginsMayCollapse, + MarginsMayNotCollapse, +} + +// Propagates the `layers_needed_for_descendants` flag appropriately from a child. This is called +// as part of height assignment. +// +// If any fixed descendants of kids are present, this kid needs a layer. +// +// FIXME(pcwalton): This is too layer-happy. Like WebKit, we shouldn't do this unless +// the positioned descendants are actually on top of the fixed kids. +// +// TODO(pcwalton): Do this for CSS transforms and opacity too, at least if they're +// animating. +fn propagate_layer_flag_from_child(layers_needed_for_descendants: &mut bool, kid: &mut Flow) { + if kid.is_absolute_containing_block() { + let kid_base = flow::mut_base(kid); + if kid_base.flags_info.flags.needs_layer() { + *layers_needed_for_descendants = true + } + } else { + let kid_base = flow::mut_base(kid); + if kid_base.flags_info.flags.layers_needed_for_descendants() { + *layers_needed_for_descendants = true + } + } +} + // A block formatting context. pub struct BlockFlow { /// Data common to all flows. @@ -1180,104 +1210,37 @@ impl BlockFlow { /// /// Set the absolute position for children after doing any offsetting for /// position: relative. - pub fn build_display_list_block<E:ExtraDisplayListData>( - &mut self, - builder: &DisplayListBuilder, - container_block_size: &Size2D<Au>, - absolute_cb_abs_position: Point2D<Au>, - dirty: &Rect<Au>, - index: uint, - lists: &RefCell<DisplayListCollection<E>>) - -> uint { - + pub fn build_display_list_block(&mut self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { if self.is_float() { - self.build_display_list_float(builder, container_block_size, dirty, index, lists); - return index; + // TODO(pcwalton): This is a pseudo-stacking context. We need to merge `z-index: auto` + // kids into the parent stacking context, when that is supported. + self.build_display_list_float(stacking_context, builder, info) } else if self.is_absolutely_positioned() { - return self.build_display_list_abs(builder, container_block_size, - absolute_cb_abs_position, - dirty, index, lists); - } - - // FIXME: Shouldn't this be the abs_rect _after_ relative positioning? - let abs_rect = Rect(self.base.abs_position, self.base.position.size); - if !abs_rect.intersects(dirty) { - return index; - } - - debug!("build_display_list_block: adding display element"); - - let rel_offset = match self.box_ { - Some(ref box_) => { - box_.relative_position(container_block_size) - }, - None => { - Point2D { - x: Au::new(0), - y: Au::new(0), - } - } - }; - - // add box that starts block context - for box_ in self.box_.iter() { - box_.build_display_list(builder, dirty, self.base.abs_position + rel_offset, - (&*self) as &Flow, index, lists); - } - - // TODO: handle any out-of-flow elements - let this_position = self.base.abs_position; - for child in self.base.child_iter() { - let child_base = flow::mut_base(child); - child_base.abs_position = this_position + child_base.position.origin + rel_offset; + self.build_display_list_abs(stacking_context, builder, info) + } else { + self.build_display_list_block_common(stacking_context, + builder, + info, + Point2D(Au(0), Au(0)), + BlockLevel) } - - index } - pub fn build_display_list_float<E:ExtraDisplayListData>( - &mut self, - builder: &DisplayListBuilder, - container_block_size: &Size2D<Au>, - dirty: &Rect<Au>, - index: uint, - lists: &RefCell<DisplayListCollection<E>>) - -> bool { - let abs_rect = Rect(self.base.abs_position, self.base.position.size); - if !abs_rect.intersects(dirty) { - return true; - } - - // position:relative - let rel_offset = match self.box_ { - Some(ref box_) => { - box_.relative_position(container_block_size) - }, - None => { - Point2D { - x: Au::new(0), - y: Au::new(0), - } - } - }; - - - let offset = self.base.abs_position + self.float.get_ref().rel_pos + rel_offset; - // add box that starts block context - for box_ in self.box_.iter() { - box_.build_display_list(builder, dirty, offset, (&*self) as &Flow, index, lists); - } - - - // TODO: handle any out-of-flow elements - - // go deeper into the flow tree - for child in self.base.child_iter() { - let child_base = flow::mut_base(child); - child_base.abs_position = offset + child_base.position.origin + rel_offset; - } - - false + pub fn build_display_list_float(&mut self, + parent_stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { + let mut stacking_context = StackingContext::new(); + let float_offset = self.float.get_ref().rel_pos; + self.build_display_list_block_common(&mut stacking_context, + builder, + info, + float_offset, + RootOfStackingContextLevel); + parent_stacking_context.floats.push_all_move(stacking_context.flatten()) } /// Calculate and set the height, offsets, etc. for absolutely positioned flow. @@ -1351,63 +1314,72 @@ impl BlockFlow { box_.margin.set(margin); let mut position = box_.border_box.get(); - position.origin.y = box_.margin.get().top; + position.origin.y = Au(0); // Border box height let border_and_padding = box_.noncontent_height(); position.size.height = solution.height + border_and_padding; box_.border_box.set(position); - self.base.position.origin.y = solution.top; - self.base.position.size.height = solution.height + border_and_padding - + solution.margin_top + solution.margin_bottom; + self.base.position.origin.y = solution.top + margin.top; + self.base.position.size.height = solution.height + border_and_padding; } } /// Add display items for Absolutely Positioned flow. - pub fn build_display_list_abs<E:ExtraDisplayListData>( - &mut self, - builder: &DisplayListBuilder, - _: &Size2D<Au>, - absolute_cb_abs_position: Point2D<Au>, - dirty: &Rect<Au>, - mut index: uint, - lists: &RefCell<DisplayListCollection<E>>) - -> uint { - let flow_origin = if self.is_fixed() { + pub fn build_display_list_abs(&mut self, + parent_stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { + let mut stacking_context = StackingContext::new(); + let mut info = *info; + + info.absolute_containing_block_position = if self.is_fixed() { // The viewport is initially at (0, 0). self.base.position.origin } else { // Absolute position of Containing Block + position of absolute flow // wrt Containing Block - absolute_cb_abs_position + self.base.position.origin + info.absolute_containing_block_position + self.base.position.origin }; - if self.is_fixed() { - lists.with_mut(|lists| { - index = lists.lists.len(); - lists.add_list(DisplayList::<E>::new()); - }); - } - // Set the absolute position, which will be passed down later as part // of containing block details for absolute descendants. - self.base.abs_position = flow_origin; - let abs_rect = Rect(flow_origin, self.base.position.size); - if !abs_rect.intersects(dirty) { - return index; - } - - for box_ in self.box_.iter() { - box_.build_display_list(builder, dirty, flow_origin, (&*self) as &Flow, index, lists); - } + self.base.abs_position = info.absolute_containing_block_position; - // Go deeper into the flow tree. - for child in self.base.child_iter() { - let child_base = flow::mut_base(child); - child_base.abs_position = flow_origin + child_base.position.origin; - } + self.build_display_list_block_common(&mut stacking_context, + builder, + &info, + Point2D(Au(0), Au(0)), + RootOfStackingContextLevel); - index + if !info.layers_needed_for_positioned_flows && !self.base.flags_info.flags.needs_layer() { + // We didn't need a layer. + // + // TODO(pcwalton): `z-index`. + parent_stacking_context.positioned_descendants.push((0, stacking_context.flatten())); + return + } + + // If we got here, then we need a new layer. + // + // FIXME(pcwalton): The color is wrong! + let size = Size2D(self.base.position.size.width.to_nearest_px() as uint, + self.base.position.size.height.to_nearest_px() as uint); + let origin = Point2D(info.absolute_containing_block_position.x.to_nearest_px() as uint, + info.absolute_containing_block_position.y.to_nearest_px() as uint); + let scroll_policy = if self.is_fixed() { + FixedPosition + } else { + Scrollable + }; + let new_layer = RenderLayer { + id: self.layer_id(0), + display_list: Arc::new(stacking_context.flatten()), + rect: Rect(origin, size), + color: color::rgba(255.0, 255.0, 255.0, 0.0), + scroll_policy: scroll_policy, + }; + builder.layers.push(new_layer) } /// Return the top outer edge of the Hypothetical Box for an absolute flow. @@ -1663,6 +1635,18 @@ impl Flow for BlockFlow { } } + fn layer_id(&self, fragment_index: uint) -> LayerId { + // FIXME(pcwalton): This is a hack and is totally bogus in the presence of pseudo-elements. + // But until we have incremental reflow we can't do better--we recreate the flow for every + // DOM node so otherwise we nuke layers on every reflow. + match self.box_ { + Some(ref box_) => { + LayerId(box_.node.id(), fragment_index) + } + None => fail!("can't make a layer ID for a flow with no box"), + } + } + fn debug_str(&self) -> ~str { let txt = if self.is_float() { ~"FloatFlow: " diff --git a/src/components/main/layout/box_.rs b/src/components/main/layout/box_.rs index 972ac79da9a..047d5eb3d77 100644 --- a/src/components/main/layout/box_.rs +++ b/src/components/main/layout/box_.rs @@ -951,12 +951,10 @@ impl Box { (Au::new(0), Au::new(0)) } - pub fn paint_inline_background_border_if_applicable<E:ExtraDisplayListData>( - &self, - index: uint, - lists: &RefCell<DisplayListCollection<E>>, - absolute_bounds: &Rect<Au>, - offset: &Point2D<Au>) { + pub fn paint_inline_background_border_if_applicable(&self, + list: &mut DisplayList, + absolute_bounds: &Rect<Au>, + offset: &Point2D<Au>) { // FIXME: This causes a lot of background colors to be displayed when they are clearly not // needed. We could use display list optimization to clean this up, but it still seems // inefficient. What we really want is something like "nearest ancestor element that @@ -965,7 +963,7 @@ impl Box { match info.get() { &Some(ref box_info) => { let mut bg_rect = absolute_bounds.clone(); - for info in box_info.parent_info.rev_iter() { + for info in box_info.parent_info.as_slice().rev_iter() { // TODO (ksh8281) compute vertical-align, line-height bg_rect.origin.y = box_info.baseline + offset.y - info.font_ascent; bg_rect.size.height = info.font_ascent + info.font_descent; @@ -973,23 +971,24 @@ impl Box { info.style.get().Background.get().background_color); if !background_color.alpha.approx_eq(&0.0) { - lists.with_mut(|lists| { - let solid_color_display_item = ~SolidColorDisplayItem { - base: BaseDisplayItem { - bounds: bg_rect.clone(), - extra: ExtraDisplayListData::new(self), - }, - color: background_color.to_gfx_color(), - }; - - lists.lists[index].append_item(SolidColorDisplayItemClass(solid_color_display_item)) - }); + let solid_color_display_item = ~SolidColorDisplayItem { + base: BaseDisplayItem { + bounds: bg_rect.clone(), + node: self.node, + }, + color: background_color.to_gfx_color(), + }; + + list.push(SolidColorDisplayItemClass(solid_color_display_item)) } + let border = &info.border; + // Fast path. if border.is_zero() { - continue; + continue } + bg_rect.origin.y = bg_rect.origin.y - border.top; bg_rect.size.height = bg_rect.size.height + border.top + border.bottom; @@ -1003,26 +1002,20 @@ impl Box { let bottom_style = style.Border.get().border_bottom_style; let left_style = style.Border.get().border_left_style; + let border_display_item = ~BorderDisplayItem { + base: BaseDisplayItem { + bounds: bg_rect, + node: self.node, + }, + border: border.clone(), + color: SideOffsets2D::new(top_color.to_gfx_color(), + right_color.to_gfx_color(), + bottom_color.to_gfx_color(), + left_color.to_gfx_color()), + style: SideOffsets2D::new(top_style, right_style, bottom_style, left_style) + }; - lists.with_mut(|lists| { - let border_display_item = ~BorderDisplayItem { - base: BaseDisplayItem { - bounds: bg_rect, - extra: ExtraDisplayListData::new(self), - }, - border: border.clone(), - color: SideOffsets2D::new(top_color.to_gfx_color(), - right_color.to_gfx_color(), - bottom_color.to_gfx_color(), - left_color.to_gfx_color()), - style: SideOffsets2D::new(top_style, - right_style, - bottom_style, - left_style) - }; - - lists.lists[index].append_item(BorderDisplayItemClass(border_display_item)) - }); + list.push(BorderDisplayItemClass(border_display_item)); bg_rect.origin.x = bg_rect.origin.x + border.left; bg_rect.size.width = bg_rect.size.width - border.left - border.right; @@ -1033,11 +1026,9 @@ impl Box { } /// Adds the display items necessary to paint the background of this box to the display list if /// necessary. - pub fn paint_background_if_applicable<E:ExtraDisplayListData>( - &self, + pub fn paint_background_if_applicable(&self, + list: &mut DisplayList, builder: &DisplayListBuilder, - index: uint, - lists: &RefCell<DisplayListCollection<E>>, absolute_bounds: &Rect<Au>) { // FIXME: This causes a lot of background colors to be displayed when they are clearly not // needed. We could use display list optimization to clean this up, but it still seems @@ -1046,17 +1037,15 @@ impl Box { let style = self.style(); let background_color = style.resolve_color(style.Background.get().background_color); if !background_color.alpha.approx_eq(&0.0) { - lists.with_mut(|lists| { - let solid_color_display_item = ~SolidColorDisplayItem { - base: BaseDisplayItem { - bounds: *absolute_bounds, - extra: ExtraDisplayListData::new(self), - }, - color: background_color.to_gfx_color(), - }; + let display_item = ~SolidColorDisplayItem { + base: BaseDisplayItem { + bounds: *absolute_bounds, + node: self.node, + }, + color: background_color.to_gfx_color(), + }; - lists.lists[index].append_item(SolidColorDisplayItemClass(solid_color_display_item)) - }); + list.push(SolidColorDisplayItemClass(display_item)) } // The background image is painted on top of the background color. @@ -1064,22 +1053,81 @@ impl Box { // http://www.w3.org/TR/CSS21/colors.html#background match style.Background.get().background_image { Some(ref image_url) => { - let mut holder = ImageHolder::new(image_url.clone(), builder.ctx.image_cache.clone()); + let mut holder = ImageHolder::new(image_url.clone(), + builder.ctx.image_cache.clone()); match holder.get_image() { Some(image) => { debug!("(building display list) building background image"); - // Place the image into the display list. - lists.with_mut(|lists| { - let image_display_item = ~ImageDisplayItem { - base: BaseDisplayItem { - bounds: *absolute_bounds, - extra: ExtraDisplayListData::new(self), - }, - image: image.clone(), - }; - lists.lists[index].append_item(ImageDisplayItemClass(image_display_item)); + // Adjust bounds for `background-position` and `background-attachment`. + let mut bounds = *absolute_bounds; + let horizontal_position = model::specified( + style.Background.get().background_position.horizontal, + bounds.size.width); + let vertical_position = model::specified( + style.Background.get().background_position.vertical, + bounds.size.height); + + let clip_display_item; + match style.Background.get().background_attachment { + background_attachment::scroll => { + clip_display_item = None; + bounds.origin.x = bounds.origin.x + horizontal_position; + bounds.origin.y = bounds.origin.y + vertical_position; + bounds.size.width = bounds.size.width - horizontal_position; + bounds.size.height = bounds.size.height - vertical_position; + } + background_attachment::fixed => { + clip_display_item = Some(~ClipDisplayItem { + base: BaseDisplayItem { + bounds: bounds, + node: self.node, + }, + child_list: SmallVec0::new(), + need_clip: true, + }); + + bounds = Rect { + origin: Point2D(horizontal_position, vertical_position), + size: Size2D(bounds.origin.x + bounds.size.width, + bounds.origin.y + bounds.size.height), + } + } + } + // Adjust sizes for `background-repeat`. + match style.Background.get().background_repeat { + background_repeat::no_repeat => { + bounds.size.width = Au::from_px(image.get().width as int); + bounds.size.height = Au::from_px(image.get().height as int) + } + background_repeat::repeat_x => { + bounds.size.height = Au::from_px(image.get().height as int) + } + background_repeat::repeat_y => { + bounds.size.width = Au::from_px(image.get().width as int) + } + background_repeat::repeat => {} + }; + + + // Create the image display item. + let image_display_item = ImageDisplayItemClass(~ImageDisplayItem { + base: BaseDisplayItem { + bounds: bounds, + node: self.node, + }, + image: image.clone(), + stretch_size: Size2D(Au::from_px(image.get().width as int), + Au::from_px(image.get().height as int)), }); + + match clip_display_item { + None => list.push(image_display_item), + Some(mut clip_display_item) => { + clip_display_item.child_list.push(image_display_item); + list.push(ClipDisplayItemClass(clip_display_item)) + } + } } None => { // No image data at all? Do nothing. @@ -1095,11 +1143,7 @@ impl Box { /// Adds the display items necessary to paint the borders of this box to a display list if /// necessary. - pub fn paint_borders_if_applicable<E:ExtraDisplayListData>( - &self, - index: uint, - lists: &RefCell<DisplayListCollection<E>>, - abs_bounds: &Rect<Au>) { + pub fn paint_borders_if_applicable(&self, list: &mut DisplayList, abs_bounds: &Rect<Au>) { // Fast path. let border = self.border.get(); if border.is_zero() { @@ -1122,75 +1166,142 @@ impl Box { - self.noncontent_inline_right(); // Append the border to the display list. - lists.with_mut(|lists| { - let border_display_item = ~BorderDisplayItem { - base: BaseDisplayItem { - bounds: abs_bounds, - extra: ExtraDisplayListData::new(self), - }, - border: border, - color: SideOffsets2D::new(top_color.to_gfx_color(), - right_color.to_gfx_color(), - bottom_color.to_gfx_color(), - left_color.to_gfx_color()), - style: SideOffsets2D::new(top_style, - right_style, - bottom_style, - left_style) - }; + let border_display_item = ~BorderDisplayItem { + base: BaseDisplayItem { + bounds: abs_bounds, + node: self.node, + }, + border: border, + color: SideOffsets2D::new(top_color.to_gfx_color(), + right_color.to_gfx_color(), + bottom_color.to_gfx_color(), + left_color.to_gfx_color()), + style: SideOffsets2D::new(top_style, + right_style, + bottom_style, + left_style) + }; - lists.lists[index].append_item(BorderDisplayItemClass(border_display_item)) - }); + list.push(BorderDisplayItemClass(border_display_item)) + } + + fn build_debug_borders_around_text_boxes(&self, + stacking_context: &mut StackingContext, + flow_origin: Point2D<Au>, + text_box: &ScannedTextBoxInfo) { + let box_bounds = self.border_box.get(); + let absolute_box_bounds = box_bounds.translate(&flow_origin); + + // Compute the text box bounds and draw a border surrounding them. + let debug_border = SideOffsets2D::new_all_same(Au::from_px(1)); + + let border_display_item = ~BorderDisplayItem { + base: BaseDisplayItem { + bounds: absolute_box_bounds, + node: self.node, + }, + border: debug_border, + color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), + style: SideOffsets2D::new_all_same(border_style::solid) + + }; + stacking_context.content.push(BorderDisplayItemClass(border_display_item)); + + // Draw a rectangle representing the baselines. + let ascent = text_box.run.get().metrics_for_range(&text_box.range).ascent; + let baseline = Rect(absolute_box_bounds.origin + Point2D(Au(0), ascent), + Size2D(absolute_box_bounds.size.width, Au(0))); + + let line_display_item = ~LineDisplayItem { + base: BaseDisplayItem { + bounds: baseline, + node: self.node, + }, + color: rgb(0, 200, 0), + style: border_style::dashed, + + }; + stacking_context.content.push(LineDisplayItemClass(line_display_item)) + } + + fn build_debug_borders_around_box(&self, + stacking_context: &mut StackingContext, + flow_origin: Point2D<Au>) { + let box_bounds = self.border_box.get(); + let absolute_box_bounds = box_bounds.translate(&flow_origin); + + // This prints a debug border around the border of this box. + let debug_border = SideOffsets2D::new_all_same(Au::from_px(1)); + + let border_display_item = ~BorderDisplayItem { + base: BaseDisplayItem { + bounds: absolute_box_bounds, + node: self.node, + }, + border: debug_border, + color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), + style: SideOffsets2D::new_all_same(border_style::solid) + + }; + stacking_context.content.push(BorderDisplayItemClass(border_display_item)) } - /// Adds the display items for this box to the given display list. + /// Adds the display items for this box to the given stacking context. /// /// Arguments: + /// + /// * `stacking_context`: The stacking context to add display items to. /// * `builder`: The display list builder, which manages the coordinate system and options. /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. /// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow. /// box. - /// * `list`: The display list to which items should be appended. - /// - /// TODO: To implement stacking contexts correctly, we need to create a set of display lists, - /// one per layer of the stacking context (CSS 2.1 § 9.9.1). Each box is passed the list set - /// representing the box's stacking context. When asked to construct its constituent display - /// items, each box puts its display items into the correct stack layer according to CSS 2.1 - /// Appendix E. Finally, the builder flattens the list. - pub fn build_display_list<E:ExtraDisplayListData>( - &self, + /// * `flow`: The flow that this box belongs to. + pub fn build_display_list(&self, + stacking_context: &mut StackingContext, builder: &DisplayListBuilder, - dirty: &Rect<Au>, + _: &DisplayListBuildingInfo, flow_origin: Point2D<Au>, flow: &Flow, - index: uint, - lists: &RefCell<DisplayListCollection<E>>) { + background_and_border_level: BackgroundAndBorderLevel) { // Box position wrt to the owning flow. let box_bounds = self.border_box.get(); let absolute_box_bounds = box_bounds.translate(&flow_origin); debug!("Box::build_display_list at rel={}, abs={}: {:s}", - box_bounds, absolute_box_bounds, self.debug_str()); - debug!("Box::build_display_list: dirty={}, flow_origin={}", *dirty, flow_origin); + box_bounds, + absolute_box_bounds, + self.debug_str()); + debug!("Box::build_display_list: dirty={}, flow_origin={}", builder.dirty, flow_origin); if self.style().InheritedBox.get().visibility != visibility::visible { - return; + return } - if absolute_box_bounds.intersects(dirty) { - debug!("Box::build_display_list: intersected. Adding display item..."); - } else { + if !absolute_box_bounds.intersects(&builder.dirty) { debug!("Box::build_display_list: Did not intersect..."); - return; + return } - self.paint_inline_background_border_if_applicable(index, lists, &absolute_box_bounds, &flow_origin); - // Add the background to the list, if applicable. - self.paint_background_if_applicable(builder, index, lists, &absolute_box_bounds); + debug!("Box::build_display_list: intersected. Adding display item..."); - // Add a border, if applicable. - // - // TODO: Outlines. - self.paint_borders_if_applicable(index, lists, &absolute_box_bounds); + { + let list = + stacking_context.list_for_background_and_border_level(background_and_border_level); + + // Add a background to the list, if this is an inline. + // + // FIXME(pcwalton): This is kind of ugly; merge with the call below? + self.paint_inline_background_border_if_applicable(list, + &absolute_box_bounds, + &flow_origin); + + // Add the background to the list, if applicable. + self.paint_background_if_applicable(list, builder, &absolute_box_bounds); + + // Add a border, if applicable. + // + // TODO: Outlines. + self.paint_borders_if_applicable(list, &absolute_box_bounds); + } match self.specific { UnscannedTextBox(_) => fail!("Shouldn't see unscanned boxes here."), @@ -1224,100 +1335,45 @@ impl Box { - self.noncontent_inline_right(); // Create the text box. - lists.with_mut(|lists| { - let text_display_item = ~TextDisplayItem { - base: BaseDisplayItem { - bounds: bounds, - extra: ExtraDisplayListData::new(self), - }, - text_run: text_box.run.clone(), - range: text_box.range, - text_color: text_color, - overline_color: flow_flags.overline_color(text_color), - underline_color: flow_flags.underline_color(text_color), - line_through_color: flow_flags.line_through_color(text_color), - flags: text_flags, - }; + let text_display_item = ~TextDisplayItem { + base: BaseDisplayItem { + bounds: bounds, + node: self.node, + }, + text_run: text_box.run.clone(), + range: text_box.range, + text_color: text_color, + overline_color: flow_flags.overline_color(text_color), + underline_color: flow_flags.underline_color(text_color), + line_through_color: flow_flags.line_through_color(text_color), + flags: text_flags, + }; - lists.lists[index].append_item(TextDisplayItemClass(text_display_item)); - }); + stacking_context.content.push(TextDisplayItemClass(text_display_item)); // Draw debug frames for text bounds. // // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We // should have a real `SERVO_DEBUG` system. - debug!("{:?}", { - // Compute the text box bounds and draw a border surrounding them. - let debug_border = SideOffsets2D::new_all_same(Au::from_px(1)); - - lists.with_mut(|lists| { - let border_display_item = ~BorderDisplayItem { - base: BaseDisplayItem { - bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), - }, - border: debug_border, - color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), - style: SideOffsets2D::new_all_same(border_style::solid) - - }; - lists.lists[index].append_item(BorderDisplayItemClass(border_display_item)); - }); - - // Draw a rectangle representing the baselines. - let ascent = text_box.run.get().metrics_for_range( - &text_box.range).ascent; - let baseline = Rect(absolute_box_bounds.origin + Point2D(Au(0), ascent), - Size2D(absolute_box_bounds.size.width, Au(0))); - - lists.with_mut(|lists| { - let line_display_item = ~LineDisplayItem { - base: BaseDisplayItem { - bounds: baseline, - extra: ExtraDisplayListData::new(self), - }, - color: rgb(0, 200, 0), - style: border_style::dashed - - }; - lists.lists[index].append_item(LineDisplayItemClass(line_display_item)); - }); - }); + debug!("{:?}", self.build_debug_borders_around_text_boxes(stacking_context, + flow_origin, + text_box)) }, GenericBox | IframeBox(..) | TableBox | TableCellBox | TableRowBox | TableWrapperBox => { - lists.with_mut(|lists| { - let item = ~ClipDisplayItem { - base: BaseDisplayItem { - bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), - }, - child_list: ~[], - need_clip: self.needs_clip() - }; - lists.lists[index].append_item(ClipDisplayItemClass(item)); - }); + let item = ~ClipDisplayItem { + base: BaseDisplayItem { + bounds: absolute_box_bounds, + node: self.node, + }, + child_list: SmallVec0::new(), + need_clip: self.needs_clip() + }; + stacking_context.content.push(ClipDisplayItemClass(item)); // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We // should have a real `SERVO_DEBUG` system. - debug!("{:?}", { - // This prints a debug border around the border of this box. - let debug_border = SideOffsets2D::new_all_same(Au::from_px(1)); - - lists.with_mut(|lists| { - let border_display_item = ~BorderDisplayItem { - base: BaseDisplayItem { - bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), - }, - border: debug_border, - color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), - style: SideOffsets2D::new_all_same(border_style::solid) - - }; - lists.lists[index].append_item(BorderDisplayItemClass(border_display_item)); - }); - }); + debug!("{:?}", self.build_debug_borders_around_box(stacking_context, flow_origin)) }, ImageBox(ref image_box) => { let mut image_ref = image_box.image.borrow_mut(); @@ -1335,16 +1391,15 @@ impl Box { debug!("(building display list) building image box"); // Place the image into the display list. - lists.with_mut(|lists| { - let image_display_item = ~ImageDisplayItem { - base: BaseDisplayItem { - bounds: bounds, - extra: ExtraDisplayListData::new(self), - }, - image: image.clone(), - }; - lists.lists[index].append_item(ImageDisplayItemClass(image_display_item)); - }); + let image_display_item = ~ImageDisplayItem { + base: BaseDisplayItem { + bounds: bounds, + node: self.node, + }, + image: image.clone(), + stretch_size: bounds.size, + }; + stacking_context.content.push(ImageDisplayItemClass(image_display_item)) } None => { // No image data at all? Do nothing. @@ -1355,24 +1410,7 @@ impl Box { } // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We // should have a real `SERVO_DEBUG` system. - debug!("{:?}", { - let debug_border = SideOffsets2D::new_all_same(Au::from_px(1)); - - lists.with_mut(|lists| { - let border_display_item = ~BorderDisplayItem { - base: BaseDisplayItem { - bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(self), - }, - border: debug_border, - color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), - style: SideOffsets2D::new_all_same(border_style::solid) - - }; - lists.lists[index].append_item(BorderDisplayItemClass(border_display_item)) - }); - }); - + debug!("{:?}", self.build_debug_borders_around_box(stacking_context, flow_origin)) } } @@ -1440,8 +1478,8 @@ impl Box { UnscannedTextBox(_) => fail!("Unscanned text boxes should have been scanned by now!"), } } + /// Returns, and computes, the height of this box. - /// pub fn content_height(&self) -> Au { match self.specific { GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox | diff --git a/src/components/main/layout/context.rs b/src/components/main/layout/context.rs index 4e3ea7bb404..558d42924be 100644 --- a/src/components/main/layout/context.rs +++ b/src/components/main/layout/context.rs @@ -5,10 +5,10 @@ //! Data needed by the layout task. use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache}; -use layout::util::OpaqueNode; use extra::url::Url; use geom::size::Size2D; +use gfx::display_list::OpaqueNode; use gfx::font_context::{FontContext, FontContextInfo}; use green::task::GreenTask; use script::layout_interface::LayoutChan; diff --git a/src/components/main/layout/display_list_builder.rs b/src/components/main/layout/display_list_builder.rs index dc20f784667..4779c913fc4 100644 --- a/src/components/main/layout/display_list_builder.rs +++ b/src/components/main/layout/display_list_builder.rs @@ -4,39 +4,34 @@ //! Constructs display lists from boxes. -use layout::box_::Box; use layout::context::LayoutContext; -use layout::util::OpaqueNode; +use geom::{Point2D, Rect, Size2D}; +use gfx::render_task::RenderLayer; use gfx; +use servo_util::geometry::Au; +use servo_util::smallvec::SmallVec0; use style; -pub trait ExtraDisplayListData { - fn new(box_: &Box) -> Self; -} +/// Manages the information needed to construct the display list. +pub struct DisplayListBuilder<'a> { + ctx: &'a LayoutContext, -pub type Nothing = (); + /// A list of render layers that we've built up, root layer not included. + layers: SmallVec0<RenderLayer>, -impl ExtraDisplayListData for OpaqueNode { - fn new(box_: &Box) -> OpaqueNode { - box_.node - } + /// The dirty rect. + dirty: Rect<Au>, } -impl ExtraDisplayListData for Nothing { - fn new(_: &Box) -> Nothing { - () - } -} - -/// A builder object that manages display list builder should mainly hold information about the -/// initial request and desired result--for example, whether the `DisplayList` is to be used for -/// painting or hit testing. This can affect which boxes are created. -/// -/// Right now, the builder isn't used for much, but it establishes the pattern we'll need once we -/// support display-list-based hit testing and so forth. -pub struct DisplayListBuilder<'a> { - ctx: &'a LayoutContext, +/// Information needed at each step of the display list building traversal. +pub struct DisplayListBuildingInfo { + /// The size of the containing block. + containing_block_size: Size2D<Au>, + /// The absolute position of the absolute containing block. + absolute_containing_block_position: Point2D<Au>, + /// Whether the absolute containing block forces positioned descendants to be layerized. + layers_needed_for_positioned_flows: bool, } // diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index 099f79c8e81..d0310d28c71 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -219,6 +219,14 @@ pub trait Flow { fail!("this is not the CB-generating flow you're looking for") } + /// Returns a layer ID for the given fragment. + fn layer_id(&self, fragment_id: uint) -> LayerId { + unsafe { + let pointer: uint = cast::transmute(self); + LayerId(pointer, fragment_id) + } + } + /// Returns a debugging string describing this flow. fn debug_str(&self) -> ~str { ~"???" @@ -336,16 +344,11 @@ pub trait MutableFlowUtils { /// Computes the overflow region for this flow. fn store_overflow(self, _: &mut LayoutContext); - /// builds the display lists - fn build_display_lists<E:ExtraDisplayListData>( - self, - builder: &DisplayListBuilder, - container_block_size: &Size2D<Au>, - absolute_cb_abs_position: Point2D<Au>, - dirty: &Rect<Au>, - index: uint, - mut list: &RefCell<DisplayListCollection<E>>) - -> bool; + /// Builds the display lists for this flow and its descendants. + fn build_display_list(self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo); /// Destroys the flow. fn destroy(self); @@ -606,6 +609,17 @@ bitfield!(FlowFlags, override_overline, set_override_overline, 0b0000_0100) // NB: If you update this, you need to update TEXT_DECORATION_OVERRIDE_BITMASK. bitfield!(FlowFlags, override_line_through, set_override_line_through, 0b0000_1000) +// Whether this flow contains a flow that has its own layer within the same absolute containing +// block. +bitfield!(FlowFlags, + layers_needed_for_descendants, + set_layers_needed_for_descendants, + 0b0100_0000) + +// Whether this flow must have its own layer. Even if this flag is not set, it might get its own +// layer if it's deemed to be likely to overlap flows with their own layer. +bitfield!(FlowFlags, needs_layer, set_needs_layer, 0b1000_0000) + // The text alignment for this flow. impl FlowFlags { #[inline] diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index dc05d359ed7..84786e2d282 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -3,21 +3,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use css::node_style::StyledNode; -use layout::box_::{Box, CannotSplit, GenericBox, IframeBox, ImageBox, ScannedTextBox, SplitDidFit}; -use layout::box_::{SplitDidNotFit, UnscannedTextBox, InlineInfo}; -use layout::box_::{TableColumnBox, TableRowBox, TableWrapperBox, TableCellBox, TableBox}; +use layout::box_::{Box, CannotSplit, GenericBox, IframeBox, ImageBox, InlineInfo, ScannedTextBox}; +use layout::box_::{SplitDidFit, SplitDidNotFit, TableBox, TableCellBox, TableColumnBox}; +use layout::box_::{TableRowBox, TableWrapperBox, UnscannedTextBox}; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo}; use layout::floats::{FloatLeft, Floats, PlacementInfo}; use layout::flow::{BaseFlow, FlowClass, Flow, InlineFlowClass}; use layout::flow; +use layout::model::IntrinsicWidths; use layout::util::ElementMapping; use layout::wrapper::ThreadSafeLayoutNode; use collections::{Deque, RingBuf}; use geom::{Point2D, Rect, Size2D}; -use gfx::display_list::DisplayListCollection; +use gfx::display_list::{ContentLevel, StackingContext}; use servo_util::geometry::Au; +use servo_util::geometry; use servo_util::range::Range; use std::cell::RefCell; use std::mem; @@ -480,17 +482,13 @@ impl InlineFlow { self.boxes = ~[]; } - pub fn build_display_list_inline<E:ExtraDisplayListData>( - &self, + pub fn build_display_list_inline(&self, + stacking_context: &mut StackingContext, builder: &DisplayListBuilder, - container_block_size: &Size2D<Au>, - dirty: &Rect<Au>, - index: uint, - lists: &RefCell<DisplayListCollection<E>>) - -> uint { + info: &DisplayListBuildingInfo) { let abs_rect = Rect(self.base.abs_position, self.base.position.size); - if !abs_rect.intersects(dirty) { - return index; + if !abs_rect.intersects(&builder.dirty) { + return } // TODO(#228): Once we form line boxes and have their cached bounds, we can be smarter and @@ -498,15 +496,19 @@ impl InlineFlow { debug!("Flow: building display list for {:u} inline boxes", self.boxes.len()); for box_ in self.boxes.iter() { - let rel_offset: Point2D<Au> = box_.relative_position(container_block_size); - box_.build_display_list(builder, dirty, self.base.abs_position + rel_offset, (&*self) as &Flow, index, lists); + let rel_offset: Point2D<Au> = box_.relative_position(&info.containing_block_size); + box_.build_display_list(stacking_context, + builder, + info, + self.base.abs_position + rel_offset, + (&*self) as &Flow, + ContentLevel); } // TODO(#225): Should `inline-block` elements have flows as children of the inline flow or // should the flow be nested inside the box somehow? - // For now, don't traverse the subtree rooted here - index + // For now, don't traverse the subtree rooted here. } /// Returns the relative offset from the baseline for this box, taking into account the value @@ -696,6 +698,7 @@ impl Flow for InlineFlow { // // TODO(pcwalton): Cache the linebox scanner? debug!("assign_height_inline: floats in: {:?}", self.base.floats); + // assign height for inline boxes for box_ in self.boxes.iter() { box_.assign_replaced_height_if_necessary(); diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index b9050816668..aad21667c2f 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -11,22 +11,22 @@ use css::select::new_stylist; use css::node_style::StyledNode; use layout::construct::{FlowConstructionResult, NoConstructionResult}; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ToGfxColor}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo, ToGfxColor}; use layout::flow::{Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils}; use layout::flow::{PreorderFlowTraversal, PostorderFlowTraversal}; use layout::flow; use layout::incremental::RestyleDamage; use layout::parallel::PaddedUnsafeFlow; use layout::parallel; -use layout::util::{LayoutDataAccess, OpaqueNode, LayoutDataWrapper}; +use layout::util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods}; use layout::wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode}; use extra::url::Url; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; -use gfx::display_list::{ClipDisplayItemClass, DisplayItem, DisplayItemIterator}; -use gfx::display_list::{DisplayList, DisplayListCollection}; +use gfx::display_list::{ClipDisplayItemClass, DisplayItem, DisplayItemIterator, DisplayList}; +use gfx::display_list::{OpaqueNode, StackingContext}; use gfx::font_context::{FontContext, FontContextInfo}; use gfx::render_task::{RenderMsg, RenderChan, RenderLayer}; use gfx::{render_task, color}; @@ -41,18 +41,20 @@ use script::layout_interface::{ContentChangedDocumentDamage, LayoutChan, Msg, Pr use script::layout_interface::{QueryMsg, ReapLayoutDataMsg, Reflow, UntrustedNodeAddress}; use script::layout_interface::{ReflowForDisplay, ReflowMsg}; use script::script_task::{ReflowCompleteMsg, ScriptChan, SendEventMsg}; +use servo_msg::compositor_msg::Scrollable; use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, FailureMsg}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use servo_net::local_image_cache::{ImageResponder, LocalImageCache}; use servo_util::geometry::Au; +use servo_util::geometry; use servo_util::opts::Opts; +use servo_util::smallvec::{SmallVec, SmallVec0, SmallVec1}; use servo_util::time::{ProfilerChan, profile}; use servo_util::time; use servo_util::task::send_on_failure; use servo_util::workqueue::WorkQueue; use std::cast::transmute; use std::cast; -use std::cell::RefCell; use std::comm::Port; use std::mem; use std::ptr; @@ -79,7 +81,7 @@ pub struct LayoutTask { script_chan: ScriptChan, /// The channel on which messages can be sent to the painting task. - render_chan: RenderChan<OpaqueNode>, + render_chan: RenderChan, /// The channel on which messages can be sent to the image cache. image_cache_task: ImageCacheTask, @@ -91,7 +93,7 @@ pub struct LayoutTask { screen_size: Size2D<Au>, /// A cached display list. - display_list_collection: Option<Arc<DisplayListCollection<OpaqueNode>>>, + display_list: Option<Arc<DisplayList>>, stylist: ~Stylist, @@ -251,7 +253,7 @@ impl LayoutTask { constellation_chan: ConstellationChan, failure_msg: Failure, script_chan: ScriptChan, - render_chan: RenderChan<OpaqueNode>, + render_chan: RenderChan, img_cache_task: ImageCacheTask, opts: Opts, profiler_chan: ProfilerChan, @@ -282,7 +284,7 @@ impl LayoutTask { chan: LayoutChan, constellation_chan: ConstellationChan, script_chan: ScriptChan, - render_chan: RenderChan<OpaqueNode>, + render_chan: RenderChan, image_cache_task: ImageCacheTask, opts: &Opts, profiler_chan: ProfilerChan) @@ -306,7 +308,7 @@ impl LayoutTask { local_image_cache: local_image_cache, screen_size: screen_size, - display_list_collection: None, + display_list: None, stylist: ~new_stylist(), initial_css_values: Arc::new(style::initial_values()), parallel_traversal: parallel_traversal, @@ -339,7 +341,7 @@ impl LayoutTask { stylist: &*self.stylist, initial_css_values: self.initial_css_values.clone(), url: (*url).clone(), - reflow_root: OpaqueNode::from_layout_node(reflow_root), + reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root), opts: self.opts.clone(), } } @@ -627,23 +629,27 @@ impl LayoutTask { // Build the display list if necessary, and send it to the renderer. if data.goal == ReflowForDisplay { profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone(), || { - let root_size = flow::base(layout_root).position.size; - let root_abs_position = Point2D(Au::new(0), Au::new(0)); - let mut display_list_collection = DisplayListCollection::new(); - display_list_collection.add_list(DisplayList::<OpaqueNode>::new()); - let display_list_collection = ~RefCell::new(display_list_collection); - let dirty = flow::base(layout_root).position.clone(); - let display_list_builder = DisplayListBuilder { + let mut root_stacking_context = StackingContext::new(); + let mut display_list_builder = DisplayListBuilder { ctx: &layout_ctx, + layers: SmallVec0::new(), + dirty: flow::base(layout_root).position.clone(), + }; + let display_list_building_info = DisplayListBuildingInfo { + containing_block_size: flow::base(layout_root).position.size, + absolute_containing_block_position: Point2D(Au(0), Au(0)), + layers_needed_for_positioned_flows: false, }; - layout_root.build_display_lists(&display_list_builder, &root_size, - root_abs_position, - &dirty, 0u, display_list_collection); - let display_list_collection = Arc::new(display_list_collection.unwrap()); + layout_root.build_display_list(&mut root_stacking_context, + &mut display_list_builder, + &display_list_building_info); - let mut color = color::rgba(255.0, 255.0, 255.0, 255.0); + let display_list = Arc::new(root_stacking_context.flatten()); + // FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor + // it with extreme prejudice. + let mut color = color::rgba(255.0, 255.0, 255.0, 255.0); for child in node.traverse_preorder() { if child.type_id() == ElementNodeTypeId(HTMLHtmlElementTypeId) || child.type_id() == ElementNodeTypeId(HTMLBodyElementTypeId) { @@ -668,18 +674,33 @@ impl LayoutTask { } } + let root_size = Size2D(display_list_building_info.containing_block_size + .width + .to_nearest_px() as uint, + display_list_building_info.containing_block_size + .height + .to_nearest_px() as uint); let render_layer = RenderLayer { - display_list_collection: display_list_collection.clone(), - size: Size2D(root_size.width.to_nearest_px() as uint, - root_size.height.to_nearest_px() as uint), - color: color + id: layout_root.layer_id(0), + display_list: display_list.clone(), + rect: Rect(Point2D(0u, 0u), root_size), + color: color, + scroll_policy: Scrollable, }; - self.display_list_collection = Some(display_list_collection.clone()); + self.display_list = Some(display_list.clone()); + + let mut layers = SmallVec1::new(); + layers.push(render_layer); + let DisplayListBuilder { + layers: sublayers, + .. + } = display_list_builder; + layers.push_all_move(sublayers); debug!("Layout done!"); - self.render_chan.send(RenderMsg(render_layer)); + self.render_chan.send(RenderMsg(layers)); }); } @@ -701,15 +722,14 @@ impl LayoutTask { // The neat thing here is that in order to answer the following two queries we only // need to compare nodes for equality. Thus we can safely work only with `OpaqueNode`. ContentBoxQuery(node, reply_chan) => { - let node = OpaqueNode::from_script_node(node); + let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node); - fn union_boxes_for_node<'a>( - accumulator: &mut Option<Rect<Au>>, - mut iter: DisplayItemIterator<'a,OpaqueNode>, + fn union_boxes_for_node(accumulator: &mut Option<Rect<Au>>, + mut iter: DisplayItemIterator, node: OpaqueNode) { for item in iter { union_boxes_for_node(accumulator, item.children(), node); - if item.base().extra == node { + if item.base().node == node { match *accumulator { None => *accumulator = Some(item.base().bounds), Some(ref mut acc) => *acc = acc.union(&item.base().bounds), @@ -719,41 +739,49 @@ impl LayoutTask { } let mut rect = None; - for display_list in self.display_list_collection.as_ref().unwrap().get().iter() { - union_boxes_for_node(&mut rect, display_list.iter(), node); + match self.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + union_boxes_for_node(&mut rect, display_list.get().iter(), node) + } } reply_chan.send(ContentBoxResponse(rect.unwrap_or(Au::zero_rect()))) } ContentBoxesQuery(node, reply_chan) => { - let node = OpaqueNode::from_script_node(node); + let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node); - fn add_boxes_for_node<'a>( - accumulator: &mut ~[Rect<Au>], - mut iter: DisplayItemIterator<'a,OpaqueNode>, + fn add_boxes_for_node(accumulator: &mut ~[Rect<Au>], + mut iter: DisplayItemIterator, node: OpaqueNode) { for item in iter { add_boxes_for_node(accumulator, item.children(), node); - if item.base().extra == node { + if item.base().node == node { accumulator.push(item.base().bounds) } } } let mut boxes = ~[]; - for display_list in self.display_list_collection.as_ref().unwrap().get().iter() { - add_boxes_for_node(&mut boxes, display_list.iter(), node); + match self.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + add_boxes_for_node(&mut boxes, display_list.get().iter(), node) + } } reply_chan.send(ContentBoxesResponse(boxes)) } HitTestQuery(_, point, reply_chan) => { - fn hit_test(x: Au, y: Au, list: &[DisplayItem<OpaqueNode>]) + fn hit_test(x: Au, y: Au, list: &[DisplayItem]) -> Option<HitTestResponse> { for item in list.rev_iter() { match *item { ClipDisplayItemClass(ref cc) => { - let ret = hit_test(x, y, cc.child_list); - if !ret.is_none() { - return ret; + if !cc.need_clip || geometry::rect_contains_point(cc.base.bounds, + Point2D(x, y)) { + let ret = hit_test(x, y, cc.child_list.as_slice()); + if !ret.is_none() { + return ret + } } } _ => {} @@ -774,31 +802,35 @@ impl LayoutTask { y < bounds.origin.y + bounds.size.height && bounds.origin.y <= y { return Some(HitTestResponse(item.base() - .extra + .node .to_untrusted_node_address())) } } let ret: Option<HitTestResponse> = None; ret } - for display_list in self.display_list_collection.as_ref().unwrap().get().lists.rev_iter() { - let (x, y) = (Au::from_frac_px(point.x as f64), - Au::from_frac_px(point.y as f64)); - let resp = hit_test(x,y,display_list.list); - if resp.is_some() { - reply_chan.send(Ok(resp.unwrap())); - return - } + let (x, y) = (Au::from_frac_px(point.x as f64), + Au::from_frac_px(point.y as f64)); + let resp = match self.display_list { + None => fail!("no display list!"), + Some(ref display_list) => hit_test(x, y, display_list.get().list.as_slice()), + }; + if resp.is_some() { + reply_chan.send(Ok(resp.unwrap())); + return } reply_chan.send(Err(())); } MouseOverQuery(_, point, reply_chan) => { - fn mouse_over_test(x: Au, y: Au, list: &[DisplayItem<OpaqueNode>], result: &mut ~[UntrustedNodeAddress]) { + fn mouse_over_test(x: Au, + y: Au, + list: &[DisplayItem], + result: &mut ~[UntrustedNodeAddress]) { for item in list.rev_iter() { match *item { ClipDisplayItemClass(ref cc) => { - mouse_over_test(x, y, cc.child_list, result); + mouse_over_test(x, y, cc.child_list.as_slice(), result); } _ => {} } @@ -814,18 +846,24 @@ impl LayoutTask { y < bounds.origin.y + bounds.size.height && bounds.origin.y <= y { result.push(item.base() - .extra + .node .to_untrusted_node_address()); } } } let mut mouse_over_list:~[UntrustedNodeAddress] = ~[]; - for display_list in self.display_list_collection.as_ref().unwrap().get().lists.rev_iter() { - let (x, y) = (Au::from_frac_px(point.x as f64), - Au::from_frac_px(point.y as f64)); - mouse_over_test(x,y,display_list.list, &mut mouse_over_list); - } + let (x, y) = (Au::from_frac_px(point.x as f64), + Au::from_frac_px(point.y as f64)); + match self.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + mouse_over_test(x, + y, + display_list.get().list.as_slice(), + &mut mouse_over_list); + } + }; if mouse_over_list.is_empty() { reply_chan.send(Err(())); diff --git a/src/components/main/pipeline.rs b/src/components/main/pipeline.rs index 77782342c12..2cca7feeb97 100644 --- a/src/components/main/pipeline.rs +++ b/src/components/main/pipeline.rs @@ -9,7 +9,6 @@ use extra::url::Url; use geom::size::Size2D; use gfx::render_task::{PaintPermissionGranted, PaintPermissionRevoked}; use gfx::render_task::{RenderChan, RenderTask}; -use layout::util::OpaqueNode; use script::layout_interface::LayoutChan; use script::script_task::LoadMsg; use script::script_task::{AttachLayoutMsg, NewLayoutInfo, ScriptTask, ScriptChan}; @@ -28,7 +27,7 @@ pub struct Pipeline { subpage_id: Option<SubpageId>, script_chan: ScriptChan, layout_chan: LayoutChan, - render_chan: RenderChan<OpaqueNode>, + render_chan: RenderChan, layout_shutdown_port: Port<()>, render_shutdown_port: Port<()>, /// The most recently loaded url @@ -40,7 +39,7 @@ pub struct Pipeline { pub struct CompositionPipeline { id: PipelineId, script_chan: ScriptChan, - render_chan: RenderChan<OpaqueNode>, + render_chan: RenderChan, } impl Pipeline { @@ -171,7 +170,7 @@ impl Pipeline { subpage_id: Option<SubpageId>, script_chan: ScriptChan, layout_chan: LayoutChan, - render_chan: RenderChan<OpaqueNode>, + render_chan: RenderChan, layout_shutdown_port: Port<()>, render_shutdown_port: Port<()>) -> Pipeline { diff --git a/src/components/msg/compositor_msg.rs b/src/components/msg/compositor_msg.rs index 847a13d1ab7..1d5307f5f95 100644 --- a/src/components/msg/compositor_msg.rs +++ b/src/components/msg/compositor_msg.rs @@ -2,17 +2,18 @@ * 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 azure::azure_hl::Color; use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; -use azure::azure_hl::Color; use layers::platform::surface::{NativeGraphicsMetadata, NativePaintingGraphicsContext}; use layers::platform::surface::{NativeSurface, NativeSurfaceMethods}; +use serialize::{Encoder, Encodable}; +use std::fmt::{Formatter, Show}; +use std::fmt; use constellation_msg::PipelineId; -use serialize::{Encoder, Encodable}; - pub struct LayerBuffer { /// The native surface which can be shared between threads or processes. On Mac this is an /// `IOSurface`; on Linux this is an X Pixmap; on Android this is an `EGLImageKHR`. @@ -76,15 +77,73 @@ impl Epoch { } } +#[deriving(Clone, Eq)] +pub struct LayerId(uint, uint); + +impl Show for LayerId { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let LayerId(a, b) = *self; + write!(f.buf, "Layer({}, {})", a, b); + Ok(()) + } +} + +impl LayerId { + /// FIXME(pcwalton): This is unfortunate. Maybe remove this in the future. + pub fn null() -> LayerId { + LayerId(0, 0) + } +} + +/// The scrolling policy of a layer. +#[deriving(Eq)] +pub enum ScrollPolicy { + /// These layers scroll when the parent receives a scrolling message. + Scrollable, + /// These layers do not scroll when the parent receives a scrolling message. + FixedPosition, +} + +/// All layer-specific information that the painting task sends to the compositor other than the +/// buffer contents of the layer itself. +pub struct LayerMetadata { + /// An opaque ID. This is usually the address of the flow and index of the box within it. + id: LayerId, + /// The position and size of the layer in pixels. + rect: Rect<uint>, + /// The background color of the layer. + color: Color, + /// The scrolling policy of this layer. + scroll_policy: ScrollPolicy, +} + /// The interface used by the renderer to acquire draw targets for each render frame and /// submit them to be drawn to the display. pub trait RenderListener { fn get_graphics_metadata(&self) -> Option<NativeGraphicsMetadata>; - fn new_layer(&self, PipelineId, Size2D<uint>); - fn set_layer_page_size_and_color(&self, PipelineId, Size2D<uint>, Epoch, Color); - fn set_layer_clip_rect(&self, PipelineId, Rect<uint>); - fn delete_layer(&self, PipelineId); - fn paint(&self, id: PipelineId, layer_buffer_set: ~LayerBufferSet, Epoch); + fn create_layer_group_for_pipeline(&self, PipelineId, Size2D<uint>); + + /// Informs the compositor of the layers for the given pipeline. The compositor responds by + /// creating and/or destroying render layers as necessary. + fn initialize_layers_for_pipeline(&self, + pipeline_id: PipelineId, + metadata: ~[LayerMetadata], + epoch: Epoch); + + fn set_layer_clip_rect(&self, + pipeline_id: PipelineId, + layer_id: LayerId, + new_rect: Rect<uint>); + + fn delete_layer_group(&self, PipelineId); + + /// Sends new tiles for the given layer to the compositor. + fn paint(&self, + pipeline_id: PipelineId, + layer_id: LayerId, + layer_buffer_set: ~LayerBufferSet, + epoch: Epoch); + fn set_render_state(&self, render_state: RenderState); } @@ -92,8 +151,11 @@ pub trait RenderListener { /// which is used in displaying the appropriate message in the window's title. pub trait ScriptListener : Clone { fn set_ready_state(&self, ReadyState); - fn invalidate_rect(&self, PipelineId, Rect<uint>); - fn scroll_fragment_point(&self, PipelineId, Point2D<f32>); + fn invalidate_rect(&self, pipeline_id: PipelineId, layer_id: LayerId, rect: Rect<uint>); + fn scroll_fragment_point(&self, + pipeline_id: PipelineId, + layer_id: LayerId, + point: Point2D<f32>); fn close(&self); fn dup(&self) -> ~ScriptListener; } diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 2fba7693687..7bf6e1af842 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -37,7 +37,8 @@ use js::jsapi::{JSObject, JS_InhibitGC, JS_AllowGC, JS_CallFunctionValue}; use js::jsval::NullValue; use js::rust::{Compartment, Cx, CxUtils, RtUtils}; use js; -use servo_msg::compositor_msg::{FinishedLoading, Loading, PerformingLayout, ScriptListener}; +use servo_msg::compositor_msg::{FinishedLoading, LayerId, Loading, PerformingLayout}; +use servo_msg::compositor_msg::{ScriptListener}; use servo_msg::constellation_msg::{ConstellationChan, IFrameSandboxed, IFrameUnsandboxed}; use servo_msg::constellation_msg::{LoadIframeUrlMsg, LoadCompleteMsg, LoadUrlMsg, NavigationDirection}; use servo_msg::constellation_msg::{PipelineId, SubpageId, Failure, FailureMsg}; @@ -929,10 +930,14 @@ impl ScriptTask { fn scroll_fragment_point(&self, pipeline_id: PipelineId, page: &Page, node: JS<Element>) { let (port, chan) = Chan::new(); let node: JS<Node> = NodeCast::from(&node); - let ContentBoxResponse(rect) = page.query_layout(ContentBoxQuery(node.to_trusted_node_address(), chan), port); + let ContentBoxResponse(rect) = + page.query_layout(ContentBoxQuery(node.to_trusted_node_address(), chan), port); let point = Point2D(to_frac_px(rect.origin.x).to_f32().unwrap(), to_frac_px(rect.origin.y).to_f32().unwrap()); - self.compositor.scroll_fragment_point(pipeline_id, point); + // FIXME(pcwalton): This is pretty bogus when multiple layers are involved. Really + // what needs to happen is that this needs to go through layout to ask which layer + // the element belongs to, and have it send the scroll message to the compositor. + self.compositor.scroll_fragment_point(pipeline_id, LayerId::null(), point); } /// This is the main entry point for receiving and dispatching DOM events. diff --git a/src/support/layers/rust-layers b/src/support/layers/rust-layers -Subproject 400a01307515def5dd6874ec44ed8a017ada36f +Subproject 79e405fa59c052ff78d7a2527a92474a32ac9b4 |