diff options
author | bors-servo <release+servo@mozilla.com> | 2014-04-03 21:01:48 -0400 |
---|---|---|
committer | bors-servo <release+servo@mozilla.com> | 2014-04-03 21:01:48 -0400 |
commit | 3ec22157ca20227cd2d648744b6c711ed0ae8366 (patch) | |
tree | 0df1e9fa97578eef867d6ca60d81df14bf6825c6 /src | |
parent | 897c679be28bbb29e248ca4c31ba917cf516e993 (diff) | |
parent | 9e3f7a005d4691f5382b757fdab22e3742b6a73f (diff) | |
download | servo-3ec22157ca20227cd2d648744b6c711ed0ae8366.tar.gz servo-3ec22157ca20227cd2d648744b6c711ed0ae8366.zip |
auto merge of #1988 : pcwalton/servo/acid2-2, r=SimonSapin
r? @metajack @SimonSapin @larsbergstrom @june0cho @hyunjunekim
Diffstat (limited to 'src')
62 files changed, 4565 insertions, 2720 deletions
diff --git a/src/components/gfx/display_list.rs b/src/components/gfx/display_list.rs index 34e3d278b3e..abb22e56863 100644 --- a/src/components/gfx/display_list.rs +++ b/src/components/gfx/display_list.rs @@ -18,64 +18,142 @@ 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 std::cast::transmute_region; +use servo_util::smallvec::{SmallVec, SmallVec0, SmallVecIterator}; +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>] +/// 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 { + let OpaqueNode(pointer) = *self; + pointer + } } -impl<E> DisplayListCollection<E> { - pub fn new() -> DisplayListCollection<E> { - DisplayListCollection { - lists: ~[] - } - } +/// 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 iter<'a>(&'a self) -> DisplayListIterator<'a,E> { - ParentDisplayListIterator(self.lists.iter()) +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 add_list(&mut self, list: DisplayList<E>) { - self.lists.push(list); + 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, + } } - pub fn draw_lists_into_context(&self, render_context: &mut RenderContext) { - for list in self.lists.iter() { - list.draw_into_context(render_context); + /// 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())) + } } - 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; + // 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 +161,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 +176,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 +268,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 +292,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 +324,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 +391,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,17 +424,14 @@ impl<E> DisplayItem<E> { } } - pub fn base<'a>(&'a self) -> &'a BaseDisplayItem<E> { - // FIXME(tkuehn): Workaround for Rust region bug. - unsafe { - match *self { - SolidColorDisplayItemClass(ref solid_color) => transmute_region(&solid_color.base), - TextDisplayItemClass(ref text) => transmute_region(&text.base), - ImageDisplayItemClass(ref image_item) => transmute_region(&image_item.base), - BorderDisplayItemClass(ref border) => transmute_region(&border.base), - LineDisplayItemClass(ref line) => transmute_region(&line.base), - ClipDisplayItemClass(ref clip) => transmute_region(&clip.base), - } + pub fn base<'a>(&'a self) -> &'a BaseDisplayItem { + match *self { + SolidColorDisplayItemClass(ref solid_color) => &solid_color.base, + TextDisplayItemClass(ref text) => &text.base, + ImageDisplayItemClass(ref image_item) => &image_item.base, + BorderDisplayItemClass(ref border) => &border.base, + LineDisplayItemClass(ref line) => &line.base, + ClipDisplayItemClass(ref clip) => &clip.base, } } @@ -342,7 +439,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..24e3580eb33 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. + position: Rect<uint>, + /// The color of the background in this layer. Used for unrendered content. + background_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(#2005, 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, + position: render_layer.position, + background_color: render_layer.background_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,144 @@ 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 render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) { + 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.position.origin.x as AzFloat), + -(render_layer.position.origin.y as AzFloat)); - // 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(); - }); - } + ctx.draw_target.set_transform(&matrix); - // 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 - } - } - }; + // Clear the buffer. + ctx.clear(); - 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 the display list. + profile(time::RenderingDrawingCategory, self.profiler_chan.clone(), || { + render_layer.display_list.get().draw_into_context(&mut ctx); + ctx.draw_target.flush(); + }); + } - 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 + // 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); + }); + + 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 +432,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..fe4e386eaae 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(#2004, 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..971e7c096d8 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(#2003, 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 => {} - } + /// 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), + } + } - 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())); + /// 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, - } - })).collect(); - layer.set_occlusions(); - layer + // See if we've already made this child layer. + if self.children.iter().any(|kid_holder| { + kid_holder.child.pipeline.id == pipeline_id && + kid_holder.child.id == child_layer_id + }) { + return true + } + + 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(#2003, 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,91 @@ 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 { + return self.children.mut_iter().any(|kid_holder| { + kid_holder.child.move(pipeline_id, layer_id, origin, window_size) + }) + } - 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. + 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 +660,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 +693,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 +709,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 +774,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 +831,65 @@ 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 = ~[]; + 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 +924,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 +937,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 +986,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 +1007,9 @@ impl CompositorLayer { kid.child.forget_all_tiles(); } } + + pub fn id_of_first_child(&self) -> LayerId { + self.children.iter().next().expect("no first child!").child.id + } } diff --git a/src/components/main/compositing/compositor_task.rs b/src/components/main/compositing/compositor_task.rs index 6666fcf4456..4e0093fb2c4 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,68 @@ 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(#2004, 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.position.origin.x as f32, + metadata.position.origin.y as f32); + let size = Size2D(metadata.position.size.width as f32, + metadata.position.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.background_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 +174,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..ac4b1d9232e 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/css/node_util.rs b/src/components/main/css/node_util.rs index d6caaad5f01..3b37d4ce016 100644 --- a/src/components/main/css/node_util.rs +++ b/src/components/main/css/node_util.rs @@ -5,7 +5,7 @@ use layout::incremental::RestyleDamage; use layout::util::LayoutDataAccess; use layout::wrapper::{TLayoutNode, ThreadSafeLayoutNode}; - +use layout::wrapper::{After, AfterBlock, Before, BeforeBlock, Normal}; use std::cast; use style::ComputedValues; use sync::Arc; @@ -25,13 +25,35 @@ impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> { fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues> { unsafe { let layout_data_ref = self.borrow_layout_data(); - cast::transmute_region(layout_data_ref.get() - .as_ref() - .unwrap() - .data - .style - .as_ref() - .unwrap()) + match self.get_element_type() { + Before | BeforeBlock => { + cast::transmute_region(layout_data_ref.get() + .as_ref() + .unwrap() + .data + .before_style + .as_ref() + .unwrap()) + } + After | AfterBlock => { + cast::transmute_region(layout_data_ref.get() + .as_ref() + .unwrap() + .data + .after_style + .as_ref() + .unwrap()) + } + Normal => { + cast::transmute_region(layout_data_ref.get() + .as_ref() + .unwrap() + .data + .style + .as_ref() + .unwrap()) + } + } } } diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index 53585c47df7..d8132650c70 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -15,21 +15,30 @@ use layout::box_::{Box, ImageBox, ScannedTextBox}; use layout::construct::FlowConstructor; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; -use layout::floats::{FloatKind, Floats, PlacementInfo}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo}; +use layout::floats::{ClearBoth, ClearLeft, ClearRight, FloatKind, Floats, PlacementInfo}; use layout::flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils}; -use layout::flow::{mut_base, PreorderFlowTraversal, PostorderFlowTraversal, MutableFlowUtils}; +use layout::flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_base}; use layout::flow; -use layout::model::{MaybeAuto, Specified, Auto, specified_or_none, specified}; +use layout::model::{Auto, IntrinsicWidths, MarginCollapseInfo, MarginsCollapse}; +use layout::model::{MarginsCollapseThrough, MaybeAuto, NoCollapsibleMargins, Specified, specified}; +use layout::model::{specified_or_none}; use layout::wrapper::ThreadSafeLayoutNode; -use style::computed_values::{position}; +use style::ComputedValues; +use style::computed_values::{clear, position}; -use std::cell::RefCell; use geom::{Point2D, Rect, Size2D}; -use gfx::display_list::{DisplayListCollection, DisplayList}; +use gfx::color; +use gfx::display_list::{BackgroundAndBorderLevel, BlockLevel, RootOfStackingContextLevel}; +use gfx::display_list::{StackingContext}; +use gfx::render_task::RenderLayer; +use servo_msg::compositor_msg::{FixedPosition, LayerId, Scrollable}; use servo_util::geometry::Au; use servo_util::geometry; use servo_util::smallvec::{SmallVec, SmallVec0}; +use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LPN_Length, LPN_None}; +use style::computed_values::{LPN_Percentage, LP_Length, LP_Percentage}; +use sync::Arc; /// Information specific to floated blocks. pub struct FloatedBlockInfo { @@ -98,7 +107,7 @@ impl HeightConstraintSolution { content_height: Au, available_height: Au, static_y_offset: Au) - -> HeightConstraintSolution { + -> HeightConstraintSolution { // Distance from the top edge of the Absolute Containing Block to the // top margin edge of a hypothetical box that would have been the // first box of the element. @@ -265,6 +274,115 @@ impl HeightConstraintSolution { } } +/// Performs height calculations potentially multiple times, taking `height`, `min-height`, and +/// `max-height` into account. After each call to `next()`, the caller must call `.try()` with the +/// current calculated value of `height`. +/// +/// See CSS 2.1 § 10.7. +struct CandidateHeightIterator { + height: MaybeAuto, + max_height: Option<Au>, + min_height: Au, + candidate_value: Au, + status: CandidateHeightIteratorStatus, +} + +impl CandidateHeightIterator { + /// Creates a new candidate height iterator. `block_container_height` is `None` if the height + /// of the block container has not been determined yet. It will always be `Some` in the case of + /// absolutely-positioned containing blocks. + pub fn new(style: &ComputedValues, block_container_height: Option<Au>) + -> CandidateHeightIterator { + // Per CSS 2.1 § 10.7, percentages in `min-height` and `max-height` refer to the height of + // the containing block. If that is not determined yet by the time we need to resolve + // `min-height` and `max-height`, percentage values are ignored. + + let height = match (style.Box.get().height, block_container_height) { + (LPA_Percentage(percent), Some(block_container_height)) => { + Specified(block_container_height.scale_by(percent)) + } + (LPA_Percentage(_), None) | (LPA_Auto, _) => Auto, + (LPA_Length(length), _) => Specified(length), + }; + let max_height = match (style.Box.get().max_height, block_container_height) { + (LPN_Percentage(percent), Some(block_container_height)) => { + Some(block_container_height.scale_by(percent)) + } + (LPN_Percentage(_), None) | (LPN_None, _) => None, + (LPN_Length(length), _) => Some(length), + }; + let min_height = match (style.Box.get().min_height, block_container_height) { + (LP_Percentage(percent), Some(block_container_height)) => { + block_container_height.scale_by(percent) + } + (LP_Percentage(_), None) => Au(0), + (LP_Length(length), _) => length, + }; + + CandidateHeightIterator { + height: height, + max_height: max_height, + min_height: min_height, + candidate_value: Au(0), + status: InitialCandidateHeightStatus, + } + } + + pub fn next<'a>(&'a mut self) -> Option<(MaybeAuto, &'a mut Au)> { + self.status = match self.status { + InitialCandidateHeightStatus => TryingHeightCandidateHeightStatus, + TryingHeightCandidateHeightStatus => { + match self.max_height { + Some(max_height) if self.candidate_value > max_height => { + TryingMaxCandidateHeightStatus + } + _ if self.candidate_value < self.min_height => TryingMinCandidateHeightStatus, + _ => FoundCandidateHeightStatus, + } + } + TryingMaxCandidateHeightStatus => { + if self.candidate_value < self.min_height { + TryingMinCandidateHeightStatus + } else { + FoundCandidateHeightStatus + } + } + TryingMinCandidateHeightStatus | FoundCandidateHeightStatus => { + FoundCandidateHeightStatus + } + }; + + match self.status { + TryingHeightCandidateHeightStatus => Some((self.height, &mut self.candidate_value)), + TryingMaxCandidateHeightStatus => { + Some((Specified(self.max_height.unwrap()), &mut self.candidate_value)) + } + TryingMinCandidateHeightStatus => { + Some((Specified(self.min_height), &mut self.candidate_value)) + } + FoundCandidateHeightStatus => None, + InitialCandidateHeightStatus => fail!(), + } + } +} + +enum CandidateHeightIteratorStatus { + InitialCandidateHeightStatus, + TryingHeightCandidateHeightStatus, + TryingMaxCandidateHeightStatus, + TryingMinCandidateHeightStatus, + FoundCandidateHeightStatus, +} + +// A helper function used in height calculation. +fn translate_including_floats(cur_y: &mut Au, delta: Au, inorder: bool, floats: &mut Floats) { + *cur_y = *cur_y + delta; + + if inorder { + floats.translate(Point2D(Au(0), -delta)); + } +} + /// The real assign-heights traversal for flows with position 'absolute'. /// /// This is a traversal of an Absolute Flow tree. @@ -330,6 +448,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(#2006, 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(#1244, #2007, 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. @@ -350,9 +498,7 @@ pub struct BlockFlow { } impl BlockFlow { - pub fn from_node(constructor: &mut FlowConstructor, - node: &ThreadSafeLayoutNode) - -> BlockFlow { + pub fn from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> BlockFlow { BlockFlow { base: BaseFlow::new((*node).clone()), box_: Some(Box::new(constructor, node)), @@ -362,9 +508,7 @@ impl BlockFlow { } } - pub fn from_node_and_box(node: &ThreadSafeLayoutNode, - box_: Box) - -> BlockFlow { + pub fn from_node_and_box(node: &ThreadSafeLayoutNode, box_: Box) -> BlockFlow { BlockFlow { base: BaseFlow::new((*node).clone()), box_: Some(box_), @@ -574,8 +718,8 @@ impl BlockFlow { /// This is where we use the preferred widths and minimum widths /// calculated in the bubble-widths traversal. fn get_shrink_to_fit_width(&self, available_width: Au) -> Au { - geometry::min(self.base.pref_width, - geometry::max(self.base.min_width, available_width)) + geometry::min(self.base.intrinsic_widths.preferred_width, + geometry::max(self.base.intrinsic_widths.minimum_width, available_width)) } /// Collect and update static y-offsets bubbled up by kids. @@ -589,19 +733,11 @@ impl BlockFlow { /// position already set. fn collect_static_y_offsets_from_kids(&mut self) { let mut abs_descendant_y_offsets = SmallVec0::new(); - let mut fixed_descendant_y_offsets = SmallVec0::new(); - for kid in self.base.child_iter() { let mut gives_abs_offsets = true; if kid.is_block_like() { let kid_block = kid.as_block(); - if kid_block.is_fixed() { - // It won't contribute any offsets for position 'absolute' - // descendants because it would be the CB for them. - gives_abs_offsets = false; - // Add the offset for the current fixed flow too. - fixed_descendant_y_offsets.push(kid_block.get_hypothetical_top_edge()); - } else if kid_block.is_absolutely_positioned() { + if kid_block.is_fixed() || kid_block.is_absolutely_positioned() { // It won't contribute any offsets for descendants because it // would be the CB for them. gives_abs_offsets = false; @@ -623,356 +759,289 @@ impl BlockFlow { abs_descendant_y_offsets.push(y_offset); } } - - // Get all the fixed offsets. - let kid_base = flow::mut_base(kid); - // Consume all the static y-offsets bubbled up by kid. - for y_offset in kid_base.fixed_descendants.static_y_offsets.move_iter() { - // The offsets are wrt the kid flow box. Translate them to current flow. - y_offset = y_offset + kid_base.position.origin.y; - fixed_descendant_y_offsets.push(y_offset); - } } self.base.abs_descendants.static_y_offsets = abs_descendant_y_offsets; - self.base.fixed_descendants.static_y_offsets = fixed_descendant_y_offsets; } - /// Calculate clearance, top_offset, bottom_offset, and left_offset for the box. - /// If `ignore_clear` is true, clearance does not need to be calculated. - pub fn initialize_offsets(&mut self, ignore_clear: bool) -> (Au, Au, Au, Au) { - match self.box_ { - None => (Au(0), Au(0), Au(0), Au(0)), - Some(ref box_) => { - let clearance = match box_.clear() { - Some(clear) if !ignore_clear => self.base.floats.clearance(clear), - _ => Au::new(0) - }; + /// If this is the root flow, shifts all kids down and adjusts our size to account for + /// collapsed margins. + /// + /// TODO(#2017, pcwalton): This is somewhat inefficient (traverses kids twice); can we do + /// better? + fn adjust_boxes_for_collapsed_margins_if_root(&mut self) { + if !self.is_root() { + return + } - // Offsets to content edge of box_ - let top_offset = clearance + box_.top_offset(); - let bottom_offset = box_.bottom_offset(); - let left_offset = box_.left_offset(); - - (clearance, top_offset, bottom_offset, left_offset) - } - } - } - - /// In case of inorder assign_height traversal and not absolute flow, - /// 'assign_height's of all children are visited - /// and Float info is shared between adjacent children. - /// Float info of the last child is saved in parent flow. - pub fn handle_children_floats_if_necessary(&mut self, - ctx: &mut LayoutContext, - inorder: bool, - left_offset: Au, - top_offset: Au) { - // Note: Ignoring floats for absolute flow as of now. - if inorder && !self.is_absolutely_positioned() { - // Floats for blocks work like this: - // self.floats -> child[0].floats - // visit child[0] - // child[i-1].floats -> child[i].floats - // visit child[i] - // repeat until all children are visited. - // last_child.floats -> self.floats (done at the end of this method) - self.base.floats.translate(Point2D(-left_offset, -top_offset)); - let mut floats = self.base.floats.clone(); + let (top_margin_value, bottom_margin_value) = match self.base.collapsible_margins { + MarginsCollapseThrough(margin) => (Au(0), margin.collapse()), + MarginsCollapse(top_margin, bottom_margin) => { + (top_margin.collapse(), bottom_margin.collapse()) + } + NoCollapsibleMargins(top, bottom) => (top, bottom), + }; + + // Shift all kids down (or up, if margins are negative) if necessary. + if top_margin_value != Au(0) { for kid in self.base.child_iter() { - flow::mut_base(kid).floats = floats; - kid.assign_height_inorder(ctx); - floats = flow::mut_base(kid).floats.clone(); + let kid_base = flow::mut_base(kid); + kid_base.position.origin.y = kid_base.position.origin.y + top_margin_value } - self.base.floats = floats; + } + + self.base.position.size.height = self.base.position.size.height + top_margin_value + + bottom_margin_value; + + for fragment in self.box_.iter() { + let mut position = fragment.border_box.get(); + position.size.height = position.size.height + top_margin_value + bottom_margin_value; + fragment.border_box.set(position); } } - /// Compute margin_top and margin_bottom. Also, it is decided whether top margin and - /// bottom margin are collapsible according to CSS 2.1 § 8.3.1. - pub fn precompute_margin(&mut self) -> (Au, Au, bool, bool) { - match self.box_ { - // Margins for an absolutely positioned element do not collapse with - // its children. - Some(ref box_) if !self.is_absolutely_positioned() => { - let top_margin_collapsible = !self.is_root && - box_.border.get().top == Au(0) && - box_.padding.get().top == Au(0); - - let bottom_margin_collapsible = !self.is_root && - box_.border.get().bottom == Au(0) && - box_.padding.get().bottom == Au(0); - - let margin_top = box_.margin.get().top; - let margin_bottom = box_.margin.get().bottom; - - (margin_top, margin_bottom, top_margin_collapsible, bottom_margin_collapsible) - }, - _ => (Au(0), Au(0), false, false) - } - } - - /// Compute collapsed margins between adjacent children or between the first/last child and parent - /// according to CSS 2.1 § 8.3.1. Current y position(cur_y) is continually updated for collapsing result. - pub fn compute_margin_collapse(&mut self, - cur_y: &mut Au, - top_offset: &mut Au, - margin_top: &mut Au, - margin_bottom: &mut Au, - top_margin_collapsible: bool, - bottom_margin_collapsible: bool) -> Au { - // How much to move up by to get to the beginning of - // current kid flow. - // Example: if previous sibling's margin-bottom is 20px and your - // margin-top is 12px, the collapsed margin will be 20px. Since cur_y - // will be at the bottom margin edge of the previous sibling, we have - // to move up by 12px to get to our top margin edge. So, `collapsing` - // will be set to 12px - let mut first_in_flow = true; - let mut collapsing = Au::new(0); - // The amount of margin that we can potentially collapse with - let mut collapsible = if top_margin_collapsible { - *margin_top - } else { - Au(0) - }; + /// Assign height for current flow. + /// + /// * Collapse margins for flow's children and set in-flow child flows' y-coordinates now that + /// we know their heights. + /// * Calculate and set the height of the current flow. + /// * Calculate height, vertical margins, and y-coordinate for the flow's box. Ideally, this + /// should be calculated using CSS § 10.6.7. + /// + /// For absolute flows, we store the calculated content height for the flow. We defer the + /// calculation of the other values until a later traversal. + /// + /// `inline(always)` because this is only ever called by in-order or non-in-order top-level + /// methods + #[inline(always)] + pub fn assign_height_block_base(&mut self, + layout_context: &mut LayoutContext, + inorder: bool, + margins_may_collapse: MarginsMayCollapseFlag) { + // Our current border-box position. + let mut cur_y = Au(0); + + // The sum of our top border and top padding. + let mut top_offset = Au(0); - // At this point, cur_y is at the content edge of the flow's box_ + // Absolute positioning establishes a block formatting context. Don't propagate floats + // in or out. (But do propagate them between kids.) + if inorder && self.is_absolutely_positioned() { + self.base.floats = Floats::new(); + } + if margins_may_collapse != MarginsMayCollapse { + self.base.floats = Floats::new(); + } + + let mut margin_collapse_info = MarginCollapseInfo::new(); + for fragment in self.box_.iter() { + self.base.floats.translate(Point2D(-fragment.left_offset(), Au(0))); + + top_offset = fragment.border.get().top + fragment.padding.get().top; + translate_including_floats(&mut cur_y, top_offset, inorder, &mut self.base.floats); + + let can_collapse_top_margin_with_kids = + margins_may_collapse == MarginsMayCollapse && + !self.is_absolutely_positioned() && + fragment.border.get().top == Au(0) && + fragment.padding.get().top == Au(0); + margin_collapse_info.initialize_top_margin(fragment, + can_collapse_top_margin_with_kids); + } + + // At this point, cur_y is at the content edge of the flow's box. + let mut floats = self.base.floats.clone(); + let mut layers_needed_for_descendants = false; for kid in self.base.child_iter() { - // At this point, cur_y is at bottom margin edge of previous kid if kid.is_absolutely_positioned() { - // Assume that the `hypothetical box` for an absolute flow - // starts immediately after the bottom margin edge of the - // previous flow. - kid.as_block().base.position.origin.y = *cur_y; - // Skip the collapsing for absolute flow kids and continue + // Assume that the *hypothetical box* for an absolute flow starts immediately after + // the bottom border edge of the previous flow. + kid.as_block().base.position.origin.y = cur_y; + + if inorder { + kid.assign_height_inorder(layout_context) + } + + propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid); + + // Skip the collapsing and float processing for absolute flow kids and continue // with the next flow. - } else { - kid.collapse_margins(top_margin_collapsible, - &mut first_in_flow, - margin_top, - top_offset, - &mut collapsing, - &mut collapsible); - let child_node = flow::mut_base(kid); - *cur_y = *cur_y - collapsing; - // At this point, after moving up by `collapsing`, cur_y is at the - // top margin edge of kid - child_node.position.origin.y = *cur_y; - *cur_y = *cur_y + child_node.position.size.height; - // At this point, cur_y is at the bottom margin edge of kid + continue } - } - self.collect_static_y_offsets_from_kids(); + // Assign height now for the child if it was impacted by floats and we couldn't before. + let mut floats_out = None; + if inorder { + if !kid.is_float() { + let kid_base = flow::mut_base(kid); + if kid_base.clear != clear::none { + // We have clearance, so assume there are no floats in and perform layout. + // + // FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or + // `clear: right` and there are still floats to impact, of course. But this + // gets complicated with margin collapse. Possibly the right thing to do is + // to lay out the block again in this rare case. (Note that WebKit can lay + // blocks out twice; this may be related, although I haven't looked into it + // closely.) + kid_base.floats = Floats::new() + } else { + kid_base.floats = floats.clone() + } + } else { + let kid_base = flow::mut_base(kid); + kid_base.position.origin.y = margin_collapse_info.current_float_ceiling(); + kid_base.floats = floats.clone() + } + + kid.assign_height_inorder(layout_context); + + floats_out = Some(flow::mut_base(kid).floats.clone()); + + // A horrible hack that has to do with the fact that `origin.y` is used for + // something else later (containing block for float). + if kid.is_float() { + flow::mut_base(kid).position.origin.y = cur_y; + } + } - // The bottom margin collapses with its last in-flow block-level child's bottom margin - // if the parent has no bottom border, no bottom padding. - // The bottom margin for an absolutely positioned element does not - // collapse even with its children. - collapsing = if bottom_margin_collapsible && !self.is_absolutely_positioned() { - if *margin_bottom < collapsible { - *margin_bottom = collapsible; + propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid); + + // If the child was a float, stop here. + if kid.is_float() { + if inorder { + floats = floats_out.take_unwrap(); + } + continue } - collapsible - } else { - Au::new(0) - }; - collapsing - } + // Handle any (possibly collapsed) top margin. + let kid_base = flow::mut_base(kid); + let delta = margin_collapse_info.advance_top_margin(&kid_base.collapsible_margins); + translate_including_floats(&mut cur_y, delta, inorder, &mut floats); + + // Clear past floats, if necessary. + if inorder { + let clearance = match kid_base.clear { + clear::none => Au(0), + clear::left => floats.clearance(ClearLeft), + clear::right => floats.clearance(ClearRight), + clear::both => floats.clearance(ClearBoth), + }; + cur_y = cur_y + clearance + } + + // At this point, `cur_y` is at the border edge of the child. + assert!(kid_base.position.origin.y == Au(0)); + kid_base.position.origin.y = cur_y; + + // If this was an inorder traversal, grab the child's floats now. + if inorder { + floats = floats_out.take_unwrap() + } + + // Move past the child's border box. Do not use the `translate_including_floats` + // function here because the child has already translated floats past its border box. + cur_y = cur_y + kid_base.position.size.height; + + // Handle any (possibly collapsed) bottom margin. + let delta = margin_collapse_info.advance_bottom_margin(&kid_base.collapsible_margins); + translate_including_floats(&mut cur_y, delta, inorder, &mut floats); + } + + self.base + .flags_info + .flags + .set_layers_needed_for_descendants(layers_needed_for_descendants); + + self.collect_static_y_offsets_from_kids(); + + // Add in our bottom margin and compute our collapsible margins. + for fragment in self.box_.iter() { + let can_collapse_bottom_margin_with_kids = + margins_may_collapse == MarginsMayCollapse && + !self.is_absolutely_positioned() && + fragment.border.get().bottom == Au(0) && + fragment.padding.get().bottom == Au(0); + let (collapsible_margins, delta) = + margin_collapse_info.finish_and_compute_collapsible_margins( + fragment, + can_collapse_bottom_margin_with_kids); + self.base.collapsible_margins = collapsible_margins; + translate_including_floats(&mut cur_y, delta, inorder, &mut floats); + } + + // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but this + // is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat the + // root element as having `overflow: scroll` and use the layers-based scrolling + // infrastructure to make it scrollable. + let mut height = cur_y - top_offset; + if self.is_root() { + height = Au::max(layout_context.screen_size.height, height) + } - /// For an absolutely positioned element, store the content height for use in calculating - /// the absolute flow's dimensions later. - pub fn store_content_height_if_absolutely_positioned(&mut self, - height: Au) -> bool { if self.is_absolutely_positioned() { + // The content height includes all the floats per CSS 2.1 § 10.6.7. The easiest way to + // handle this is to just treat this as clearance. + height = height + floats.clearance(ClearBoth); + + // Fixed position layers get layers. + if self.is_fixed() { + self.base.flags_info.flags.set_needs_layer(true) + } + + // Store the content height for use in calculating the absolute flow's dimensions + // later. for box_ in self.box_.iter() { let mut temp_position = box_.border_box.get(); temp_position.size.height = height; box_.border_box.set(temp_position); } - return true; + return } - false - } - - /// Compute the box height and set border_box and margin of the box. - pub fn compute_height_position(&mut self, - height: &mut Au, - border_and_padding: Au, - margin_top: Au, - margin_bottom: Au, - clearance: Au) { - // Here, height is content height of box_ - let mut noncontent_height = border_and_padding; - for box_ in self.box_.iter() { - let mut position = box_.border_box.get(); - let mut margin = box_.margin.get(); - // The associated box is the border box of this flow. - // Margin after collapse - margin.top = margin_top; - margin.bottom = margin_bottom; + for fragment in self.box_.iter() { + let mut candidate_height_iterator = CandidateHeightIterator::new(fragment.style(), + None); + for (candidate_height, new_candidate_height) in candidate_height_iterator { + *new_candidate_height = match candidate_height { + Auto => height, + Specified(value) => value + } + } - position.origin.y = clearance + margin.top; - // Border box height - position.size.height = *height + noncontent_height; + // Adjust `cur_y` as necessary to account for the explicitly-specified height. + height = candidate_height_iterator.candidate_value; + let delta = height - (cur_y - top_offset); + translate_including_floats(&mut cur_y, delta, inorder, &mut floats); - noncontent_height = noncontent_height + clearance + margin.top + margin.bottom; + // Compute content height and noncontent height. + let bottom_offset = fragment.border.get().bottom + fragment.padding.get().bottom; + translate_including_floats(&mut cur_y, bottom_offset, inorder, &mut floats); - box_.border_box.set(position); - box_.margin.set(margin); + // Now that `cur_y` is at the bottom of the border box, compute the final border box + // position. + let mut border_box = fragment.border_box.get(); + border_box.size.height = cur_y; + border_box.origin.y = Au(0); + fragment.border_box.set(border_box); + self.base.position.size.height = cur_y; } - // Height of margin box + clearance - self.base.position.size.height = *height + noncontent_height; - } + self.base.floats = floats.clone(); + self.adjust_boxes_for_collapsed_margins_if_root(); - /// Set floats_out at the last step of the assign height calculation. - pub fn set_floats_out_if_inorder(&mut self, - inorder: bool, - height: Au, - cur_y: Au, - top_offset: Au, - bottom_offset: Au, - left_offset: Au) { - if inorder { - let extra_height = height - (cur_y - top_offset) + bottom_offset; - self.base.floats.translate(Point2D(left_offset, -extra_height)); - } - } - - /// Assign heights for all flows in absolute flow tree and store overflow for all - /// absolute descendants. - pub fn assign_height_absolute_flows(&mut self, ctx: &mut LayoutContext) { if self.is_root_of_absolute_flow_tree() { // Assign heights for all flows in this Absolute flow tree. // This is preorder because the height of an absolute flow may depend on // the height of its CB, which may also be an absolute flow. - self.traverse_preorder_absolute_flows(&mut AbsoluteAssignHeightsTraversal(ctx)); + self.traverse_preorder_absolute_flows(&mut AbsoluteAssignHeightsTraversal( + layout_context)); // Store overflow for all absolute descendants. self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal { - layout_context: ctx, + layout_context: layout_context, }); } } - /// Assign height for current flow. - /// - /// + Collapse margins for flow's children and set in-flow child flows' - /// y-coordinates now that we know their heights. - /// + Calculate and set the height of the current flow. - /// + Calculate height, vertical margins, and y-coordinate for the flow's - /// box. Ideally, this should be calculated using CSS Section 10.6.7 - /// - /// For absolute flows, store the calculated content height for the flow. - /// Defer the calculation of the other values till a later traversal. - /// - /// inline(always) because this is only ever called by in-order or non-in-order top-level - /// methods - #[inline(always)] - fn assign_height_block_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { - - // Note: Ignoring clearance for absolute flows as of now. - let ignore_clear = self.is_absolutely_positioned(); - let (clearance, mut top_offset, bottom_offset, left_offset) = - self.initialize_offsets(ignore_clear); - - self.handle_children_floats_if_necessary(ctx, inorder, - left_offset, top_offset); - - let (mut margin_top, mut margin_bottom, - top_margin_collapsible, bottom_margin_collapsible) = self.precompute_margin(); - - let mut cur_y = top_offset; - let collapsing = self.compute_margin_collapse(&mut cur_y, - &mut top_offset, - &mut margin_top, - &mut margin_bottom, - top_margin_collapsible, - bottom_margin_collapsible); - - // TODO: A box's own margins collapse if the 'min-height' property is zero, and it has neither - // top or bottom borders nor top or bottom padding, and it has a 'height' of either 0 or 'auto', - // and it does not contain a line box, and all of its in-flow children's margins (if any) collapse. - - let screen_height = ctx.screen_size.height; - - let mut height = if self.is_root() { - // FIXME(pcwalton): The max is taken here so that you can scroll the page, but this is - // not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat - // the root element as having `overflow: scroll` and use the layers-based scrolling - // infrastructure to make it scrollable. - Au::max(screen_height, cur_y) - } else { - // (cur_y - collapsing) will get you the the bottom margin-edge of - // the bottom-most child. - // top_offset: top margin-edge of the topmost child. - // hence, height = content height - cur_y - top_offset - collapsing - }; - - // For an absolutely positioned element, store the content height and stop the function. - if self.store_content_height_if_absolutely_positioned(height) { - return; - } - - let mut border_and_padding = Au::new(0); - for box_ in self.box_.iter() { - let style = box_.style(); - - // At this point, `height` is the height of the containing block, so passing `height` - // as the second argument here effectively makes percentages relative to the containing - // block per CSS 2.1 § 10.5. - height = match MaybeAuto::from_style(style.Box.get().height, height) { - Auto => height, - Specified(value) => value - }; - - border_and_padding = box_.padding.get().top + box_.padding.get().bottom + - box_.border.get().top + box_.border.get().bottom; - } - - self.compute_height_position(&mut height, - border_and_padding, - margin_top, - margin_bottom, - clearance); - - self.set_floats_out_if_inorder(inorder, height, cur_y, top_offset, bottom_offset, left_offset); - self.assign_height_absolute_flows(ctx); - if self.is_root() { - self.assign_height_store_overflow_fixed_flows(ctx); - } - } - - /// Assign height for all fixed descendants. - /// - /// A flat iteration over all fixed descendants, passing their respective - /// static y offsets. - /// Also, store overflow immediately because nothing else depends on a - /// fixed flow's height. - fn assign_height_store_overflow_fixed_flows(&mut self, ctx: &mut LayoutContext) { - assert!(self.is_root()); - let mut descendant_offset_iter = self.base.fixed_descendants.iter_with_offset(); - // Pass in the respective static y offset for each descendant. - for (ref mut descendant_link, ref y_offset) in descendant_offset_iter { - match descendant_link.resolve() { - Some(fixed_flow) => { - { - let block = fixed_flow.as_block(); - // The stored y_offset is wrt to the flow box (which - // will is also the CB, so it is the correct final value). - block.static_y_offset = **y_offset; - block.calculate_abs_height_and_margins(ctx); - } - fixed_flow.store_overflow(ctx); - } - None => fail!("empty Rawlink to a descendant") - } - } - } - /// Add placement information about current float flow for use by the parent. /// /// Also, use information given by parent about other floats to find out @@ -1009,7 +1078,7 @@ impl BlockFlow { let info = PlacementInfo { size: Size2D(self.base.position.size.width + full_noncontent_width, height + margin_height), - ceiling: clearance, + ceiling: clearance + self.base.position.origin.y, max_width: self.float.get_ref().containing_width, kind: self.float.get_ref().float_kind, }; @@ -1060,7 +1129,7 @@ impl BlockFlow { cur_y = cur_y + child_base.position.size.height; } - let mut height = cur_y - top_offset; + let content_height = cur_y - top_offset; let mut noncontent_height; let box_ = self.box_.as_ref().unwrap(); @@ -1072,107 +1141,83 @@ impl BlockFlow { noncontent_height = box_.padding.get().top + box_.padding.get().bottom + box_.border.get().top + box_.border.get().bottom; - //TODO(eatkinson): compute heights properly using the 'height' property. - let height_prop = MaybeAuto::from_style(box_.style().Box.get().height, - Au::new(0)).specified_or_zero(); + // Calculate content height, taking `min-height` and `max-height` into account. - height = geometry::max(height, height_prop) + noncontent_height; - debug!("assign_height_float -- height: {}", height); + let mut candidate_height_iterator = CandidateHeightIterator::new(box_.style(), None); + for (candidate_height, new_candidate_height) in candidate_height_iterator { + *new_candidate_height = match candidate_height { + Auto => content_height, + Specified(value) => value, + } + } - position.size.height = height; - box_.border_box.set(position); - } + let content_height = candidate_height_iterator.candidate_value; - /// In case of float, initialize containing_width at the beginning step of assign_width. - pub fn set_containing_width_if_float(&mut self, containing_block_width: Au) { - if self.is_float() { - self.float.get_mut_ref().containing_width = containing_block_width; + debug!("assign_height_float -- height: {}", content_height + noncontent_height); - // Parent usually sets this, but floats are never inorder - self.base.flags_info.flags.set_inorder(false); - } + position.size.height = content_height + noncontent_height; + box_.border_box.set(position); } - /// Assign the computed left_content_edge and content_width to children. - pub fn propagate_assigned_width_to_children(&mut self, left_content_edge: Au, - content_width: Au, - opt_col_widths: Option<~[Au]>) { - let has_inorder_children = if self.is_float() { - self.base.num_floats > 0 - } else { - self.base.flags_info.flags.inorder() || self.base.num_floats > 0 - }; - - let kid_abs_cb_x_offset; - if self.is_positioned() { - match self.box_ { - Some(ref box_) => { - // Pass yourself as a new Containing Block - // The static x offset for any immediate kid flows will be the - // left padding - kid_abs_cb_x_offset = box_.padding.get().left; - } - None => fail!("BlockFlow: no principal box found"), + fn build_display_list_block_common(&mut self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo, + offset: Point2D<Au>, + background_border_level: BackgroundAndBorderLevel) { + let mut info = *info; + let mut rel_offset = Point2D(Au(0), Au(0)); + for fragment in self.box_.iter() { + rel_offset = fragment.relative_position(&info.relative_containing_block_size); + + // Add the box that starts the block context. + fragment.build_display_list(stacking_context, + builder, + &info, + self.base.abs_position + rel_offset + offset, + (&*self) as &Flow, + background_border_level); + + // For relatively-positioned descendants, the containing block formed by a block is + // just the content box. The containing block for absolutely-positioned descendants, + // on the other hand, only established if we are positioned. + info.relative_containing_block_size = fragment.content_box_size(); + if self.is_positioned() { + info.absolute_containing_block_position = + self.base.abs_position + + self.generated_cb_position() + + fragment.relative_position(&info.relative_containing_block_size) } - } else { - // For kids, the left margin edge will be at our left content edge. - // The current static offset is at our left margin - // edge. So move in to the left content edge. - kid_abs_cb_x_offset = self.base.absolute_static_x_offset + left_content_edge; } - let kid_fixed_cb_x_offset = self.base.fixed_static_x_offset + left_content_edge; - - // FIXME(ksh8281): avoid copy - let flags_info = self.base.flags_info.clone(); - - // Left margin edge of kid flow is at our left content edge - let mut kid_left_margin_edge = left_content_edge; - // Width of kid flow is our content width - let mut kid_width = content_width; - for (i, kid) in self.base.child_iter().enumerate() { - assert!(kid.is_block_flow() || kid.is_inline_flow() || kid.is_table_kind()); - match opt_col_widths { - Some(ref col_widths) => { - // If kid is table_rowgroup or table_row, the column widths info should be - // copied from its parent. - if kid.is_table_rowgroup() { - kid.as_table_rowgroup().col_widths = col_widths.clone() - } else if kid.is_table_row() { - kid.as_table_row().col_widths = col_widths.clone() - } else if kid.is_table_cell() { - // If kid is table_cell, the x offset and width for each cell should be - // calculated from parent's column widths info. - kid_left_margin_edge = if i == 0 { - Au(0) - } else { - kid_left_margin_edge + col_widths[i-1] - }; - kid_width = col_widths[i] - } - } - None => {} - } - if kid.is_block_flow() { - let kid_block = kid.as_block(); - kid_block.base.absolute_static_x_offset = kid_abs_cb_x_offset; - kid_block.base.fixed_static_x_offset = kid_fixed_cb_x_offset; + let this_position = self.base.abs_position; + for kid in self.base.child_iter() { + { + let kid_base = flow::mut_base(kid); + kid_base.abs_position = this_position + kid_base.position.origin + rel_offset + + offset; } - let child_base = flow::mut_base(kid); - child_base.position.origin.x = kid_left_margin_edge; - child_base.position.size.width = kid_width; - child_base.flags_info.flags.set_inorder(has_inorder_children); - if !child_base.flags_info.flags.inorder() { - child_base.floats = Floats::new(); + if kid.is_absolutely_positioned() { + // All absolute flows will be handled by their containing block. + continue } - // Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow. - // - // TODO(pcwalton): When we have out-of-flow children, don't unconditionally propagate. + kid.build_display_list(stacking_context, builder, &info); + } - child_base.flags_info.propagate_text_decoration_from_parent(&flags_info); - child_base.flags_info.propagate_text_alignment_from_parent(&flags_info) + // Process absolute descendant links. + let mut absolute_info = info; + absolute_info.layers_needed_for_positioned_flows = + self.base.flags_info.flags.layers_needed_for_descendants(); + for abs_descendant_link in self.base.abs_descendants.iter() { + match abs_descendant_link.resolve() { + Some(flow) => { + // TODO(pradeep): Send in our absolute position directly. + flow.build_display_list(stacking_context, builder, &absolute_info) + } + None => fail!("empty Rawlink to a descendant") + } } } @@ -1180,104 +1225,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(#2009, 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. @@ -1293,12 +1271,10 @@ impl BlockFlow { for box_ in self.box_.iter() { // This is the stored content height value from assign-height - let content_height = box_.border_box.get().size.height; + let content_height = box_.border_box.get().size.height - box_.noncontent_height(); let style = box_.style(); - let height_used_val = MaybeAuto::from_style(style.Box.get().height, containing_block_height); - // Non-auto margin-top and margin-bottom values have already been // calculated during assign-width. let margin = box_.margin.get(); @@ -1316,7 +1292,8 @@ impl BlockFlow { MaybeAuto::from_style(style.PositionOffsets.get().bottom, containing_block_height)); let available_height = containing_block_height - box_.noncontent_height(); - let solution = if self.is_replaced_content() { + let mut solution = None; + if self.is_replaced_content() { // Calculate used value of height just like we do for inline replaced elements. // TODO: Pass in the containing block height when Box's // assign-height can handle it correctly. @@ -1325,25 +1302,36 @@ impl BlockFlow { // margin because of erroneous height calculation in Box_. // Check this when that has been fixed. let height_used_val = box_.border_box.get().size.height; - HeightConstraintSolution::solve_vertical_constraints_abs_replaced(height_used_val, - margin_top, - margin_bottom, - top, - bottom, - content_height, - available_height, - static_y_offset) + solution = Some(HeightConstraintSolution::solve_vertical_constraints_abs_replaced( + height_used_val, + margin_top, + margin_bottom, + top, + bottom, + content_height, + available_height, + static_y_offset)); } else { - HeightConstraintSolution::solve_vertical_constraints_abs_nonreplaced( - height_used_val, - margin_top, - margin_bottom, - top, - bottom, - content_height, - available_height, - static_y_offset) - }; + let mut candidate_height_iterator = + CandidateHeightIterator::new(style, Some(containing_block_height)); + + for (height_used_val, new_candidate_height) in candidate_height_iterator { + solution = + Some(HeightConstraintSolution::solve_vertical_constraints_abs_nonreplaced( + height_used_val, + margin_top, + margin_bottom, + top, + bottom, + content_height, + available_height, + static_y_offset)); + + *new_candidate_height = solution.unwrap().height + } + } + + let solution = solution.unwrap(); let mut margin = box_.margin.get(); margin.top = solution.margin_top; @@ -1351,63 +1339,70 @@ 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(#781, pcwalton): `z-index`. + parent_stacking_context.positioned_descendants.push((0, stacking_context.flatten())); + return + } + + // If we got here, then we need a new layer. + 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()), + position: Rect(origin, size), + background_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. @@ -1420,6 +1415,113 @@ impl BlockFlow { fn get_hypothetical_top_edge(&self) -> Au { self.base.position.origin.y } + + /// Initializes the containing width if this block flow is a float. This is done at the start + /// of `assign_widths`. + fn set_containing_width_if_float(&mut self, containing_block_width: Au) { + if self.is_float() { + self.float.get_mut_ref().containing_width = containing_block_width; + + // Parent usually sets this, but floats are never inorder + self.base.flags_info.flags.set_inorder(false); + } + } + + /// Assigns the computed left content edge and width to all the children of this block flow. + pub fn propagate_assigned_width_to_children(&mut self, + left_content_edge: Au, + content_width: Au, + opt_col_widths: Option<~[Au]>) { + let has_inorder_children = if self.is_float() { + self.base.num_floats > 0 + } else { + self.base.flags_info.flags.inorder() || self.base.num_floats > 0 + }; + + let kid_abs_cb_x_offset; + if self.is_positioned() { + match self.box_ { + Some(ref box_) => { + // Pass yourself as a new Containing Block + // The static x offset for any immediate kid flows will be the + // left padding + kid_abs_cb_x_offset = box_.padding.get().left; + } + None => fail!("BlockFlow: no principal box found"), + } + } else { + // For kids, the left margin edge will be at our left content edge. + // The current static offset is at our left margin + // edge. So move in to the left content edge. + kid_abs_cb_x_offset = self.base.absolute_static_x_offset + left_content_edge; + } + let kid_fixed_cb_x_offset = self.base.fixed_static_x_offset + left_content_edge; + + // This value is used only for table cells. + let mut kid_left_margin_edge = left_content_edge; + + let flags_info = self.base.flags_info.clone(); + for (i, kid) in self.base.child_iter().enumerate() { + if kid.is_block_flow() { + let kid_block = kid.as_block(); + kid_block.base.absolute_static_x_offset = kid_abs_cb_x_offset; + kid_block.base.fixed_static_x_offset = kid_fixed_cb_x_offset; + } + + { + let child_base = flow::mut_base(kid); + // Left margin edge of kid flow is at our left content edge + child_base.position.origin.x = left_content_edge; + // Width of kid flow is our content width + child_base.position.size.width = content_width; + child_base.flags_info.flags.set_inorder(has_inorder_children); + + if !child_base.flags_info.flags.inorder() { + child_base.floats = Floats::new(); + } + } + + // Handle tables. + match opt_col_widths { + Some(ref col_widths) => { + // If kid is table_rowgroup or table_row, the column widths info should be + // copied from its parent. + let kid_width; + if kid.is_table() || kid.is_table_rowgroup() || kid.is_table_row() { + *kid.col_widths() = col_widths.clone(); + + // Width of kid flow is our content width. + kid_width = content_width + } else if kid.is_table_cell() { + // If kid is table_cell, the x offset and width for each cell should be + // calculated from parent's column widths info. + kid_left_margin_edge = if i == 0 { + Au(0) + } else { + kid_left_margin_edge + col_widths[i-1] + }; + + kid_width = col_widths[i] + } else { + // Width of kid flow is our content width. + kid_width = content_width + } + + let kid_base = flow::mut_base(kid); + kid_base.position.origin.x = kid_left_margin_edge; + kid_base.position.size.width = kid_width; + } + None => {} + } + + // Per CSS 2.1 § 16.3.1, text decoration propagates to all children in flow. + // + // TODO(#2018, pcwalton): Do this in the cascade instead. + let child_base = flow::mut_base(kid); + child_base.flags_info.propagate_text_decoration_from_parent(&flags_info); + child_base.flags_info.propagate_text_alignment_from_parent(&flags_info) + } + } } impl Flow for BlockFlow { @@ -1441,20 +1543,22 @@ impl Flow for BlockFlow { min/pref widths based on child context widths and dimensions of any boxes it is responsible for flowing. */ - /* TODO: absolute contexts */ /* TODO: inline-blocks */ fn bubble_widths(&mut self, _: &mut LayoutContext) { - let mut min_width = Au::new(0); - let mut pref_width = Au::new(0); let mut num_floats = 0; - /* find max width from child block contexts */ + // Find the maximum width from children. + let mut intrinsic_widths = IntrinsicWidths::new(); for child_ctx in self.base.child_iter() { assert!(child_ctx.is_block_flow() || child_ctx.is_inline_flow() || child_ctx.is_table_kind()); let child_base = flow::mut_base(child_ctx); - min_width = geometry::max(min_width, child_base.min_width); - pref_width = geometry::max(pref_width, child_base.pref_width); + intrinsic_widths.minimum_width = + geometry::max(intrinsic_widths.minimum_width, + child_base.intrinsic_widths.total_minimum_width()); + intrinsic_widths.preferred_width = + geometry::max(intrinsic_widths.preferred_width, + child_base.intrinsic_widths.total_preferred_width()); num_floats = num_floats + child_base.num_floats; } @@ -1465,21 +1569,22 @@ impl Flow for BlockFlow { self.base.num_floats = num_floats; } - /* if not an anonymous block context, add in block box's widths. - these widths will not include child elements, just padding etc. */ + // Add in borders, padding, and margins. for box_ in self.box_.iter() { { // Can compute border width here since it doesn't depend on anything. box_.compute_borders(box_.style()) } - let (this_minimum_width, this_preferred_width) = box_.minimum_and_preferred_widths(); - min_width = min_width + this_minimum_width; - pref_width = pref_width + this_preferred_width; + let box_intrinsic_widths = box_.intrinsic_widths(); + intrinsic_widths.minimum_width = geometry::max(intrinsic_widths.minimum_width, + box_intrinsic_widths.minimum_width); + intrinsic_widths.preferred_width = geometry::max(intrinsic_widths.preferred_width, + box_intrinsic_widths.preferred_width); + intrinsic_widths.surround_width = box_intrinsic_widths.surround_width } - self.base.min_width = min_width; - self.base.pref_width = pref_width; + self.base.intrinsic_widths = intrinsic_widths } /// Recursively (top-down) determines the actual width of child contexts and boxes. When called @@ -1514,6 +1619,10 @@ impl Flow for BlockFlow { self.compute_used_width(ctx, containing_block_width); for box_ in self.box_.iter() { + // Assign `clear` now so that the assign-heights pass will have the correct value for + // it. + self.base.clear = box_.style().Box.get().clear; + // Move in from the left border edge left_content_edge = box_.border_box.get().origin.x + box_.padding.get().left + box_.border.get().left; @@ -1539,7 +1648,7 @@ impl Flow for BlockFlow { self.assign_height_float_inorder(); } else { debug!("assign_height_inorder: assigning height for block"); - self.assign_height_block_base(ctx, true); + self.assign_height_block_base(ctx, true, MarginsMayCollapse); } } @@ -1560,51 +1669,10 @@ impl Flow for BlockFlow { self.assign_height_inorder(ctx); return; } - self.assign_height_block_base(ctx, false); + self.assign_height_block_base(ctx, false, MarginsMayCollapse); } } - // CSS Section 8.3.1 - Collapsing Margins - // `self`: the Flow whose margins we want to collapse. - // `collapsing`: value to be set by this function. This tells us how much - // of the top margin has collapsed with a previous margin. - // `collapsible`: Potential collapsible margin at the bottom of this flow's box. - fn collapse_margins(&mut self, - top_margin_collapsible: bool, - first_in_flow: &mut bool, - margin_top: &mut Au, - top_offset: &mut Au, - collapsing: &mut Au, - collapsible: &mut Au) { - if self.is_float() { - // Margins between a floated box and any other box do not collapse. - *collapsing = Au::new(0); - return; - } - - for box_ in self.box_.iter() { - // The top margin collapses with its first in-flow block-level child's - // top margin if the parent has no top border, no top padding. - if *first_in_flow && top_margin_collapsible { - // If top-margin of parent is less than top-margin of its first child, - // the parent box goes down until its top is aligned with the child. - if *margin_top < box_.margin.get().top { - // TODO: The position of child floats should be updated and this - // would influence clearance as well. See #725 - let extra_margin = box_.margin.get().top - *margin_top; - *top_offset = *top_offset + extra_margin; - *margin_top = box_.margin.get().top; - } - } - // The bottom margin of an in-flow block-level element collapses - // with the top margin of its next in-flow block-level sibling. - *collapsing = geometry::min(box_.margin.get().top, *collapsible); - *collapsible = box_.margin.get().bottom; - } - - *first_in_flow = false; - } - fn mark_as_root(&mut self) { self.is_root = true } @@ -1663,6 +1731,18 @@ impl Flow for BlockFlow { } } + fn layer_id(&self, fragment_index: uint) -> LayerId { + // FIXME(#2010, 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: " @@ -1676,6 +1756,10 @@ impl Flow for BlockFlow { None => ~"", }) } + + fn is_absolute_containing_block(&self) -> bool { + self.is_positioned() + } } /// The inputs for the widths-and-margins constraint equation. diff --git a/src/components/main/layout/box_.rs b/src/components/main/layout/box_.rs index 972ac79da9a..f5acd633944 100644 --- a/src/components/main/layout/box_.rs +++ b/src/components/main/layout/box_.rs @@ -4,17 +4,29 @@ //! The `Box` type, which represents the leaves of the layout tree. +use css::node_style::StyledNode; +use layout::construct::FlowConstructor; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo, ToGfxColor}; +use layout::floats::{ClearBoth, ClearLeft, ClearRight, ClearType}; +use layout::flow::{Flow, FlowFlagsInfo}; +use layout::flow; +use layout::model::{Auto, IntrinsicWidths, MaybeAuto, Specified, specified}; +use layout::model; +use layout::util::OpaqueNodeMethods; +use layout::wrapper::{TLayoutNode, ThreadSafeLayoutNode}; + use extra::url::Url; use sync::{MutexArc, Arc}; use geom::{Point2D, Rect, Size2D, SideOffsets2D}; use geom::approxeq::ApproxEq; use gfx::color::rgb; -use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass}; -use gfx::display_list::{LineDisplayItem, LineDisplayItemClass}; -use gfx::display_list::{ImageDisplayItem, ImageDisplayItemClass}; -use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, TextDisplayItem}; -use gfx::display_list::{TextDisplayItemClass, TextDisplayItemFlags, ClipDisplayItem}; -use gfx::display_list::{ClipDisplayItemClass, DisplayListCollection}; +use gfx::display_list::{BackgroundAndBorderLevel, BaseDisplayItem, BorderDisplayItem}; +use gfx::display_list::{BorderDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass}; +use gfx::display_list::{DisplayList, ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem}; +use gfx::display_list::{LineDisplayItemClass, OpaqueNode, SolidColorDisplayItem}; +use gfx::display_list::{SolidColorDisplayItemClass, StackingContext, TextDisplayItem}; +use gfx::display_list::{TextDisplayItemClass, TextDisplayItemFlags}; use gfx::font::FontStyle; use gfx::text::text_run::TextRun; use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId}; @@ -24,26 +36,16 @@ use servo_util::geometry::Au; use servo_util::geometry; use servo_util::range::*; use servo_util::namespace; +use servo_util::smallvec::{SmallVec, SmallVec0}; use servo_util::str::is_whitespace; - use std::cast; use std::cell::RefCell; use std::num::Zero; use style::{ComputedValues, TElement, TNode, cascade, initial_values}; use style::computed_values::{LengthOrPercentage, LengthOrPercentageOrAuto, overflow, LPA_Auto}; -use style::computed_values::{border_style, clear, font_family, line_height, position}; -use style::computed_values::{text_align, text_decoration, vertical_align, visibility, white_space}; - -use css::node_style::StyledNode; -use layout::construct::FlowConstructor; -use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor}; -use layout::floats::{ClearBoth, ClearLeft, ClearRight, ClearType}; -use layout::flow::{Flow, FlowFlagsInfo}; -use layout::flow; -use layout::model::{MaybeAuto, specified, Auto, Specified}; -use layout::util::OpaqueNode; -use layout::wrapper::{TLayoutNode, ThreadSafeLayoutNode}; +use style::computed_values::{background_attachment, background_repeat, border_style, clear}; +use style::computed_values::{font_family, line_height, position, text_align, text_decoration}; +use style::computed_values::{vertical_align, visibility, white_space}; /// Boxes (`struct Box`) are the leaves of the layout tree. They cannot position themselves. In /// general, boxes do not have a simple correspondence with CSS boxes in the specification: @@ -288,18 +290,21 @@ pub enum SplitBoxResult { } -/// data for inline boxes +/// Data for inline boxes. +/// +/// FIXME(#2013, pcwalton): Copying `InlineParentInfo` vectors all the time is really inefficient. +/// Use atomic reference counting instead. #[deriving(Clone)] pub struct InlineInfo { - parent_info: ~[InlineParentInfo], + parent_info: SmallVec0<InlineParentInfo>, baseline: Au, } impl InlineInfo { pub fn new() -> InlineInfo { InlineInfo { - parent_info: ~[], - baseline: Au::new(0), + parent_info: SmallVec0::new(), + baseline: Au(0), } } } @@ -414,10 +419,16 @@ def_noncontent_horiz!(left, merge_noncontent_inline_left, clear_noncontent_inl def_noncontent_horiz!(right, merge_noncontent_inline_right, clear_noncontent_inline_right) impl Box { - /// Constructs a new `Box` instance. + /// Constructs a new `Box` instance for the given node. + /// + /// Arguments: + /// + /// * `constructor`: The flow constructor. + /// + /// * `node`: The node to create a box for. pub fn new(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> Box { Box { - node: OpaqueNode::from_thread_safe_layout_node(node), + node: OpaqueNodeMethods::from_thread_safe_layout_node(node), style: node.style().clone(), border_box: RefCell::new(Au::zero_rect()), border: RefCell::new(Zero::zero()), @@ -433,7 +444,7 @@ impl Box { /// Constructs a new `Box` instance from a specific info. pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificBoxInfo) -> Box { Box { - node: OpaqueNode::from_thread_safe_layout_node(node), + node: OpaqueNodeMethods::from_thread_safe_layout_node(node), style: node.style().clone(), border_box: RefCell::new(Au::zero_rect()), border: RefCell::new(Zero::zero()), @@ -460,7 +471,7 @@ impl Box { let (node_style, _) = cascade(&[], false, Some(node.style().get()), &initial_values(), None); Box { - node: OpaqueNode::from_thread_safe_layout_node(node), + node: OpaqueNodeMethods::from_thread_safe_layout_node(node), style: Arc::new(node_style), border_box: RefCell::new(Au::zero_rect()), border: RefCell::new(Zero::zero()), @@ -589,54 +600,55 @@ impl Box { } } - /// Returns the shared part of the width for computation of minimum and preferred width per - /// CSS 2.1. - fn guess_width(&self) -> Au { - let style = self.style(); - let mut margin_left = Au::new(0); - let mut margin_right = Au::new(0); - let mut padding_left = Au::new(0); - let mut padding_right = Au::new(0); - - match self.specific { - GenericBox | IframeBox(_) | ImageBox(_) => { - margin_left = MaybeAuto::from_style(style.Margin.get().margin_left, - Au::new(0)).specified_or_zero(); - margin_right = MaybeAuto::from_style(style.Margin.get().margin_right, - Au::new(0)).specified_or_zero(); - padding_left = self.compute_padding_length(style.Padding.get().padding_left, - Au::new(0)); - padding_right = self.compute_padding_length(style.Padding.get().padding_right, - Au::new(0)); - } - TableBox | TableCellBox => { - padding_left = self.compute_padding_length(style.Padding.get().padding_left, - Au::new(0)); - padding_right = self.compute_padding_length(style.Padding.get().padding_right, - Au::new(0)); + /// Uses the style only to estimate the intrinsic widths. These may be modified for text or + /// replaced elements. + fn style_specified_intrinsic_width(&self) -> IntrinsicWidths { + let (use_margins, use_padding) = match self.specific { + GenericBox | IframeBox(_) | ImageBox(_) => (true, true), + TableBox | TableCellBox => (false, true), + TableWrapperBox => (true, false), + TableRowBox => (false, false), + ScannedTextBox(_) | TableColumnBox(_) | UnscannedTextBox(_) => { + // Styles are irrelevant for these kinds of boxes. + return IntrinsicWidths::new() } - TableWrapperBox => { - margin_left = MaybeAuto::from_style(style.Margin.get().margin_left, - Au::new(0)).specified_or_zero(); - margin_right = MaybeAuto::from_style(style.Margin.get().margin_right, - Au::new(0)).specified_or_zero(); - } - TableRowBox => {} - ScannedTextBox(_) | TableColumnBox(_) | UnscannedTextBox(_) => return Au(0), - } + }; + let style = self.style(); let width = MaybeAuto::from_style(style.Box.get().width, Au::new(0)).specified_or_zero(); - width + margin_left + margin_right + padding_left + padding_right + - self.border.get().left + self.border.get().right + let (margin_left, margin_right) = if use_margins { + (MaybeAuto::from_style(style.Margin.get().margin_left, Au(0)).specified_or_zero(), + MaybeAuto::from_style(style.Margin.get().margin_right, Au(0)).specified_or_zero()) + } else { + (Au(0), Au(0)) + }; + + let (padding_left, padding_right) = if use_padding { + (self.compute_padding_length(style.Padding.get().padding_left, Au(0)), + self.compute_padding_length(style.Padding.get().padding_right, Au(0))) + } else { + (Au(0), Au(0)) + }; + + let surround_width = margin_left + margin_right + padding_left + padding_right + + self.border.get().left + self.border.get().right; + + IntrinsicWidths { + minimum_width: width, + preferred_width: width, + surround_width: surround_width, + } } pub fn calculate_line_height(&self, font_size: Au) -> Au { - match self.line_height() { + let from_inline = match self.style().InheritedBox.get().line_height { line_height::Normal => font_size.scale_by(1.14), line_height::Number(l) => font_size.scale_by(l), line_height::Length(l) => l - } + }; + let minimum = self.style().InheritedBox.get()._servo_minimum_line_height; + Au::max(from_inline, minimum) } /// Populates the box model border parameters from the given computed style. @@ -864,10 +876,6 @@ impl Box { self.style().InheritedText.get().text_align } - pub fn line_height(&self) -> line_height::T { - self.style().InheritedBox.get().line_height - } - pub fn vertical_align(&self) -> vertical_align::T { self.style().Box.get().vertical_align } @@ -920,15 +928,6 @@ impl Box { } } - /// Returns true if this element is replaced content. This is true for images, form elements, - /// and so on. - pub fn is_replaced(&self) -> bool { - match self.specific { - ImageBox(..) => true, - _ => false, - } - } - /// Returns true if this element can be split. This is true for text boxes. pub fn can_split(&self) -> bool { match self.specific { @@ -951,12 +950,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 +962,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 +970,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 +1001,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 +1025,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 +1036,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 +1052,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 +1142,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 +1165,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)) } - /// Adds the display items for this box to the given display list. + 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 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."), @@ -1204,7 +1314,7 @@ impl Box { let inline_info = self.inline_info.borrow(); match inline_info.get() { &Some(ref info) => { - for data in info.parent_info.rev_iter() { + for data in info.parent_info.as_slice().rev_iter() { let parent_info = FlowFlagsInfo::new(data.style.get()); flow_flags.propagate_text_decoration_from_parent(&parent_info); } @@ -1224,100 +1334,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 +1390,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 +1409,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)) } } @@ -1392,18 +1429,19 @@ impl Box { } _ => {} } - } - /// Returns the *minimum width* and *preferred width* of this box as defined by CSS 2.1. - pub fn minimum_and_preferred_widths(&self) -> (Au, Au) { - let guessed_width = self.guess_width(); - let (additional_minimum, additional_preferred) = match self.specific { - GenericBox | IframeBox(_) | TableBox | TableCellBox | TableColumnBox(_) | - TableRowBox | TableWrapperBox => (Au(0), Au(0)), + /// Returns the intrinsic widths of this fragment. + pub fn intrinsic_widths(&self) -> IntrinsicWidths { + let mut result = self.style_specified_intrinsic_width(); + + match self.specific { + GenericBox | IframeBox(_) | TableBox | TableCellBox | TableColumnBox(_) | TableRowBox | + TableWrapperBox => {} ImageBox(ref image_box_info) => { let image_width = image_box_info.image_width(); - (image_width, image_width) + result.minimum_width = geometry::max(result.minimum_width, image_width); + result.preferred_width = geometry::max(result.preferred_width, image_width); } ScannedTextBox(ref text_box_info) => { let range = &text_box_info.range; @@ -1415,11 +1453,29 @@ impl Box { max_line_width = Au::max(max_line_width, line_metrics.advance_width); } - (min_line_width, max_line_width) + result.minimum_width = geometry::max(result.minimum_width, min_line_width); + result.preferred_width = geometry::max(result.preferred_width, max_line_width); } UnscannedTextBox(..) => fail!("Unscanned text boxes should have been scanned by now!"), - }; - (guessed_width + additional_minimum, guessed_width + additional_preferred) + } + + // Take borders and padding for parent inline boxes into account. + let inline_info = self.inline_info.get(); + match inline_info { + None => {} + Some(ref inline_info) => { + for inline_parent_info in inline_info.parent_info.iter() { + let border_width = inline_parent_info.border.left + + inline_parent_info.border.right; + let padding_width = inline_parent_info.padding.left + + inline_parent_info.padding.right; + result.minimum_width = result.minimum_width + border_width + padding_width; + result.preferred_width = result.preferred_width + border_width + padding_width; + } + } + } + + result } @@ -1440,8 +1496,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 | @@ -1625,16 +1681,16 @@ impl Box { } } - /// Assigns replaced width for this box only if it is replaced content. - /// - /// This assigns only the width, not margin or anything else. - /// CSS 2.1 § 10.3.2. - pub fn assign_replaced_width_if_necessary(&self,container_width: Au) { + /// Assigns replaced width, padding, and margins for this box only if it is replaced content + /// per CSS 2.1 § 10.3.2. + pub fn assign_replaced_width_if_necessary(&self, container_width: Au) { match self.specific { GenericBox | IframeBox(_) | TableBox | TableCellBox | TableRowBox | TableWrapperBox => {} ImageBox(ref image_box_info) => { - // TODO(ksh8281): compute border,margin,padding + self.compute_padding(self.style(), container_width); + + // TODO(ksh8281): compute border,margin let width = ImageBoxInfo::style_length(self.style().Box.get().width, image_box_info.dom_width, container_width); diff --git a/src/components/main/layout/construct.rs b/src/components/main/layout/construct.rs index 8b25937c624..e24805f687a 100644 --- a/src/components/main/layout/construct.rs +++ b/src/components/main/layout/construct.rs @@ -22,14 +22,14 @@ use css::node_style::StyledNode; use layout::block::BlockFlow; -use layout::box_::{Box, GenericBox, IframeBox, IframeBoxInfo, ImageBox, ImageBoxInfo, TableBox}; -use layout::box_::{TableCellBox, TableColumnBox, TableColumnBoxInfo, TableRowBox, TableWrapperBox}; -use layout::box_::{InlineInfo, InlineParentInfo, SpecificBoxInfo, UnscannedTextBox}; -use layout::box_::{UnscannedTextBoxInfo}; +use layout::box_::{Box, GenericBox, IframeBox, IframeBoxInfo, ImageBox, ImageBoxInfo}; +use layout::box_::{InlineInfo, InlineParentInfo, SpecificBoxInfo}; +use layout::box_::{TableBox, TableCellBox, TableColumnBox, TableColumnBoxInfo, TableRowBox}; +use layout::box_::{TableWrapperBox, UnscannedTextBox, UnscannedTextBoxInfo}; use layout::context::LayoutContext; use layout::floats::FloatKind; use layout::flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils}; -use layout::flow::{Descendants, AbsDescendants, FixedDescendants}; +use layout::flow::{Descendants, AbsDescendants}; use layout::flow_list::{Rawlink}; use layout::inline::InlineFlow; use layout::table_wrapper::TableWrapperFlow; @@ -40,31 +40,34 @@ use layout::table_rowgroup::TableRowGroupFlow; use layout::table_row::TableRowFlow; use layout::table_cell::TableCellFlow; use layout::text::TextRunScanner; -use layout::util::{LayoutDataAccess, OpaqueNode}; +use layout::util::{LayoutDataAccess, OpaqueNodeMethods}; use layout::wrapper::{PostorderNodeMutTraversal, TLayoutNode, ThreadSafeLayoutNode}; +use layout::wrapper::{Before, BeforeBlock, After, AfterBlock, Normal}; +use extra::url::Url; +use gfx::display_list::OpaqueNode; use gfx::font_context::FontContext; use script::dom::bindings::codegen::InheritTypes::TextCast; use script::dom::bindings::js::JS; -use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId, HTMLObjectElementTypeId}; -use script::dom::element::{HTMLTableElementTypeId, HTMLTableSectionElementTypeId}; -use script::dom::element::{HTMLTableDataCellElementTypeId, HTMLTableHeaderCellElementTypeId}; -use script::dom::element::{HTMLTableColElementTypeId, HTMLTableRowElementTypeId}; +use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId}; +use script::dom::element::{HTMLObjectElementTypeId}; +use script::dom::element::{HTMLTableColElementTypeId, HTMLTableDataCellElementTypeId}; +use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId}; +use script::dom::element::{HTMLTableRowElementTypeId, HTMLTableSectionElementTypeId}; use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId}; use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, ProcessingInstructionNodeTypeId}; use script::dom::node::{TextNodeTypeId}; use script::dom::text::Text; -use style::computed_values::{display, position, float, white_space}; -use style::ComputedValues; +use servo_util::geometry::Au; use servo_util::namespace; -use servo_util::url::parse_url; -use servo_util::url::is_image_data; +use servo_util::smallvec::SmallVec; use servo_util::str::is_whitespace; - -use extra::url::Url; -use sync::Arc; +use servo_util::url::{is_image_data, parse_url}; use std::mem; use std::num::Zero; +use style::ComputedValues; +use style::computed_values::{display, position, float, white_space}; +use sync::Arc; /// The results of flow construction for a DOM node. pub enum ConstructionResult { @@ -75,7 +78,7 @@ pub enum ConstructionResult { /// This node contributed a flow at the proper position in the tree. /// Nothing more needs to be done for this node. It has bubbled up fixed /// and absolute descendant flows that have a CB above it. - FlowConstructionResult(~Flow, AbsDescendants, FixedDescendants), + FlowConstructionResult(~Flow, AbsDescendants), /// This node contributed some object or objects that will be needed to construct a proper flow /// later up the tree, but these objects have not yet found their home. @@ -86,7 +89,7 @@ impl ConstructionResult { fn destroy(&mut self) { match *self { NoConstructionResult => {} - FlowConstructionResult(ref mut flow, _, _) => flow.destroy(), + FlowConstructionResult(ref mut flow, _) => flow.destroy(), ConstructionItemConstructionResult(ref mut item) => item.destroy(), } } @@ -132,9 +135,6 @@ struct InlineBoxesConstructionResult { /// Any absolute descendants that we're bubbling up. abs_descendants: AbsDescendants, - - /// Any fixed descendants that we're bubbling up. - fixed_descendants: FixedDescendants, } /// Represents an {ib} split that has not yet found the containing block that it belongs to. This @@ -281,7 +281,8 @@ impl<'a> FlowConstructor<'a> { } /// Builds the `ImageBoxInfo` for the given image. This is out of line to guide inlining. - fn build_box_info_for_image(&mut self, node: &ThreadSafeLayoutNode, url: Option<Url>) -> SpecificBoxInfo { + fn build_box_info_for_image(&mut self, node: &ThreadSafeLayoutNode, url: Option<Url>) + -> SpecificBoxInfo { match url { None => GenericBox, Some(url) => { @@ -296,19 +297,25 @@ impl<'a> FlowConstructor<'a> { pub fn build_specific_box_info_for_node(&mut self, node: &ThreadSafeLayoutNode) -> SpecificBoxInfo { match node.type_id() { - ElementNodeTypeId(HTMLImageElementTypeId) => self.build_box_info_for_image(node, node.image_url()), - ElementNodeTypeId(HTMLIFrameElementTypeId) => IframeBox(IframeBoxInfo::new(node)), - ElementNodeTypeId(HTMLObjectElementTypeId) => { + Some(ElementNodeTypeId(HTMLImageElementTypeId)) => { + self.build_box_info_for_image(node, node.image_url()) + } + Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => { + IframeBox(IframeBoxInfo::new(node)) + } + Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => { let data = node.get_object_data(&self.layout_context.url); self.build_box_info_for_image(node, data) } - ElementNodeTypeId(HTMLTableElementTypeId) => TableWrapperBox, - ElementNodeTypeId(HTMLTableColElementTypeId) => TableColumnBox(TableColumnBoxInfo::new(node)), - ElementNodeTypeId(HTMLTableDataCellElementTypeId) | - ElementNodeTypeId(HTMLTableHeaderCellElementTypeId) => TableCellBox, - ElementNodeTypeId(HTMLTableRowElementTypeId) | - ElementNodeTypeId(HTMLTableSectionElementTypeId) => TableRowBox, - TextNodeTypeId => UnscannedTextBox(UnscannedTextBoxInfo::new(node)), + Some(ElementNodeTypeId(HTMLTableElementTypeId)) => TableWrapperBox, + Some(ElementNodeTypeId(HTMLTableColElementTypeId)) => { + TableColumnBox(TableColumnBoxInfo::new(node)) + } + Some(ElementNodeTypeId(HTMLTableDataCellElementTypeId)) | + Some(ElementNodeTypeId(HTMLTableHeaderCellElementTypeId)) => TableCellBox, + Some(ElementNodeTypeId(HTMLTableRowElementTypeId)) | + Some(ElementNodeTypeId(HTMLTableSectionElementTypeId)) => TableRowBox, + None | Some(TextNodeTypeId) => UnscannedTextBox(UnscannedTextBoxInfo::new(node)), _ => GenericBox, } } @@ -352,6 +359,115 @@ impl<'a> FlowConstructor<'a> { } } + fn build_block_flow_using_children_construction_result(&mut self, + flow: &mut ~Flow, + consecutive_siblings: &mut ~[~Flow], + node: &ThreadSafeLayoutNode, + kid: ThreadSafeLayoutNode, + opt_boxes_for_inline_flow: &mut Option<~[Box]>, + abs_descendants: &mut Descendants, + first_box: &mut bool) { + match kid.swap_out_construction_result() { + NoConstructionResult => {} + FlowConstructionResult(kid_flow, kid_abs_descendants) => { + // If kid_flow is TableCaptionFlow, kid_flow should be added under + // TableWrapperFlow. + if flow.is_table() && kid_flow.is_table_caption() { + kid.set_flow_construction_result(FlowConstructionResult( + kid_flow, + Descendants::new())) + } else if flow.need_anonymous_flow(kid_flow) { + consecutive_siblings.push(kid_flow) + } else { + // Strip ignorable whitespace from the start of this flow per CSS 2.1 § + // 9.2.1.1. + if flow.is_table_kind() || *first_box { + strip_ignorable_whitespace_from_start(opt_boxes_for_inline_flow); + *first_box = false + } + + // Flush any inline boxes that we were gathering up. This allows us to handle + // {ib} splits. + debug!("flushing {} inline box(es) to flow A", + opt_boxes_for_inline_flow.as_ref() + .map_or(0, |boxes| boxes.len())); + self.flush_inline_boxes_to_flow_or_list_if_necessary( + opt_boxes_for_inline_flow, + flow, + consecutive_siblings, + node); + if !consecutive_siblings.is_empty() { + let consecutive_siblings = mem::replace(consecutive_siblings, ~[]); + self.generate_anonymous_missing_child(consecutive_siblings, + flow, + node); + } + flow.add_new_child(kid_flow); + } + abs_descendants.push_descendants(kid_abs_descendants); + } + ConstructionItemConstructionResult(InlineBoxesConstructionItem( + InlineBoxesConstructionResult { + splits: opt_splits, + boxes: boxes, + abs_descendants: kid_abs_descendants, + })) => { + // Add any {ib} splits. + match opt_splits { + None => {} + Some(splits) => { + for split in splits.move_iter() { + // Pull apart the {ib} split object and push its predecessor boxes + // onto the list. + let InlineBlockSplit { + predecessor_boxes: predecessor_boxes, + flow: kid_flow + } = split; + opt_boxes_for_inline_flow.push_all_move(predecessor_boxes); + + // If this is the first box in flow, then strip ignorable + // whitespace per CSS 2.1 § 9.2.1.1. + if *first_box { + strip_ignorable_whitespace_from_start( + opt_boxes_for_inline_flow); + *first_box = false + } + + // Flush any inline boxes that we were gathering up. + debug!("flushing {} inline box(es) to flow A", + opt_boxes_for_inline_flow.as_ref() + .map_or(0, |boxes| boxes.len())); + self.flush_inline_boxes_to_flow_or_list_if_necessary( + opt_boxes_for_inline_flow, + flow, + consecutive_siblings, + node); + + // Push the flow generated by the {ib} split onto our list of + // flows. + if flow.need_anonymous_flow(kid_flow) { + consecutive_siblings.push(kid_flow) + } else { + flow.add_new_child(kid_flow) + } + } + } + } + + // Add the boxes to the list we're maintaining. + opt_boxes_for_inline_flow.push_all_move(boxes); + abs_descendants.push_descendants(kid_abs_descendants); + } + ConstructionItemConstructionResult(WhitespaceConstructionItem(..)) => { + // Nothing to do here. + } + ConstructionItemConstructionResult(TableColumnBoxConstructionItem(_)) => { + // TODO: Implement anonymous table objects for missing parents + // CSS 2.1 § 17.2.1, step 3-2 + } + } + } + /// Build block flow for current node using information from children nodes. /// /// Consume results from children and combine them, handling {ib} splits. @@ -367,109 +483,22 @@ impl<'a> FlowConstructor<'a> { let mut opt_boxes_for_inline_flow = None; let mut consecutive_siblings = ~[]; let mut first_box = true; + // List of absolute descendants, in tree order. let mut abs_descendants = Descendants::new(); - let mut fixed_descendants = Descendants::new(); for kid in node.children() { - match kid.swap_out_construction_result() { - NoConstructionResult => {} - FlowConstructionResult(kid_flow, kid_abs_descendants, kid_fixed_descendants) => { - // If kid_flow is TableCaptionFlow, kid_flow should be added under TableWrapperFlow. - if flow.is_table() && kid_flow.is_table_caption() { - kid.set_flow_construction_result(FlowConstructionResult(kid_flow, - Descendants::new(), - Descendants::new())) - } else if flow.need_anonymous_flow(kid_flow) { - consecutive_siblings.push(kid_flow) - } else { - // Strip ignorable whitespace from the start of this flow per CSS 2.1 § - // 9.2.1.1. - if flow.is_table_kind() || first_box { - strip_ignorable_whitespace_from_start(&mut opt_boxes_for_inline_flow); - first_box = false - } - - // Flush any inline boxes that we were gathering up. This allows us to handle - // {ib} splits. - debug!("flushing {} inline box(es) to flow A", - opt_boxes_for_inline_flow.as_ref() - .map_or(0, |boxes| boxes.len())); - self.flush_inline_boxes_to_flow_or_list_if_necessary(&mut opt_boxes_for_inline_flow, - &mut flow, - &mut consecutive_siblings, - node); - if !consecutive_siblings.is_empty() { - self.generate_anonymous_missing_child(consecutive_siblings, &mut flow, node); - consecutive_siblings = ~[]; - } - flow.add_new_child(kid_flow); - } - abs_descendants.push_descendants(kid_abs_descendants); - fixed_descendants.push_descendants(kid_fixed_descendants); - } - ConstructionItemConstructionResult(InlineBoxesConstructionItem( - InlineBoxesConstructionResult { - splits: opt_splits, - boxes: boxes, - abs_descendants: kid_abs_descendants, - fixed_descendants: kid_fixed_descendants, - })) => { - // Add any {ib} splits. - match opt_splits { - None => {} - Some(splits) => { - for split in splits.move_iter() { - // Pull apart the {ib} split object and push its predecessor boxes - // onto the list. - let InlineBlockSplit { - predecessor_boxes: predecessor_boxes, - flow: kid_flow - } = split; - opt_boxes_for_inline_flow.push_all_move(predecessor_boxes); - - // If this is the first box in flow, then strip ignorable - // whitespace per CSS 2.1 § 9.2.1.1. - if first_box { - strip_ignorable_whitespace_from_start( - &mut opt_boxes_for_inline_flow); - first_box = false - } - - // Flush any inline boxes that we were gathering up. - debug!("flushing {} inline box(es) to flow A", - opt_boxes_for_inline_flow.as_ref() - .map_or(0, - |boxes| boxes.len())); - self.flush_inline_boxes_to_flow_or_list_if_necessary( - &mut opt_boxes_for_inline_flow, - &mut flow, - &mut consecutive_siblings, - node); - - // Push the flow generated by the {ib} split onto our list of - // flows. - if flow.need_anonymous_flow(kid_flow) { - consecutive_siblings.push(kid_flow) - } else { - flow.add_new_child(kid_flow) - } - } - } - } - - // Add the boxes to the list we're maintaining. - opt_boxes_for_inline_flow.push_all_move(boxes); - abs_descendants.push_descendants(kid_abs_descendants); - fixed_descendants.push_descendants(kid_fixed_descendants); - } - ConstructionItemConstructionResult(WhitespaceConstructionItem(..)) => { - // Nothing to do here. - } - ConstructionItemConstructionResult(TableColumnBoxConstructionItem(_)) => { - // TODO: Implement anonymous table objects for missing parents - // CSS 2.1 § 17.2.1, step 3-2 - } + if kid.get_element_type() != Normal { + self.process(&kid); } + + self.build_block_flow_using_children_construction_result( + &mut flow, + &mut consecutive_siblings, + node, + kid, + &mut opt_boxes_for_inline_flow, + &mut abs_descendants, + &mut first_box); } // Perform a final flush of any inline boxes that we were gathering up to handle {ib} @@ -493,16 +522,13 @@ impl<'a> FlowConstructor<'a> { flow.set_abs_descendants(abs_descendants); abs_descendants = Descendants::new(); - if is_fixed_positioned { - // Send itself along with the other fixed descendants. - fixed_descendants.push(Rawlink::some(flow)); - } else if is_absolutely_positioned { + if is_fixed_positioned || is_absolutely_positioned { // This is now the only absolute flow in the subtree which hasn't yet // reached its CB. abs_descendants.push(Rawlink::some(flow)); } } - FlowConstructionResult(flow, abs_descendants, fixed_descendants) + FlowConstructionResult(flow, abs_descendants) } /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly @@ -521,7 +547,6 @@ impl<'a> FlowConstructor<'a> { self.build_flow_using_children(flow, node) } - /// Concatenates the boxes of kids, adding in our own borders/padding/margins if necessary. /// Returns the `InlineBoxesConstructionResult`, if any. There will be no /// `InlineBoxesConstructionResult` if this node consisted entirely of ignorable whitespace. @@ -530,13 +555,12 @@ impl<'a> FlowConstructor<'a> { let mut opt_inline_block_splits = None; let mut opt_box_accumulator = None; let mut abs_descendants = Descendants::new(); - let mut fixed_descendants = Descendants::new(); // Concatenate all the boxes of our kids, creating {ib} splits as necessary. for kid in node.children() { match kid.swap_out_construction_result() { NoConstructionResult => {} - FlowConstructionResult(flow, kid_abs_descendants, kid_fixed_descendants) => { + FlowConstructionResult(flow, kid_abs_descendants) => { // {ib} split. Flush the accumulator to our new split and make a new // accumulator to hold any subsequent boxes we come across. let split = InlineBlockSplit { @@ -545,14 +569,12 @@ impl<'a> FlowConstructor<'a> { }; opt_inline_block_splits.push(split); abs_descendants.push_descendants(kid_abs_descendants); - fixed_descendants.push_descendants(kid_fixed_descendants); } ConstructionItemConstructionResult(InlineBoxesConstructionItem( InlineBoxesConstructionResult { splits: opt_splits, boxes: boxes, abs_descendants: kid_abs_descendants, - fixed_descendants: kid_fixed_descendants, })) => { // Bubble up {ib} splits. @@ -579,7 +601,6 @@ impl<'a> FlowConstructor<'a> { // Push residual boxes. opt_box_accumulator.push_all_move(boxes); abs_descendants.push_descendants(kid_abs_descendants); - fixed_descendants.push_descendants(kid_fixed_descendants); } ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node, whitespace_style)) @@ -612,7 +633,7 @@ impl<'a> FlowConstructor<'a> { for box_ in boxes.iter() { total.push(box_); } - self.set_inline_info_for_inline_child(&total, node); + self.set_inline_info_for_inline_child(total, node); }, None => { @@ -622,7 +643,7 @@ impl<'a> FlowConstructor<'a> { total.push(box_); } } - self.set_inline_info_for_inline_child(&total, node); + self.set_inline_info_for_inline_child(total, node); } } }, @@ -633,7 +654,7 @@ impl<'a> FlowConstructor<'a> { for box_ in boxes.iter() { total.push(box_); } - self.set_inline_info_for_inline_child(&total, node); + self.set_inline_info_for_inline_child(total, node); }, None => {} } @@ -648,7 +669,6 @@ impl<'a> FlowConstructor<'a> { splits: opt_inline_block_splits, boxes: opt_box_accumulator.to_vec(), abs_descendants: abs_descendants, - fixed_descendants: fixed_descendants, }); ConstructionItemConstructionResult(construction_item) } else { @@ -656,8 +676,9 @@ impl<'a> FlowConstructor<'a> { } } + // FIXME(#1999, pcwalton): Why does this function create a box only to throw it away??? fn set_inline_info_for_inline_child(&mut self, - boxes: &~[&Box], + boxes: &[&Box], parent_node: &ThreadSafeLayoutNode) { let parent_box = Box::new(self, parent_node); let font_style = parent_box.font_style(); @@ -671,33 +692,39 @@ impl<'a> FlowConstructor<'a> { let boxes_len = boxes.len(); parent_box.compute_borders(parent_box.style()); + // FIXME(#2000, pcwalton): I suspect that `Au(0)` is not correct for the containing block + // width. + parent_box.compute_padding(parent_box.style(), Au(0)); + for (i, box_) in boxes.iter().enumerate() { if box_.inline_info.with( |data| data.is_none() ) { box_.inline_info.set(Some(InlineInfo::new())); } let mut border = parent_box.border.get(); + let mut padding = parent_box.padding.get(); if i != 0 { border.left = Zero::zero(); + padding.left = Zero::zero() } if i != (boxes_len - 1) { border.right = Zero::zero(); + padding.right = Zero::zero() } let mut info = box_.inline_info.borrow_mut(); match info.get() { &Some(ref mut info) => { - // TODO(ksh8281) compute margin,padding - info.parent_info.push( - InlineParentInfo { - padding: Zero::zero(), - border: border, - margin: Zero::zero(), - style: parent_box.style.clone(), - font_ascent: font_ascent, - font_descent: font_descent, - node: OpaqueNode::from_thread_safe_layout_node(parent_node), - }); + // TODO(ksh8281): Compute margins. + info.parent_info.push(InlineParentInfo { + padding: padding, + border: border, + margin: Zero::zero(), + style: parent_box.style.clone(), + font_ascent: font_ascent, + font_descent: font_descent, + node: OpaqueNodeMethods::from_thread_safe_layout_node(parent_node), + }) }, &None => {} } @@ -712,20 +739,22 @@ impl<'a> FlowConstructor<'a> { } // If this node is ignorable whitespace, bail out now. + // + // FIXME(#2001, pcwalton): Don't do this if there's padding or borders. if node.is_ignorable_whitespace() { - let opaque_node = OpaqueNode::from_thread_safe_layout_node(node); + let opaque_node = OpaqueNodeMethods::from_thread_safe_layout_node(node); return ConstructionItemConstructionResult(WhitespaceConstructionItem( opaque_node, node.style().clone())) } + let mut opt_box_accumulator = None; + opt_box_accumulator.push(Box::new(self, node)); + let construction_item = InlineBoxesConstructionItem(InlineBoxesConstructionResult { splits: None, - boxes: ~[ - Box::new(self, node) - ], + boxes: opt_box_accumulator.to_vec(), abs_descendants: Descendants::new(), - fixed_descendants: Descendants::new(), }); ConstructionItemConstructionResult(construction_item) } @@ -750,7 +779,7 @@ impl<'a> FlowConstructor<'a> { for kid in node.children() { match kid.swap_out_construction_result() { NoConstructionResult | ConstructionItemConstructionResult(_) => {} - FlowConstructionResult(kid_flow, _, _) => { + FlowConstructionResult(kid_flow, _) => { // Only kid flows with table-caption are matched here. assert!(kid_flow.is_table_caption()); table_wrapper_flow.add_new_child(kid_flow); @@ -761,8 +790,10 @@ impl<'a> FlowConstructor<'a> { /// Generates an anonymous table flow according to CSS 2.1 § 17.2.1, step 2. /// If necessary, generate recursively another anonymous table flow. - fn generate_anonymous_missing_child(&mut self, child_flows: ~[~Flow], - flow: &mut ~Flow, node: &ThreadSafeLayoutNode) { + fn generate_anonymous_missing_child(&mut self, + child_flows: ~[~Flow], + flow: &mut ~Flow, + node: &ThreadSafeLayoutNode) { let mut anonymous_flow = flow.generate_missing_child_flow(node); let mut consecutive_siblings = ~[]; for kid_flow in child_flows.move_iter() { @@ -805,10 +836,9 @@ impl<'a> FlowConstructor<'a> { // NOTE: The order of captions and table are not the same order as in the DOM tree. // All caption blocks are placed before the table flow match construction_result { - FlowConstructionResult(table_flow, table_abs_descendants, table_fixed_descendants) => { + FlowConstructionResult(table_flow, table_abs_descendants) => { wrapper_flow.add_new_child(table_flow); abs_descendants.push_descendants(table_abs_descendants); - fixed_descendants.push_descendants(table_fixed_descendants); } _ => {} } @@ -832,7 +862,7 @@ impl<'a> FlowConstructor<'a> { abs_descendants.push(Rawlink::some(wrapper_flow)); } } - FlowConstructionResult(wrapper_flow, abs_descendants, fixed_descendants) + FlowConstructionResult(wrapper_flow, abs_descendants) } /// Builds a flow for a node with `display: table-caption`. This yields a `TableCaptionFlow` @@ -904,7 +934,7 @@ impl<'a> FlowConstructor<'a> { let mut flow = ~TableColGroupFlow::from_node_and_boxes(node, box_, col_boxes) as ~Flow; flow.finish(self.layout_context); - FlowConstructionResult(flow, Descendants::new(), Descendants::new()) + FlowConstructionResult(flow, Descendants::new()) } } @@ -922,16 +952,23 @@ impl<'a> PostorderNodeMutTraversal for FlowConstructor<'a> { fn process(&mut self, node: &ThreadSafeLayoutNode) -> bool { // Get the `display` property for this node, and determine whether this node is floated. let (display, float, positioning) = match node.type_id() { - ElementNodeTypeId(_) => { + None => { + // Pseudo-element. + let style = node.style().get(); + (display::inline, style.Box.get().float, style.Box.get().position) + } + Some(ElementNodeTypeId(_)) => { let style = node.style().get(); (style.Box.get().display, style.Box.get().float, style.Box.get().position) } - TextNodeTypeId => (display::inline, float::none, position::static_), - CommentNodeTypeId | - DoctypeNodeTypeId | - DocumentFragmentNodeTypeId | - DocumentNodeTypeId | - ProcessingInstructionNodeTypeId => (display::none, float::none, position::static_), + Some(TextNodeTypeId) => (display::inline, float::none, position::static_), + Some(CommentNodeTypeId) | + Some(DoctypeNodeTypeId) | + Some(DocumentFragmentNodeTypeId) | + Some(DocumentNodeTypeId) | + Some(ProcessingInstructionNodeTypeId) => { + (display::none, float::none, position::static_) + } }; debug!("building flow for node: {:?} {:?}", display, float); @@ -1044,21 +1081,22 @@ trait NodeUtils { impl<'ln> NodeUtils for ThreadSafeLayoutNode<'ln> { fn is_replaced_content(&self) -> bool { match self.type_id() { - TextNodeTypeId | - ProcessingInstructionNodeTypeId | - CommentNodeTypeId | - DoctypeNodeTypeId | - DocumentFragmentNodeTypeId | - DocumentNodeTypeId | - ElementNodeTypeId(HTMLImageElementTypeId) => true, - ElementNodeTypeId(HTMLObjectElementTypeId) => self.has_object_data(), - ElementNodeTypeId(_) => false, + Some(TextNodeTypeId) | + Some(ProcessingInstructionNodeTypeId) | + Some(CommentNodeTypeId) | + Some(DoctypeNodeTypeId) | + Some(DocumentFragmentNodeTypeId) | + Some(DocumentNodeTypeId) | + None | + Some(ElementNodeTypeId(HTMLImageElementTypeId)) => true, + Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => self.has_object_data(), + Some(ElementNodeTypeId(_)) => false, } } fn is_ignorable_whitespace(&self) -> bool { match self.type_id() { - TextNodeTypeId => { + Some(TextNodeTypeId) => { unsafe { let text: JS<Text> = TextCast::to(self.get_jsmanaged()).unwrap(); if !is_whitespace(text.get().characterdata.data) { @@ -1085,7 +1123,17 @@ impl<'ln> NodeUtils for ThreadSafeLayoutNode<'ln> { fn set_flow_construction_result(&self, result: ConstructionResult) { let mut layout_data_ref = self.mutate_layout_data(); match *layout_data_ref.get() { - Some(ref mut layout_data) => layout_data.data.flow_construction_result = result, + Some(ref mut layout_data) =>{ + match self.get_element_type() { + Before | BeforeBlock => { + layout_data.data.before_flow_construction_result = result + }, + After | AfterBlock => { + layout_data.data.after_flow_construction_result = result + }, + Normal => layout_data.data.flow_construction_result = result, + } + }, None => fail!("no layout data"), } } @@ -1095,7 +1143,20 @@ impl<'ln> NodeUtils for ThreadSafeLayoutNode<'ln> { let mut layout_data_ref = self.mutate_layout_data(); match *layout_data_ref.get() { Some(ref mut layout_data) => { - mem::replace(&mut layout_data.data.flow_construction_result, NoConstructionResult) + match self.get_element_type() { + Before | BeforeBlock => { + mem::replace(&mut layout_data.data.before_flow_construction_result, + NoConstructionResult) + } + After | AfterBlock => { + mem::replace(&mut layout_data.data.after_flow_construction_result, + NoConstructionResult) + } + Normal => { + mem::replace(&mut layout_data.data.flow_construction_result, + NoConstructionResult) + } + } } None => fail!("no layout data"), } 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..3bec56473c3 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 for relatively-positioned descendants. + relative_containing_block_size: Size2D<Au>, + /// The position and size 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..d9bcb967670 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -26,14 +26,16 @@ /// similar methods. use css::node_style::StyledNode; -use layout::block::{BlockFlow}; +use layout::block::BlockFlow; use layout::box_::{Box, TableRowBox, TableCellBox}; -use layout::context::LayoutContext; use layout::construct::OptVector; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::context::LayoutContext; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo, ToGfxColor}; use layout::floats::Floats; +use layout::flow_list::{FlowList, Link, Rawlink, FlowListIterator, MutFlowListIterator}; use layout::incremental::RestyleDamage; use layout::inline::InlineFlow; +use layout::model::{CollapsibleMargins, IntrinsicWidths, MarginCollapseInfo}; use layout::parallel::FlowParallelInfo; use layout::parallel; use layout::table_wrapper::TableWrapperFlow; @@ -44,24 +46,22 @@ use layout::table_row::TableRowFlow; use layout::table_caption::TableCaptionFlow; use layout::table_cell::TableCellFlow; use layout::wrapper::ThreadSafeLayoutNode; -use layout::flow_list::{FlowList, Link, Rawlink, FlowListIterator, MutFlowListIterator}; use collections::Deque; -use geom::point::Point2D; use geom::Size2D; +use geom::point::Point2D; use geom::rect::Rect; -use gfx::display_list::{ClipDisplayItemClass, DisplayListCollection, DisplayList}; -use layout::display_list_builder::ToGfxColor; use gfx::color::Color; -use servo_util::smallvec::{SmallVec, SmallVec0}; +use gfx::display_list::StackingContext; +use servo_msg::compositor_msg::LayerId; use servo_util::geometry::Au; +use servo_util::smallvec::{SmallVec, SmallVec0}; use std::cast; -use std::cell::RefCell; +use std::iter::Zip; use std::sync::atomics::Relaxed; use std::vec::MutItems; -use std::iter::Zip; use style::ComputedValues; -use style::computed_values::{text_align, position}; +use style::computed_values::{clear, position, text_align}; /// Virtual methods that make up a float context. /// @@ -126,6 +126,24 @@ pub trait Flow { fail!("called as_table_cell() on a non-tablecell flow") } + /// If this is a table row or table rowgroup or table flow, returns column widths. + /// Fails otherwise. + fn col_widths<'a>(&'a mut self) -> &'a mut ~[Au] { + fail!("called col_widths() on an other flow than table-row/table-rowgroup/table") + } + + /// If this is a table row flow or table rowgroup flow or table flow, returns column min widths. + /// Fails otherwise. + fn col_min_widths<'a>(&'a self) -> &'a ~[Au] { + fail!("called col_min_widths() on an other flow than table-row/table-rowgroup/table") + } + + /// If this is a table row flow or table rowgroup flow or table flow, returns column min widths. + /// Fails otherwise. + fn col_pref_widths<'a>(&'a self) -> &'a ~[Au] { + fail!("called col_pref_widths() on an other flow than table-row/table-rowgroup/table") + } + // Main methods /// Pass 1 of reflow: computes minimum and preferred widths. @@ -148,15 +166,10 @@ pub trait Flow { fail!("assign_height_inorder not yet implemented") } - /// Collapses margins with the parent flow. This runs as part of assign-heights. - fn collapse_margins(&mut self, - _top_margin_collapsible: bool, - _first_in_flow: &mut bool, - _margin_top: &mut Au, - _top_offset: &mut Au, - _collapsing: &mut Au, - _collapsible: &mut Au) { - fail!("collapse_margins not yet implemented") + fn compute_collapsible_top_margin(&mut self, + _layout_context: &mut LayoutContext, + _margin_collapse_info: &mut MarginCollapseInfo) { + // The default implementation is a no-op. } /// Marks this flow as the root flow. The default implementation is a no-op. @@ -209,6 +222,11 @@ pub trait Flow { false } + /// Returns true if this is an absolute containing block. + fn is_absolute_containing_block(&self) -> bool { + false + } + /// Return the dimensions of the CB generated _by_ this flow for absolute descendants. fn generated_cb_size(&self) -> Size2D<Au> { fail!("generated_cb_size not yet implemented") @@ -219,6 +237,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 +362,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); @@ -369,11 +390,6 @@ pub trait MutableOwnedFlowUtils { /// Set this flow as the Containing Block for all the absolute descendants. fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants); - /// Set fixed descendants for this flow. - /// - /// Set yourself as the Containing Block for all the fixed descendants. - fn set_fixed_descendants(&mut self, fixed_descendants: AbsDescendants); - /// Destroys the flow. fn destroy(&mut self); } @@ -606,6 +622,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] @@ -690,7 +717,6 @@ impl Descendants { } pub type AbsDescendants = Descendants; -pub type FixedDescendants = Descendants; type DescendantIter<'a> = MutItems<'a, Rawlink>; @@ -708,12 +734,16 @@ pub struct BaseFlow { /* layout computations */ // TODO: min/pref and position are used during disjoint phases of // layout; maybe combine into a single enum to save space. - min_width: Au, - pref_width: Au, + intrinsic_widths: IntrinsicWidths, - /// The upper left corner of the box representing this flow, relative to - /// the box representing its parent flow. - /// For absolute flows, this represents the position wrt to its Containing Block. + /// The upper left corner of the box representing this flow, relative to the box representing + /// its parent flow. + /// + /// For absolute flows, this represents the position with respect to its *containing block*. + /// + /// This does not include margins in the block flow direction, because those can collapse. So + /// for the block direction (usually vertical), this represents the *border box*. For the + /// inline direction (usually horizontal), this represents the *margin box*. position: Rect<Au>, /// The amount of overflow of this flow, relative to the containing block. Must include all the @@ -728,6 +758,9 @@ pub struct BaseFlow { /// The floats next to this flow. floats: Floats, + /// The value of this flow's `clear` property, if any. + clear: clear::T, + /// For normal flows, this is the number of floated descendants that are /// not contained within any other floated descendant of this flow. For /// floats, it is 1. @@ -735,15 +768,15 @@ pub struct BaseFlow { /// decide whether to do an in-order traversal for assign_height. num_floats: uint, + /// The collapsible margins for this flow, if any. + collapsible_margins: CollapsibleMargins, + /// The position of this flow in page coordinates, computed during display list construction. abs_position: Point2D<Au>, - /// Details about descendants with position 'absolute' for which we are - /// the CB. This is in tree order. This includes any direct children. + /// Details about descendants with position 'absolute' or 'fixed' for which we are the + /// containing block. This is in tree order. This includes any direct children. abs_descendants: AbsDescendants, - /// Details about descendants with position 'fixed'. - /// TODO: Optimize this, because this will be set only for the root. - fixed_descendants: FixedDescendants, /// Offset wrt the nearest positioned ancestor - aka the Containing Block /// for any absolutely positioned elements. @@ -800,8 +833,7 @@ impl BaseFlow { next_sibling: None, prev_sibling: Rawlink::none(), - min_width: Au::new(0), - pref_width: Au::new(0), + intrinsic_widths: IntrinsicWidths::new(), position: Au::zero_rect(), overflow: Au::zero_rect(), @@ -809,9 +841,10 @@ impl BaseFlow { floats: Floats::new(), num_floats: 0, + collapsible_margins: CollapsibleMargins::new(), + clear: clear::none, abs_position: Point2D(Au::new(0), Au::new(0)), abs_descendants: Descendants::new(), - fixed_descendants: Descendants::new(), absolute_static_x_offset: Au::new(0), fixed_static_x_offset: Au::new(0), absolute_cb: Rawlink::none(), @@ -1068,6 +1101,7 @@ impl<'a> MutableFlowUtils for &'a mut Flow { overflow = overflow.union(&kid_overflow) } + // FIXME(#2004, pcwalton): This is wrong for `position: fixed`. for descendant_link in mut_base(self).abs_descendants.iter() { match descendant_link.resolve() { Some(flow) => { @@ -1078,183 +1112,62 @@ impl<'a> MutableFlowUtils for &'a mut Flow { None => fail!("empty Rawlink to a descendant") } } - - if self.is_root() { - for fixed_descendant_link in mut_base(self).fixed_descendants.iter() { - match fixed_descendant_link.resolve() { - Some(flow) => { - let mut kid_overflow = base(flow).overflow; - kid_overflow = kid_overflow.translate(&my_position.origin); - overflow = overflow.union(&kid_overflow) - } - None => fail!("empty Rawlink to a descendant") - } - } - } } mut_base(self).overflow = overflow; } - /// Push display items for current flow and its children onto `list`. + /// Push display items for current flow and its descendants onto the appropriate display lists + /// of the given stacking context. + /// + /// Arguments: /// - /// For InlineFlow, add display items for all its boxes onto list`. - /// For BlockFlow, add a ClipDisplayItemClass for itself and its children, - /// plus any other display items like border. + /// * `stacking_context`: The parent stacking context that this flow belongs to and to which + /// display items will be added. /// - /// `container_block_size`: Size of the Containing Block for the current - /// flow. This is used for relative positioning (which resolves percentage - /// values for 'top', etc. after all Containing Block heights have been computed.) - /// `absolute_cb_abs_position`: Absolute position of the Containing Block - /// for the flow if it is absolutely positioned. - fn build_display_lists<E:ExtraDisplayListData>( - self, - builder: &DisplayListBuilder, - container_block_size: &Size2D<Au>, - absolute_cb_abs_position: Point2D<Au>, - dirty: &Rect<Au>, - mut index: uint, - lists: &RefCell<DisplayListCollection<E>>) - -> bool { + /// * `builder`: The display list builder, which contains information used during the entire + /// display list building pass. + /// + /// * `info`: Per-flow display list building information. + fn build_display_list(self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { debug!("Flow: building display list"); - index = match self.class() { - BlockFlowClass => self.as_block().build_display_list_block(builder, - container_block_size, - absolute_cb_abs_position, - dirty, - index, - lists), - InlineFlowClass => self.as_inline().build_display_list_inline(builder, container_block_size, dirty, index, lists), - TableWrapperFlowClass => self.as_table_wrapper().build_display_list_table_wrapper(builder, - container_block_size, - absolute_cb_abs_position, - dirty, - index, - lists), - TableFlowClass => self.as_table().build_display_list_table(builder, - container_block_size, - absolute_cb_abs_position, - dirty, - index, - lists), - TableRowGroupFlowClass => self.as_table_rowgroup().build_display_list_table_rowgroup(builder, - container_block_size, - absolute_cb_abs_position, - dirty, - index, - lists), - TableRowFlowClass => self.as_table_row().build_display_list_table_row(builder, - container_block_size, - absolute_cb_abs_position, - dirty, - index, - lists), - TableCaptionFlowClass => self.as_table_caption().build_display_list_table_caption(builder, - container_block_size, - absolute_cb_abs_position, - dirty, - index, - lists), - TableCellFlowClass => self.as_table_cell().build_display_list_table_cell(builder, - container_block_size, - absolute_cb_abs_position, - dirty, - index, - lists), - TableColGroupFlowClass => index, - }; - - if lists.with_mut(|lists| lists.lists[index].list.len() == 0) { - return true; - } - - if self.is_block_container() || self.is_table_kind() { - let block = self.as_block(); - let mut child_lists = DisplayListCollection::new(); - child_lists.add_list(DisplayList::new()); - let child_lists = RefCell::new(child_lists); - let container_block_size; - let abs_cb_position; - // TODO(pradeep): Move this into a generated CB function and stuff in Flow. - match block.box_ { - Some(ref box_) => { - // The Containing Block formed by a Block for relatively - // positioned descendants is the content box. - container_block_size = box_.content_box_size(); - - abs_cb_position = if block.is_positioned() { - block.base.abs_position + block.generated_cb_position() - } else { - absolute_cb_abs_position - }; - } - None => fail!("Flow: block container should have a box_") + match self.class() { + BlockFlowClass => { + self.as_block().build_display_list_block(stacking_context, builder, info) } - - for kid in block.base.child_iter() { - if kid.is_absolutely_positioned() { - // All absolute flows will be handled by their CB. - continue; - } - kid.build_display_lists(builder, &container_block_size, - abs_cb_position, - dirty, 0u, &child_lists); + InlineFlowClass => { + self.as_inline().build_display_list_inline(stacking_context, builder, info) } - - // TODO: Maybe we should handle position 'absolute' and 'fixed' - // descendants before normal descendants just in case there is a - // problem when display-list building is parallel and both the - // original parent and this flow access the same absolute flow. - // Note that this can only be done once we have paint order - // working cos currently the later boxes paint over the absolute - // and fixed boxes :| - for abs_descendant_link in block.base.abs_descendants.iter() { - match abs_descendant_link.resolve() { - Some(flow) => { - // TODO(pradeep): Send in your abs_position directly. - flow.build_display_lists(builder, &container_block_size, - abs_cb_position, - dirty, 0u, &child_lists); - } - None => fail!("empty Rawlink to a descendant") - } + TableWrapperFlowClass => { + self.as_table_wrapper().build_display_list_table_wrapper(stacking_context, + builder, + info) } - - if block.is_root() { - for fixed_descendant_link in block.base.fixed_descendants.iter() { - match fixed_descendant_link.resolve() { - Some(flow) => { - flow.build_display_lists(builder, &container_block_size, - abs_cb_position, - dirty, 0u, &child_lists); - } - None => fail!("empty Rawlink to a descendant") - } - } + TableFlowClass => { + self.as_table().build_display_list_table(stacking_context, builder, info) + } + TableRowGroupFlowClass => { + self.as_table_rowgroup().build_display_list_table_rowgroup(stacking_context, + builder, + info) + } + TableRowFlowClass => { + self.as_table_row().build_display_list_table_row(stacking_context, builder, info) + } + TableCaptionFlowClass => { + self.as_table_caption().build_display_list_table_caption(stacking_context, + builder, + info) + } + TableCellFlowClass => { + self.as_table_cell().build_display_list_table_cell(stacking_context, builder, info) + } + TableColGroupFlowClass => { + // Nothing to do here, as column groups don't render. } - - let mut child_lists = Some(child_lists.unwrap()); - // Find parent ClipDisplayItemClass and push all child display items - // under it - lists.with_mut(|lists| { - let mut child_lists = child_lists.take_unwrap(); - let result = lists.lists[index].list.mut_rev_iter().position(|item| { - match *item { - ClipDisplayItemClass(ref mut item) => { - item.child_list.push_all_move(child_lists.lists.shift().unwrap().list); - true - }, - _ => false, - } - }); - - if result.is_none() { - fail!("fail to find parent item"); - } - - lists.lists.push_all_move(child_lists.lists); - }); } - true } /// Destroys the flow. @@ -1314,29 +1227,6 @@ impl MutableOwnedFlowUtils for ~Flow { } } - /// Set fixed descendants for this flow. - /// - /// Set yourself as the Containing Block for all the fixed descendants. - /// - /// Assumption: This is called in a bottom-up traversal, so that nothing - /// else is accessing the descendant flows. - /// Assumption: This is the root flow. - fn set_fixed_descendants(&mut self, fixed_descendants: FixedDescendants) { - let self_link = Rawlink::some(*self); - let block = self.as_block(); - block.base.fixed_descendants = fixed_descendants; - - for descendant_link in block.base.fixed_descendants.iter() { - match descendant_link.resolve() { - Some(flow) => { - let base = mut_base(flow); - base.absolute_cb = self_link.clone(); - } - None => fail!("empty Rawlink to a descendant") - } - } - } - /// Destroys the flow. fn destroy(&mut self) { let self_borrowed: &mut Flow = *self; diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index dc05d359ed7..4c3e10fc265 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -3,23 +3,24 @@ * 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; use std::u16; use style::computed_values::{text_align, vertical_align, white_space}; @@ -480,17 +481,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 +495,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 = box_.relative_position(&info.relative_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 @@ -628,20 +629,19 @@ impl Flow for InlineFlow { child_base.floats = Floats::new(); } - let mut min_width = Au::new(0); - let mut pref_width = Au::new(0); - + let mut intrinsic_widths = IntrinsicWidths::new(); for box_ in self.boxes.iter() { debug!("Flow: measuring {:s}", box_.debug_str()); box_.compute_borders(box_.style()); - let (this_minimum_width, this_preferred_width) = - box_.minimum_and_preferred_widths(); - min_width = Au::max(min_width, this_minimum_width); - pref_width = Au::max(pref_width, this_preferred_width); + + let box_intrinsic_widths = box_.intrinsic_widths(); + intrinsic_widths.minimum_width = geometry::max(intrinsic_widths.minimum_width, + box_intrinsic_widths.minimum_width); + intrinsic_widths.preferred_width = geometry::max(intrinsic_widths.preferred_width, + box_intrinsic_widths.preferred_width); } - self.base.min_width = min_width; - self.base.pref_width = pref_width; + self.base.intrinsic_widths = intrinsic_widths; self.base.num_floats = num_floats; } @@ -696,6 +696,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(); @@ -883,20 +884,6 @@ impl Flow for InlineFlow { self.base.floats.translate(Point2D(Au::new(0), -self.base.position.size.height)); } - fn collapse_margins(&mut self, - _: bool, - _: &mut bool, - _: &mut Au, - _: &mut Au, - collapsing: &mut Au, - collapsible: &mut Au) { - *collapsing = Au::new(0); - // Non-empty inline flows prevent collapsing between the previous margion and the next. - if self.base.position.size.height > Au::new(0) { - *collapsible = Au::new(0); - } - } - fn debug_str(&self) -> ~str { ~"InlineFlow: " + self.boxes.map(|s| s.debug_str()).connect(", ") } diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index b9050816668..267da50e62b 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(), } } @@ -432,15 +434,13 @@ impl LayoutTask { None => fail!("no layout data for root node"), }; let mut flow = match result { - FlowConstructionResult(mut flow, abs_descendants, fixed_descendants) => { + FlowConstructionResult(mut flow, abs_descendants) => { // Note: Assuming that the root has display 'static' (as per // CSS Section 9.3.1). Otherwise, if it were absolutely // positioned, it would return a reference to itself in // `abs_descendants` and would lead to a circular reference. // Set Root as CB for any remaining absolute descendants. flow.set_abs_descendants(abs_descendants); - // Set Root as CB for all fixed descendants. - flow.set_fixed_descendants(fixed_descendants); flow } _ => fail!("Flow construction didn't result in a flow at the root of the tree!"), @@ -627,26 +627,30 @@ 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 { + relative_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) { + if child.type_id() == Some(ElementNodeTypeId(HTMLHtmlElementTypeId)) || + child.type_id() == Some(ElementNodeTypeId(HTMLBodyElementTypeId)) { let element_bg_color = { let thread_safe_child = ThreadSafeLayoutNode::new(&child); thread_safe_child.style() @@ -668,18 +672,33 @@ impl LayoutTask { } } + let root_size = Size2D(display_list_building_info.relative_containing_block_size + .width + .to_nearest_px() as uint, + display_list_building_info.relative_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(), + position: Rect(Point2D(0u, 0u), root_size), + background_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 +720,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 +737,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 +800,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 +844,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/layout/model.rs b/src/components/main/layout/model.rs index 2a8b6af2f69..378c43d1bb6 100644 --- a/src/components/main/layout/model.rs +++ b/src/components/main/layout/model.rs @@ -4,8 +4,257 @@ //! Borders, padding, and margins. -use servo_util::geometry::Au; +use layout::box_::Box; + use computed = style::computed_values; +use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage}; +use servo_util::geometry::Au; +use servo_util::geometry; + +/// A collapsible margin. See CSS 2.1 § 8.3.1. +pub struct AdjoiningMargins { + /// The value of the greatest positive margin. + most_positive: Au, + + /// The actual value (not the absolute value) of the negative margin with the largest absolute + /// value. Since this is not the absolute value, this is always zero or negative. + most_negative: Au, +} + +impl AdjoiningMargins { + pub fn new() -> AdjoiningMargins { + AdjoiningMargins { + most_positive: Au(0), + most_negative: Au(0), + } + } + + pub fn from_margin(margin_value: Au) -> AdjoiningMargins { + if margin_value >= Au(0) { + AdjoiningMargins { + most_positive: margin_value, + most_negative: Au(0), + } + } else { + AdjoiningMargins { + most_positive: Au(0), + most_negative: margin_value, + } + } + } + + pub fn union(&mut self, other: AdjoiningMargins) { + self.most_positive = geometry::max(self.most_positive, other.most_positive); + self.most_negative = geometry::min(self.most_negative, other.most_negative) + } + + pub fn collapse(&self) -> Au { + self.most_positive + self.most_negative + } +} + +/// Represents the top and bottom margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1. +pub enum CollapsibleMargins { + /// Margins may not collapse with this flow. + NoCollapsibleMargins(Au, Au), + + /// Both the top and bottom margins (specified here in that order) may collapse, but the + /// margins do not collapse through this flow. + MarginsCollapse(AdjoiningMargins, AdjoiningMargins), + + /// Margins collapse *through* this flow. This means, essentially, that the flow doesn’t + /// have any border, padding, or out-of-flow (floating or positioned) content + MarginsCollapseThrough(AdjoiningMargins), +} + +impl CollapsibleMargins { + pub fn new() -> CollapsibleMargins { + NoCollapsibleMargins(Au(0), Au(0)) + } +} + +enum FinalMarginState { + MarginsCollapseThroughFinalMarginState, + BottomMarginCollapsesFinalMarginState, +} + +pub struct MarginCollapseInfo { + state: MarginCollapseState, + top_margin: AdjoiningMargins, + margin_in: AdjoiningMargins, +} + +impl MarginCollapseInfo { + /// TODO(#2012, pcwalton): Remove this method once `box_` is not an `Option`. + pub fn new() -> MarginCollapseInfo { + MarginCollapseInfo { + state: AccumulatingCollapsibleTopMargin, + top_margin: AdjoiningMargins::new(), + margin_in: AdjoiningMargins::new(), + } + } + + pub fn initialize_top_margin(&mut self, + fragment: &Box, + can_collapse_top_margin_with_kids: bool) { + if !can_collapse_top_margin_with_kids { + self.state = AccumulatingMarginIn + } + + self.top_margin = AdjoiningMargins::from_margin(fragment.margin.get().top) + } + + pub fn finish_and_compute_collapsible_margins(mut self, + fragment: &Box, + can_collapse_bottom_margin_with_kids: bool) + -> (CollapsibleMargins, Au) { + let state = match self.state { + AccumulatingCollapsibleTopMargin => { + match fragment.style().Box.get().height { + LPA_Auto | LPA_Length(Au(0)) | LPA_Percentage(0.) => { + MarginsCollapseThroughFinalMarginState + }, + _ => { + // If the box has an explicitly specified height, margins may not collapse + // through it. + BottomMarginCollapsesFinalMarginState + } + } + } + AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState, + }; + + // Different logic is needed here depending on whether this flow can collapse its bottom + // margin with its children. + let bottom_margin = fragment.margin.get().bottom; + if !can_collapse_bottom_margin_with_kids { + match state { + MarginsCollapseThroughFinalMarginState => { + let advance = self.top_margin.collapse(); + self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin)); + (MarginsCollapse(self.top_margin, self.margin_in), advance) + } + BottomMarginCollapsesFinalMarginState => { + let advance = self.margin_in.collapse(); + self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin)); + (MarginsCollapse(self.top_margin, self.margin_in), advance) + } + } + } else { + match state { + MarginsCollapseThroughFinalMarginState => { + self.top_margin.union(AdjoiningMargins::from_margin(bottom_margin)); + (MarginsCollapseThrough(self.top_margin), Au(0)) + } + BottomMarginCollapsesFinalMarginState => { + self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin)); + (MarginsCollapse(self.top_margin, self.margin_in), Au(0)) + } + } + } + } + + pub fn current_float_ceiling(&mut self) -> Au { + match self.state { + AccumulatingCollapsibleTopMargin => self.top_margin.collapse(), + AccumulatingMarginIn => self.margin_in.collapse(), + } + } + + /// Adds the child's potentially collapsible top margin to the current margin state and + /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount + /// that should be added to the Y offset during block layout. + pub fn advance_top_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au { + match (self.state, *child_collapsible_margins) { + (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(top, _)) => { + self.state = AccumulatingMarginIn; + top + } + (AccumulatingCollapsibleTopMargin, MarginsCollapse(top, _)) => { + self.top_margin.union(top); + self.state = AccumulatingMarginIn; + Au(0) + } + (AccumulatingMarginIn, NoCollapsibleMargins(top, _)) => { + let previous_margin_value = self.margin_in.collapse(); + self.margin_in = AdjoiningMargins::new(); + previous_margin_value + top + } + (AccumulatingMarginIn, MarginsCollapse(top, _)) => { + self.margin_in.union(top); + let margin_value = self.margin_in.collapse(); + self.margin_in = AdjoiningMargins::new(); + margin_value + } + (_, MarginsCollapseThrough(_)) => { + // For now, we ignore this; this will be handled by `advance_bottom_margin` below. + Au(0) + } + } + } + + /// Adds the child's potentially collapsible bottom margin to the current margin state and + /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount + /// that should be added to the Y offset during block layout. + pub fn advance_bottom_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au { + match (self.state, *child_collapsible_margins) { + (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(..)) | + (AccumulatingCollapsibleTopMargin, MarginsCollapse(..)) => { + // Can't happen because the state will have been replaced with + // `AccumulatingMarginIn` above. + fail!("should not be accumulating collapsible top margins anymore!") + } + (AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => { + self.top_margin.union(margin); + Au(0) + } + (AccumulatingMarginIn, NoCollapsibleMargins(_, bottom)) => { + assert_eq!(self.margin_in.most_positive, Au(0)); + assert_eq!(self.margin_in.most_negative, Au(0)); + bottom + } + (AccumulatingMarginIn, MarginsCollapse(_, bottom)) | + (AccumulatingMarginIn, MarginsCollapseThrough(bottom)) => { + self.margin_in.union(bottom); + Au(0) + } + } + } +} + +enum MarginCollapseState { + AccumulatingCollapsibleTopMargin, + AccumulatingMarginIn, +} + +/// Intrinsic widths, which consist of minimum and preferred. +pub struct IntrinsicWidths { + /// The *minimum width* of the content. + minimum_width: Au, + /// The *preferred width* of the content. + preferred_width: Au, + /// The estimated sum of borders, padding, and margins. Some calculations use this information + /// when computing intrinsic widths. + surround_width: Au, +} + +impl IntrinsicWidths { + pub fn new() -> IntrinsicWidths { + IntrinsicWidths { + minimum_width: Au(0), + preferred_width: Au(0), + surround_width: Au(0), + } + } + + pub fn total_minimum_width(&self) -> Au { + self.minimum_width + self.surround_width + } + + pub fn total_preferred_width(&self) -> Au { + self.preferred_width + self.surround_width + } +} /// Useful helper data type when computing values for blocks and positioned elements. pub enum MaybeAuto { diff --git a/src/components/main/layout/parallel.rs b/src/components/main/layout/parallel.rs index d81c14d7a06..3967609cf34 100644 --- a/src/components/main/layout/parallel.rs +++ b/src/components/main/layout/parallel.rs @@ -14,10 +14,11 @@ use layout::flow::{Flow, PreorderFlowTraversal, PostorderFlowTraversal}; use layout::flow; use layout::layout_task::{AssignHeightsAndStoreOverflowTraversal, AssignWidthsTraversal}; use layout::layout_task::{BubbleWidthsTraversal}; -use layout::util::{LayoutDataAccess, OpaqueNode}; +use layout::util::{LayoutDataAccess, OpaqueNodeMethods}; use layout::wrapper::{layout_node_to_unsafe_layout_node, LayoutNode, PostorderNodeMutTraversal}; use layout::wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode}; +use gfx::display_list::OpaqueNode; use servo_util::time::{ProfilerChan, profile}; use servo_util::time; use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy}; @@ -244,7 +245,8 @@ fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode, node.initialize_layout_data(layout_context.layout_chan.clone()); // Get the parent node. - let parent_opt = if OpaqueNode::from_layout_node(&node) == layout_context.reflow_root { + let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node); + let parent_opt = if opaque_node == layout_context.reflow_root { None } else { node.parent_node() @@ -345,7 +347,8 @@ fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode, } // If this is the reflow root, we're done. - if layout_context.reflow_root == OpaqueNode::from_layout_node(&node) { + let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node); + if layout_context.reflow_root == opaque_node { break } diff --git a/src/components/main/layout/table.rs b/src/components/main/layout/table.rs index 55ce4de5844..1a8b38975ba 100644 --- a/src/components/main/layout/table.rs +++ b/src/components/main/layout/table.rs @@ -5,22 +5,21 @@ //! CSS table formatting contexts. use layout::box_::Box; -use layout::block::BlockFlow; -use layout::block::{WidthAndMarginsComputer, WidthConstraintInput, WidthConstraintSolution}; +use layout::block::{BlockFlow, MarginsMayNotCollapse, WidthAndMarginsComputer}; +use layout::block::{WidthConstraintInput, WidthConstraintSolution}; use layout::construct::FlowConstructor; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo}; use layout::floats::{FloatKind}; use layout::flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use layout::flow; use layout::table_wrapper::{TableLayout, FixedLayout, AutoLayout}; use layout::wrapper::ThreadSafeLayoutNode; -use std::cell::RefCell; -use style::computed_values::table_layout; -use geom::{Point2D, Rect, Size2D}; -use gfx::display_list::DisplayListCollection; +use gfx::display_list::StackingContext; use servo_util::geometry::Au; +use servo_util::geometry; +use style::computed_values::table_layout; /// A table flow corresponded to the table's internal table box under a table wrapper flow. /// The properties `position`, `float`, and `margin-*` are used on the table wrapper box, @@ -31,6 +30,12 @@ pub struct TableFlow { /// Column widths col_widths: ~[Au], + /// Column min widths. + col_min_widths: ~[Au], + + /// Column pref widths. + col_pref_widths: ~[Au], + /// Table-layout property table_layout: TableLayout, } @@ -49,6 +54,8 @@ impl TableFlow { TableFlow { block_flow: block_flow, col_widths: ~[], + col_min_widths: ~[], + col_pref_widths: ~[], table_layout: table_layout } } @@ -66,6 +73,8 @@ impl TableFlow { TableFlow { block_flow: block_flow, col_widths: ~[], + col_min_widths: ~[], + col_pref_widths: ~[], table_layout: table_layout } } @@ -84,6 +93,8 @@ impl TableFlow { TableFlow { block_flow: block_flow, col_widths: ~[], + col_min_widths: ~[], + col_pref_widths: ~[], table_layout: table_layout } } @@ -91,61 +102,46 @@ impl TableFlow { pub fn teardown(&mut self) { self.block_flow.teardown(); self.col_widths = ~[]; + self.col_min_widths = ~[]; + self.col_pref_widths = ~[]; + } + + /// Update the corresponding value of self_widths if a value of kid_widths has larger value + /// than one of self_widths. + pub fn update_col_widths(self_widths: &mut ~[Au], kid_widths: &~[Au]) -> Au { + let mut sum_widths = Au(0); + let mut kid_widths_it = kid_widths.iter(); + for self_width in self_widths.mut_iter() { + match kid_widths_it.next() { + Some(kid_width) => { + if *self_width < *kid_width { + *self_width = *kid_width; + } + }, + None => {} + } + sum_widths = sum_widths + *self_width; + } + sum_widths } /// Assign height for table flow. /// + /// TODO(#2014, pcwalton): This probably doesn't handle margin collapse right. + /// /// inline(always) because this is only ever called by in-order or non-in-order top-level /// methods #[inline(always)] - fn assign_height_table_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { - - let (_, top_offset, bottom_offset, left_offset) = self.block_flow.initialize_offsets(true); - - self.block_flow.handle_children_floats_if_necessary(ctx, inorder, - left_offset, top_offset); - - let mut cur_y = top_offset; - for kid in self.block_flow.base.child_iter() { - let child_node = flow::mut_base(kid); - child_node.position.origin.y = cur_y; - cur_y = cur_y + child_node.position.size.height; - } - - let height = cur_y - top_offset; - - let mut noncontent_height = Au::new(0); - for box_ in self.block_flow.box_.iter() { - let mut position = box_.border_box.get(); - - // noncontent_height = border_top/bottom + padding_top/bottom of box - noncontent_height = box_.noncontent_height(); - - position.origin.y = Au(0); - position.size.height = height + noncontent_height; - - box_.border_box.set(position); - } - - self.block_flow.base.position.size.height = height + noncontent_height; - - self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, - top_offset, bottom_offset, left_offset); + fn assign_height_table_base(&mut self, layout_context: &mut LayoutContext, inorder: bool) { + self.block_flow.assign_height_block_base(layout_context, inorder, MarginsMayNotCollapse); } - pub fn build_display_list_table<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_table(&mut self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { debug!("build_display_list_table: same process as block flow"); - self.block_flow.build_display_list_block(builder, container_block_size, - absolute_cb_abs_position, - dirty, index, lists) + self.block_flow.build_display_list_block(stacking_context, builder, info); } } @@ -162,54 +158,95 @@ impl Flow for TableFlow { &mut self.block_flow } - /// This function finds the specified column widths from column group and the first row. - /// Those are used in fixed table layout calculation. - /* FIXME: automatic table layout calculation */ - fn bubble_widths(&mut self, ctx: &mut LayoutContext) { + fn col_widths<'a>(&'a mut self) -> &'a mut ~[Au] { + &mut self.col_widths + } + + fn col_min_widths<'a>(&'a self) -> &'a ~[Au] { + &self.col_min_widths + } + + fn col_pref_widths<'a>(&'a self) -> &'a ~[Au] { + &self.col_pref_widths + } + + /// The specified column widths are set from column group and the first row for the fixed + /// table layout calculation. + /// The maximum min/pref widths of each column are set from the rows for the automatic + /// table layout calculation. + fn bubble_widths(&mut self, _: &mut LayoutContext) { + let mut min_width = Au(0); + let mut pref_width = Au(0); let mut did_first_row = false; + let mut num_floats = 0; - /* find max width from child block contexts */ for kid in self.block_flow.base.child_iter() { assert!(kid.is_proper_table_child()); if kid.is_table_colgroup() { self.col_widths.push_all(kid.as_table_colgroup().widths); + self.col_min_widths = self.col_widths.clone(); + self.col_pref_widths = self.col_widths.clone(); } else if kid.is_table_rowgroup() || kid.is_table_row() { // read column widths from table-row-group/table-row, and assign // width=0 for the columns not defined in column-group // FIXME: need to read widths from either table-header-group OR // first table-row - let kid_col_widths = if kid.is_table_rowgroup() { - &kid.as_table_rowgroup().col_widths - } else { - &kid.as_table_row().col_widths - }; match self.table_layout { - FixedLayout if !did_first_row => { - did_first_row = true; - let mut child_widths = kid_col_widths.iter(); - for col_width in self.col_widths.mut_iter() { - match child_widths.next() { - Some(child_width) => { - if *col_width == Au::new(0) { - *col_width = *child_width; - } - }, - None => break + FixedLayout => { + let kid_col_widths = kid.col_widths(); + if !did_first_row { + did_first_row = true; + let mut child_widths = kid_col_widths.iter(); + for col_width in self.col_widths.mut_iter() { + match child_widths.next() { + Some(child_width) => { + if *col_width == Au::new(0) { + *col_width = *child_width; + } + }, + None => break + } } } + let num_child_cols = kid_col_widths.len(); + let num_cols = self.col_widths.len(); + debug!("table until the previous row has {} column(s) and this row has {} column(s)", + num_cols, num_child_cols); + for i in range(num_cols, num_child_cols) { + self.col_widths.push( kid_col_widths[i] ); + } }, - _ => {} - } - let num_child_cols = kid_col_widths.len(); - let num_cols = self.col_widths.len(); - debug!("colgroup has {} column(s) and child has {} column(s)", num_cols, num_child_cols); - for i in range(num_cols, num_child_cols) { - self.col_widths.push( kid_col_widths[i] ); + AutoLayout => { + min_width = TableFlow::update_col_widths(&mut self.col_min_widths, kid.col_min_widths()); + pref_width = TableFlow::update_col_widths(&mut self.col_pref_widths, kid.col_pref_widths()); + + // update the number of column widths from table-rows. + let num_cols = self.col_min_widths.len(); + let num_child_cols = kid.col_min_widths().len(); + debug!("table until the previous row has {} column(s) and this row has {} column(s)", + num_cols, num_child_cols); + for i in range(num_cols, num_child_cols) { + self.col_widths.push(Au::new(0)); + let new_kid_min = kid.col_min_widths()[i]; + self.col_min_widths.push( new_kid_min ); + let new_kid_pref = kid.col_pref_widths()[i]; + self.col_pref_widths.push( new_kid_pref ); + min_width = min_width + new_kid_min; + pref_width = pref_width + new_kid_pref; + } + } } } + let child_base = flow::mut_base(kid); + num_floats = num_floats + child_base.num_floats; + } + for box_ in self.block_flow.box_.iter() { + box_.compute_borders(box_.style()); } - self.block_flow.bubble_widths(ctx); + self.block_flow.base.num_floats = num_floats; + self.block_flow.base.intrinsic_widths.minimum_width = min_width; + self.block_flow.base.intrinsic_widths.preferred_width = geometry::max(min_width, pref_width); } /// Recursively (top-down) determines the actual width of child contexts and boxes. When called @@ -242,20 +279,25 @@ impl Flow for TableFlow { content_width = box_.border_box.get().size.width - padding_and_borders; } - // In fixed table layout, we distribute extra space among the unspecified columns if there are - // any, or among all the columns if all are specified. - if (total_column_width < content_width) && (num_unspecified_widths == 0) { - let ratio = content_width.to_f64().unwrap() / total_column_width.to_f64().unwrap(); - for col_width in self.col_widths.mut_iter() { - *col_width = (*col_width).scale_by(ratio); - } - } else if num_unspecified_widths != 0 { - let extra_column_width = (content_width - total_column_width) / Au::new(num_unspecified_widths); - for col_width in self.col_widths.mut_iter() { - if *col_width == Au(0) { - *col_width = extra_column_width; + match self.table_layout { + FixedLayout => { + // In fixed table layout, we distribute extra space among the unspecified columns if there are + // any, or among all the columns if all are specified. + if (total_column_width < content_width) && (num_unspecified_widths == 0) { + let ratio = content_width.to_f64().unwrap() / total_column_width.to_f64().unwrap(); + for col_width in self.col_widths.mut_iter() { + *col_width = (*col_width).scale_by(ratio); + } + } else if num_unspecified_widths != 0 { + let extra_column_width = (content_width - total_column_width) / Au::new(num_unspecified_widths); + for col_width in self.col_widths.mut_iter() { + if *col_width == Au(0) { + *col_width = extra_column_width; + } + } } } + _ => {} } self.block_flow.propagate_assigned_width_to_children(left_content_edge, content_width, Some(self.col_widths.clone())); @@ -275,20 +317,6 @@ impl Flow for TableFlow { self.assign_height_table_base(ctx, false); } - // CSS Section 8.3.1 - Collapsing Margins - // Since `margin` is not used on table box, `collapsing` and `collapsible` are set to 0 - fn collapse_margins(&mut self, - _: bool, - _: &mut bool, - _: &mut Au, - _: &mut Au, - collapsing: &mut Au, - collapsible: &mut Au) { - // `margin` is not used on table box. - *collapsing = Au::new(0); - *collapsible = Au::new(0); - } - fn debug_str(&self) -> ~str { let txt = ~"TableFlow: "; txt.append(match self.block_flow.box_ { diff --git a/src/components/main/layout/table_caption.rs b/src/components/main/layout/table_caption.rs index b9ca7bd211a..7aecbe22fb5 100644 --- a/src/components/main/layout/table_caption.rs +++ b/src/components/main/layout/table_caption.rs @@ -7,14 +7,11 @@ use layout::block::BlockFlow; use layout::construct::FlowConstructor; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo}; use layout::flow::{TableCaptionFlowClass, FlowClass, Flow}; use layout::wrapper::ThreadSafeLayoutNode; -use std::cell::RefCell; -use geom::{Point2D, Rect, Size2D}; -use gfx::display_list::DisplayListCollection; -use servo_util::geometry::Au; +use gfx::display_list::StackingContext; /// A table formatting context. pub struct TableCaptionFlow { @@ -34,19 +31,12 @@ impl TableCaptionFlow { self.block_flow.teardown(); } - pub fn build_display_list_table_caption<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_table_caption(&mut self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { debug!("build_display_list_table_caption: same process as block flow"); - self.block_flow.build_display_list_block(builder, container_block_size, - absolute_cb_abs_position, - dirty, index, lists) + self.block_flow.build_display_list_block(stacking_context, builder, info) } } @@ -86,13 +76,6 @@ impl Flow for TableCaptionFlow { self.block_flow.assign_height(ctx); } - /// table-caption has margins but is not collapsed with a sibling(table) - /// or its parents(table-wrapper). - /// Therefore, margins to be collapsed do not exist. - fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, - _: &mut Au, _: &mut Au, _: &mut Au) { - } - fn debug_str(&self) -> ~str { let txt = ~"TableCaptionFlow: "; txt.append(match self.block_flow.box_ { diff --git a/src/components/main/layout/table_cell.rs b/src/components/main/layout/table_cell.rs index 624f0d8deb4..d6e88b629fe 100644 --- a/src/components/main/layout/table_cell.rs +++ b/src/components/main/layout/table_cell.rs @@ -5,16 +5,15 @@ //! CSS table formatting contexts. use layout::box_::Box; -use layout::block::{BlockFlow, WidthAndMarginsComputer}; +use layout::block::{BlockFlow, MarginsMayNotCollapse, WidthAndMarginsComputer}; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo}; use layout::flow::{TableCellFlowClass, FlowClass, Flow}; +use layout::model::{MaybeAuto}; use layout::table::InternalTable; use layout::wrapper::ThreadSafeLayoutNode; -use std::cell::RefCell; -use geom::{Point2D, Rect, Size2D}; -use gfx::display_list::{DisplayListCollection}; +use gfx::display_list::StackingContext; use servo_util::geometry::Au; /// A table formatting context. @@ -42,62 +41,23 @@ impl TableCellFlow { /// Assign height for table-cell flow. /// + /// TODO(#2015, pcwalton): This doesn't handle floats right. + /// /// inline(always) because this is only ever called by in-order or non-in-order top-level /// methods #[inline(always)] - fn assign_height_table_cell_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { - let (_, mut top_offset, bottom_offset, left_offset) = self.block_flow - .initialize_offsets(true); - - self.block_flow.handle_children_floats_if_necessary(ctx, inorder, - left_offset, top_offset); - let mut cur_y = top_offset; - // Since table cell does not have `margin`, the first child's top margin and - // the last child's bottom margin do not collapse. - self.block_flow.compute_margin_collapse(&mut cur_y, - &mut top_offset, - &mut Au(0), - &mut Au(0), - false, - false); - - // CSS 2.1 § 17.5.3. Table cell box height is the minimum height required by the content. - let height = cur_y - top_offset; - - // TODO(june0cho): vertical-align of table-cell should be calculated. - let mut noncontent_height = Au::new(0); - for box_ in self.block_flow.box_.iter() { - let mut position = box_.border_box.get(); - - // noncontent_height = border_top/bottom + padding_top/bottom of box - noncontent_height = box_.noncontent_height(); - - position.origin.y = Au(0); - position.size.height = height + noncontent_height; - - box_.border_box.set(position); - } - - self.block_flow.base.position.size.height = height + noncontent_height; - - self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, top_offset, - bottom_offset, left_offset); - self.block_flow.assign_height_absolute_flows(ctx); + fn assign_height_table_cell_base(&mut self, + layout_context: &mut LayoutContext, + inorder: bool) { + self.block_flow.assign_height_block_base(layout_context, inorder, MarginsMayNotCollapse) } - pub fn build_display_list_table_cell<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 { - debug!("build_display_list_table_cell: same process as block flow"); - self.block_flow.build_display_list_block(builder, container_block_size, - absolute_cb_abs_position, - dirty, index, lists) + pub fn build_display_list_table_cell(&mut self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { + debug!("build_display_list_table: same process as block flow"); + self.block_flow.build_display_list_block(stacking_context, builder, info) } } @@ -117,6 +77,18 @@ impl Flow for TableCellFlow { /// Minimum/preferred widths set by this function are used in automatic table layout calculation. fn bubble_widths(&mut self, ctx: &mut LayoutContext) { self.block_flow.bubble_widths(ctx); + for box_ in self.block_flow.box_.iter() { + let specified_width = MaybeAuto::from_style(box_.style().Box.get().width, + Au::new(0)).specified_or_zero(); + if self.block_flow.base.intrinsic_widths.minimum_width < specified_width { + self.block_flow.base.intrinsic_widths.minimum_width = specified_width; + } + if self.block_flow.base.intrinsic_widths.preferred_width < + self.block_flow.base.intrinsic_widths.minimum_width { + self.block_flow.base.intrinsic_widths.preferred_width = + self.block_flow.base.intrinsic_widths.minimum_width; + } + } } /// Recursively (top-down) determines the actual width of child contexts and boxes. When called @@ -156,12 +128,6 @@ impl Flow for TableCellFlow { self.assign_height_table_cell_base(ctx, false); } - /// TableCellBox and their parents(TableRowBox) do not have margins. - /// Therefore, margins to be collapsed do not exist. - fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, - _: &mut Au, _: &mut Au, _: &mut Au) { - } - fn debug_str(&self) -> ~str { let txt = ~"TableCellFlow: "; txt.append(match self.block_flow.box_ { diff --git a/src/components/main/layout/table_colgroup.rs b/src/components/main/layout/table_colgroup.rs index 8d9e1d98870..a53c825660f 100644 --- a/src/components/main/layout/table_colgroup.rs +++ b/src/components/main/layout/table_colgroup.rs @@ -82,12 +82,6 @@ impl Flow for TableColGroupFlow { fn assign_height(&mut self, _ctx: &mut LayoutContext) { } - /// TableColumnBox and their parents(TableBox) do not have margins. - /// Therefore, margins to be collapsed do not exist. - fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, - _: &mut Au, _: &mut Au, _: &mut Au) { - } - fn debug_str(&self) -> ~str { let txt = ~"TableColGroupFlow: "; txt.append(match self.box_ { diff --git a/src/components/main/layout/table_row.rs b/src/components/main/layout/table_row.rs index 17ebaded351..8dbc40265d0 100644 --- a/src/components/main/layout/table_row.rs +++ b/src/components/main/layout/table_row.rs @@ -9,16 +9,14 @@ use layout::block::BlockFlow; use layout::block::WidthAndMarginsComputer; use layout::construct::FlowConstructor; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo}; use layout::flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use layout::flow; use layout::table::InternalTable; use layout::model::{MaybeAuto, Specified, Auto}; use layout::wrapper::ThreadSafeLayoutNode; -use std::cell::RefCell; -use geom::{Point2D, Rect, Size2D}; -use gfx::display_list::DisplayListCollection; +use gfx::display_list::StackingContext; use servo_util::geometry::Au; use servo_util::geometry; @@ -28,6 +26,12 @@ pub struct TableRowFlow { /// Column widths. col_widths: ~[Au], + + /// Column min widths. + col_min_widths: ~[Au], + + /// Column pref widths. + col_pref_widths: ~[Au], } impl TableRowFlow { @@ -37,6 +41,8 @@ impl TableRowFlow { TableRowFlow { block_flow: BlockFlow::from_node_and_box(node, box_), col_widths: ~[], + col_min_widths: ~[], + col_pref_widths: ~[], } } @@ -46,12 +52,16 @@ impl TableRowFlow { TableRowFlow { block_flow: BlockFlow::from_node(constructor, node), col_widths: ~[], + col_min_widths: ~[], + col_pref_widths: ~[], } } pub fn teardown(&mut self) { self.block_flow.teardown(); self.col_widths = ~[]; + self.col_min_widths = ~[]; + self.col_pref_widths = ~[]; } pub fn box_<'a>(&'a mut self) -> &'a Option<Box>{ @@ -66,19 +76,23 @@ impl TableRowFlow { /// Assign height for table-row flow. /// + /// TODO(pcwalton): This doesn't handle floats and positioned elements right. + /// /// inline(always) because this is only ever called by in-order or non-in-order top-level /// methods #[inline(always)] - fn assign_height_table_row_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { - let (top_offset, bottom_offset, left_offset) = self.initialize_offsets(); + fn assign_height_table_row_base(&mut self, layout_context: &mut LayoutContext, inorder: bool) { + let (top_offset, _, _) = self.initialize_offsets(); - self.block_flow.handle_children_floats_if_necessary(ctx, inorder, - left_offset, top_offset); let mut cur_y = top_offset; // Per CSS 2.1 § 17.5.3, find max_y = max( computed `height`, minimum height of all cells ) let mut max_y = Au::new(0); for kid in self.block_flow.base.child_iter() { + if inorder { + kid.assign_height_inorder(layout_context) + } + for child_box in kid.as_table_cell().box_().iter() { // TODO: Percentage height let child_specified_height = MaybeAuto::from_style(child_box.style().Box.get().height, @@ -101,6 +115,8 @@ impl TableRowFlow { cur_y = cur_y + height; // Assign the height of own box + // + // FIXME(pcwalton): Take `cur_y` into account. for box_ in self.block_flow.box_.iter() { let mut position = box_.border_box.get(); position.size.height = height; @@ -118,24 +134,14 @@ impl TableRowFlow { let child_node = flow::mut_base(kid); child_node.position.size.height = height; } - - self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, - top_offset, bottom_offset, left_offset); } - pub fn build_display_list_table_row<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_table_row(&mut self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { debug!("build_display_list_table_row: same process as block flow"); - self.block_flow.build_display_list_block(builder, container_block_size, - absolute_cb_abs_position, - dirty, index, lists) + self.block_flow.build_display_list_block(stacking_context, builder, info) } } @@ -152,27 +158,51 @@ impl Flow for TableRowFlow { &mut self.block_flow } + fn col_widths<'a>(&'a mut self) -> &'a mut ~[Au] { + &mut self.col_widths + } + + fn col_min_widths<'a>(&'a self) -> &'a ~[Au] { + &self.col_min_widths + } + + fn col_pref_widths<'a>(&'a self) -> &'a ~[Au] { + &self.col_pref_widths + } + /// Recursively (bottom-up) determines the context's preferred and minimum widths. When called /// on this context, all child contexts have had their min/pref widths set. This function must /// decide min/pref widths based on child context widths and dimensions of any boxes it is /// responsible for flowing. /// Min/pref widths set by this function are used in automatic table layout calculation. - /// Also, this function collects the specified column widths of children cells. Those are used - /// in fixed table layout calculation - fn bubble_widths(&mut self, ctx: &mut LayoutContext) { + /// The specified column widths of children cells are used in fixed table layout calculation. + fn bubble_widths(&mut self, _: &mut LayoutContext) { + let mut min_width = Au(0); + let mut pref_width = Au(0); + let mut num_floats = 0; /* find the specified widths from child table-cell contexts */ for kid in self.block_flow.base.child_iter() { assert!(kid.is_table_cell()); + // collect the specified column widths of cells. These are used in fixed table layout calculation. for child_box in kid.as_table_cell().box_().iter() { let child_specified_width = MaybeAuto::from_style(child_box.style().Box.get().width, Au::new(0)).specified_or_zero(); self.col_widths.push(child_specified_width); } - } - // TODO: calculate min_width & pref_width for automatic table layout calculation - self.block_flow.bubble_widths(ctx); + // collect min_width & pref_width of children cells for automatic table layout calculation. + let child_base = flow::mut_base(kid); + self.col_min_widths.push(child_base.intrinsic_widths.minimum_width); + self.col_pref_widths.push(child_base.intrinsic_widths.preferred_width); + min_width = min_width + child_base.intrinsic_widths.minimum_width; + pref_width = pref_width + child_base.intrinsic_widths.preferred_width; + num_floats = num_floats + child_base.num_floats; + } + self.block_flow.base.num_floats = num_floats; + self.block_flow.base.intrinsic_widths.minimum_width = min_width; + self.block_flow.base.intrinsic_widths.preferred_width = geometry::max(min_width, + pref_width); } /// Recursively (top-down) determines the actual width of child contexts and boxes. When called @@ -205,12 +235,6 @@ impl Flow for TableRowFlow { self.assign_height_table_row_base(ctx, false); } - /// TableRowBox and their parents(TableBox) do not have margins. - /// Therefore, margins to be collapsed do not exist. - fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, - _: &mut Au, _: &mut Au, _: &mut Au) { - } - fn debug_str(&self) -> ~str { let txt = ~"TableRowFlow: "; txt.append(match self.block_flow.box_ { diff --git a/src/components/main/layout/table_rowgroup.rs b/src/components/main/layout/table_rowgroup.rs index 92bb505d1e9..a0b2364146c 100644 --- a/src/components/main/layout/table_rowgroup.rs +++ b/src/components/main/layout/table_rowgroup.rs @@ -9,16 +9,15 @@ use layout::block::BlockFlow; use layout::block::WidthAndMarginsComputer; use layout::construct::FlowConstructor; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo}; use layout::flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils}; use layout::flow; -use layout::table::InternalTable; +use layout::table::{InternalTable, TableFlow}; use layout::wrapper::ThreadSafeLayoutNode; -use std::cell::RefCell; -use geom::{Point2D, Rect, Size2D}; -use gfx::display_list::DisplayListCollection; +use gfx::display_list::StackingContext; use servo_util::geometry::Au; +use servo_util::geometry; /// A table formatting context. pub struct TableRowGroupFlow { @@ -26,6 +25,12 @@ pub struct TableRowGroupFlow { /// Column widths col_widths: ~[Au], + + /// Column min widths. + col_min_widths: ~[Au], + + /// Column pref widths. + col_pref_widths: ~[Au], } impl TableRowGroupFlow { @@ -35,6 +40,8 @@ impl TableRowGroupFlow { TableRowGroupFlow { block_flow: BlockFlow::from_node_and_box(node, box_), col_widths: ~[], + col_min_widths: ~[], + col_pref_widths: ~[], } } @@ -44,12 +51,16 @@ impl TableRowGroupFlow { TableRowGroupFlow { block_flow: BlockFlow::from_node(constructor, node), col_widths: ~[], + col_min_widths: ~[], + col_pref_widths: ~[], } } pub fn teardown(&mut self) { self.block_flow.teardown(); self.col_widths = ~[]; + self.col_min_widths = ~[]; + self.col_pref_widths = ~[]; } pub fn box_<'a>(&'a mut self) -> &'a Option<Box>{ @@ -64,14 +75,14 @@ impl TableRowGroupFlow { /// Assign height for table-rowgroup flow. /// + /// FIXME(pcwalton): This doesn't handle floats right. + /// /// inline(always) because this is only ever called by in-order or non-in-order top-level /// methods #[inline(always)] - fn assign_height_table_rowgroup_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { - let (top_offset, bottom_offset, left_offset) = self.initialize_offsets(); + fn assign_height_table_rowgroup_base(&mut self, _: &mut LayoutContext, _: bool) { + let (top_offset, _, _) = self.initialize_offsets(); - self.block_flow.handle_children_floats_if_necessary(ctx, inorder, - left_offset, top_offset); let mut cur_y = top_offset; for kid in self.block_flow.base.child_iter() { @@ -88,24 +99,14 @@ impl TableRowGroupFlow { box_.border_box.set(position); } self.block_flow.base.position.size.height = height; - - self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, - top_offset, bottom_offset, left_offset); } - pub fn build_display_list_table_rowgroup<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_table_rowgroup(&mut self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { debug!("build_display_list_table_rowgroup: same process as block flow"); - self.block_flow.build_display_list_block(builder, container_block_size, - absolute_cb_abs_position, - dirty, index, lists) + self.block_flow.build_display_list_block(stacking_context, builder, info) } } @@ -122,6 +123,18 @@ impl Flow for TableRowGroupFlow { &mut self.block_flow } + fn col_widths<'a>(&'a mut self) -> &'a mut ~[Au] { + &mut self.col_widths + } + + fn col_min_widths<'a>(&'a self) -> &'a ~[Au] { + &self.col_min_widths + } + + fn col_pref_widths<'a>(&'a self) -> &'a ~[Au] { + &self.col_pref_widths + } + /// Recursively (bottom-up) determines the context's preferred and minimum widths. When called /// on this context, all child contexts have had their min/pref widths set. This function must /// decide min/pref widths based on child context widths and dimensions of any boxes it is @@ -129,24 +142,48 @@ impl Flow for TableRowGroupFlow { /// Min/pref widths set by this function are used in automatic table layout calculation. /// Also, this function finds the specified column widths from the first row. /// Those are used in fixed table layout calculation - fn bubble_widths(&mut self, ctx: &mut LayoutContext) { - /* find the specified column widths from the first table-row. - update the number of column widths from other table-rows. */ + fn bubble_widths(&mut self, _: &mut LayoutContext) { + let mut min_width = Au(0); + let mut pref_width = Au(0); + let mut num_floats = 0; + for kid in self.block_flow.base.child_iter() { assert!(kid.is_table_row()); - if self.col_widths.is_empty() { - self.col_widths = kid.as_table_row().col_widths.clone(); + + // calculate min_width & pref_width for automatic table layout calculation + // 'self.col_min_widths' collects the maximum value of cells' min-widths for each column. + // 'self.col_pref_widths' collects the maximum value of cells' pref-widths for each column. + if self.col_widths.is_empty() { // First Row + assert!(self.col_min_widths.is_empty() && self.col_pref_widths.is_empty()); + // 'self.col_widths' collects the specified column widths from the first table-row for fixed table layout calculation. + self.col_widths = kid.col_widths().clone(); + self.col_min_widths = kid.col_min_widths().clone(); + self.col_pref_widths = kid.col_pref_widths().clone(); } else { + min_width = TableFlow::update_col_widths(&mut self.col_min_widths, kid.col_min_widths()); + pref_width = TableFlow::update_col_widths(&mut self.col_pref_widths, kid.col_pref_widths()); + + // update the number of column widths from table-rows. let num_cols = self.col_widths.len(); - let num_child_cols = kid.as_table_row().col_widths.len(); - for _ in range(num_cols, num_child_cols) { + let num_child_cols = kid.col_min_widths().len(); + for i in range(num_cols, num_child_cols) { self.col_widths.push(Au::new(0)); + let new_kid_min = kid.col_min_widths()[i]; + self.col_min_widths.push(kid.col_min_widths()[i]); + let new_kid_pref = kid.col_pref_widths()[i]; + self.col_pref_widths.push(kid.col_pref_widths()[i]); + min_width = min_width + new_kid_min; + pref_width = pref_width + new_kid_pref; } } + let child_base = flow::mut_base(kid); + num_floats = num_floats + child_base.num_floats; } - // TODO: calculate min_width & pref_width for automatic table layout calculation - self.block_flow.bubble_widths(ctx); + self.block_flow.base.num_floats = num_floats; + self.block_flow.base.intrinsic_widths.minimum_width = min_width; + self.block_flow.base.intrinsic_widths.preferred_width = geometry::max(min_width, + pref_width); } /// Recursively (top-down) determines the actual width of child contexts and boxes. When called @@ -180,12 +217,6 @@ impl Flow for TableRowGroupFlow { self.assign_height_table_rowgroup_base(ctx, false); } - /// TableRowBox and their parents(TableBox) do not have margins. - /// Therefore, margins to be collapsed do not exist. - fn collapse_margins(&mut self, _: bool, _: &mut bool, _: &mut Au, - _: &mut Au, _: &mut Au, _: &mut Au) { - } - fn debug_str(&self) -> ~str { let txt = ~"TableRowGroupFlow: "; txt.append(match self.block_flow.box_ { diff --git a/src/components/main/layout/table_wrapper.rs b/src/components/main/layout/table_wrapper.rs index d0ddfa11f92..639eb542902 100644 --- a/src/components/main/layout/table_wrapper.rs +++ b/src/components/main/layout/table_wrapper.rs @@ -5,23 +5,20 @@ //! CSS table formatting contexts. use layout::box_::Box; -use layout::block::BlockFlow; -use layout::block::{WidthAndMarginsComputer, WidthConstraintInput, WidthConstraintSolution}; +use layout::block::{BlockFlow, MarginsMayNotCollapse, WidthAndMarginsComputer}; +use layout::block::{WidthConstraintInput, WidthConstraintSolution}; use layout::construct::FlowConstructor; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; -use layout::floats::{FloatKind}; +use layout::display_list_builder::{DisplayListBuilder, DisplayListBuildingInfo}; +use layout::floats::FloatKind; use layout::flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils}; -use layout::flow; -use layout::model::{MaybeAuto, Specified, Auto, specified}; +use layout::model::{Specified, Auto, specified}; use layout::wrapper::ThreadSafeLayoutNode; -use std::cell::RefCell; -use style::computed_values::table_layout; -use geom::{Point2D, Rect, Size2D}; -use gfx::display_list::DisplayListCollection; +use gfx::display_list::StackingContext; use servo_util::geometry::Au; use servo_util::geometry; +use style::computed_values::table_layout; pub enum TableLayout { FixedLayout, @@ -103,77 +100,22 @@ impl TableWrapperFlow { /// Assign height for table-wrapper flow. /// `Assign height` of table-wrapper flow follows a similar process to that of block flow. - /// However, table-wrapper flow doesn't consider collapsing margins for flow's children - /// and calculating padding/border. /// /// inline(always) because this is only ever called by in-order or non-in-order top-level /// methods #[inline(always)] - fn assign_height_table_wrapper_base(&mut self, ctx: &mut LayoutContext, inorder: bool) { - - // Note: Ignoring clearance for absolute flows as of now. - let ignore_clear = self.is_absolutely_positioned(); - let (clearance, top_offset, bottom_offset, left_offset) = self.block_flow.initialize_offsets(ignore_clear); - - self.block_flow.handle_children_floats_if_necessary(ctx, inorder, - left_offset, top_offset); - - // Table wrapper flow has margin but is not collapsed with kids(table caption and table). - let (margin_top, margin_bottom, _, _) = self.block_flow.precompute_margin(); - - let mut cur_y = top_offset; - - for kid in self.block_flow.base.child_iter() { - let child_node = flow::mut_base(kid); - child_node.position.origin.y = cur_y; - cur_y = cur_y + child_node.position.size.height; - } - - // top_offset: top margin-edge of the topmost child. - // hence, height = content height - let mut height = cur_y - top_offset; - - // For an absolutely positioned element, store the content height and stop the function. - if self.block_flow.store_content_height_if_absolutely_positioned(height) { - return; - } - - for box_ in self.block_flow.box_.iter() { - let style = box_.style(); - - // At this point, `height` is the height of the containing block, so passing `height` - // as the second argument here effectively makes percentages relative to the containing - // block per CSS 2.1 § 10.5. - height = match MaybeAuto::from_style(style.Box.get().height, height) { - Auto => height, - Specified(value) => geometry::max(value, height) - }; - } - - self.block_flow.compute_height_position(&mut height, - Au(0), - margin_top, - margin_bottom, - clearance); - - self.block_flow.set_floats_out_if_inorder(inorder, height, cur_y, - top_offset, bottom_offset, left_offset); - self.block_flow.assign_height_absolute_flows(ctx); + fn assign_height_table_wrapper_base(&mut self, + layout_context: &mut LayoutContext, + inorder: bool) { + self.block_flow.assign_height_block_base(layout_context, inorder, MarginsMayNotCollapse); } - pub fn build_display_list_table_wrapper<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_table_wrapper(&mut self, + stacking_context: &mut StackingContext, + builder: &mut DisplayListBuilder, + info: &DisplayListBuildingInfo) { debug!("build_display_list_table_wrapper: same process as block flow"); - self.block_flow.build_display_list_block(builder, container_block_size, - absolute_cb_abs_position, - dirty, index, lists) + self.block_flow.build_display_list_block(stacking_context, builder, info); } } @@ -197,7 +139,7 @@ impl Flow for TableWrapperFlow { any boxes it is responsible for flowing. */ fn bubble_widths(&mut self, ctx: &mut LayoutContext) { - /* find max width from child block contexts */ + // get column widths info from table flow for kid in self.block_flow.base.child_iter() { assert!(kid.is_table_caption() || kid.is_table()); @@ -227,8 +169,6 @@ impl Flow for TableWrapperFlow { let mut left_content_edge = Au::new(0); let mut content_width = containing_block_width; - self.block_flow.set_containing_width_if_float(containing_block_width); - let width_computer = TableWrapper; width_computer.compute_used_width_table_wrapper(self, ctx, containing_block_width); @@ -243,7 +183,12 @@ impl Flow for TableWrapperFlow { _ => {} } - self.block_flow.propagate_assigned_width_to_children(left_content_edge, content_width, None); + // In case of fixed layout, column widths are calculated in table flow. + let assigned_col_widths = match self.table_layout { + FixedLayout => None, + AutoLayout => Some(self.col_widths.clone()) + }; + self.block_flow.propagate_assigned_width_to_children(left_content_edge, content_width, assigned_col_widths); } /// This is called on kid flows by a parent. @@ -270,26 +215,6 @@ impl Flow for TableWrapperFlow { } } - // CSS Section 8.3.1 - Collapsing Margins - // `self`: the Flow whose margins we want to collapse. - // `collapsing`: value to be set by this function. This tells us how much - // of the top margin has collapsed with a previous margin. - // `collapsible`: Potential collapsible margin at the bottom of this flow's box. - fn collapse_margins(&mut self, - top_margin_collapsible: bool, - first_in_flow: &mut bool, - margin_top: &mut Au, - top_offset: &mut Au, - collapsing: &mut Au, - collapsible: &mut Au) { - self.block_flow.collapse_margins(top_margin_collapsible, - first_in_flow, - margin_top, - top_offset, - collapsing, - collapsible); - } - fn debug_str(&self) -> ~str { let txt = if self.is_float() { ~"TableWrapperFlow(Float): " @@ -327,10 +252,12 @@ impl TableWrapper { let mut input = self.compute_width_constraint_inputs(&mut table_wrapper.block_flow, parent_flow_width, ctx); - match table_wrapper.table_layout { + let computed_width = match table_wrapper.table_layout { FixedLayout => { let fixed_cells_width = table_wrapper.col_widths.iter().fold(Au(0), |sum, width| sum.add(width)); + + let mut computed_width = input.computed_width.specified_or_zero(); for box_ in table_wrapper.block_flow.box_.iter() { let style = box_.style(); @@ -344,15 +271,72 @@ impl TableWrapper { let border_left = style.Border.get().border_left_width; let border_right = style.Border.get().border_right_width; let padding_and_borders = padding_left + padding_right + border_left + border_right; - let mut computed_width = input.computed_width.specified_or_zero(); // Compare border-edge widths. Because fixed_cells_width indicates content-width, // padding and border values are added to fixed_cells_width. computed_width = geometry::max(fixed_cells_width + padding_and_borders, computed_width); - input.computed_width = Specified(computed_width); } + computed_width }, - _ => {} - } + AutoLayout => { + // Automatic table layout is calculated according to CSS 2.1 § 17.5.2.2. + let mut cap_min = Au(0); + let mut cols_min = Au(0); + let mut cols_max = Au(0); + let mut col_min_widths = &~[]; + let mut col_pref_widths = &~[]; + for kid in table_wrapper.block_flow.base.child_iter() { + if kid.is_table_caption() { + cap_min = kid.as_block().base.intrinsic_widths.minimum_width; + } else { + assert!(kid.is_table()); + cols_min = kid.as_block().base.intrinsic_widths.minimum_width; + cols_max = kid.as_block().base.intrinsic_widths.preferred_width; + col_min_widths = kid.col_min_widths(); + col_pref_widths = kid.col_pref_widths(); + } + } + // 'extra_width': difference between the calculated table width and minimum width + // required by all columns. It will be distributed over the columns. + let (width, extra_width) = match input.computed_width { + Auto => { + if input.available_width > geometry::max(cols_max, cap_min) { + if cols_max > cap_min { + table_wrapper.col_widths = col_pref_widths.clone(); + (cols_max, Au(0)) + } else { + (cap_min, cap_min - cols_min) + } + } else { + let max = if cols_min >= input.available_width && cols_min >= cap_min { + table_wrapper.col_widths = col_min_widths.clone(); + cols_min + } else { + geometry::max(input.available_width, cap_min) + }; + (max, max - cols_min) + } + }, + Specified(width) => { + let max = if cols_min >= width && cols_min >= cap_min { + table_wrapper.col_widths = col_min_widths.clone(); + cols_min + } else { + geometry::max(width, cap_min) + }; + (max, max - cols_min) + } + }; + // The extra width is distributed over the columns + if extra_width > Au(0) { + let cell_len = table_wrapper.col_widths.len() as f64; + table_wrapper.col_widths = col_min_widths.map(|width| { + width + extra_width.scale_by(1.0 / cell_len) + }); + } + width + } + }; + input.computed_width = Specified(computed_width); input } } diff --git a/src/components/main/layout/util.rs b/src/components/main/layout/util.rs index 60969ad19d7..e9a2c47a16d 100644 --- a/src/components/main/layout/util.rs +++ b/src/components/main/layout/util.rs @@ -7,6 +7,7 @@ use layout::construct::{ConstructionResult, NoConstructionResult}; use layout::parallel::DomParallelInfo; use layout::wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode}; +use gfx::display_list::OpaqueNode; use script::dom::bindings::js::JS; use script::dom::bindings::utils::Reflectable; use script::dom::node::Node; @@ -146,6 +147,10 @@ pub struct PrivateLayoutData { /// `ConstructionItem`. See comments in `construct.rs` for more details. flow_construction_result: ConstructionResult, + before_flow_construction_result: ConstructionResult, + + after_flow_construction_result: ConstructionResult, + /// Information needed during parallel traversals. parallel: DomParallelInfo, } @@ -159,6 +164,8 @@ impl PrivateLayoutData { after_style: None, restyle_damage: None, flow_construction_result: NoConstructionResult, + before_flow_construction_result: NoConstructionResult, + after_flow_construction_result: NoConstructionResult, parallel: DomParallelInfo::new(), } } @@ -200,40 +207,45 @@ impl<'ln> LayoutDataAccess for LayoutNode<'ln> { } } -/// 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 { +pub trait OpaqueNodeMethods { /// Converts a DOM node (layout view) to an `OpaqueNode`. - pub fn from_layout_node(node: &LayoutNode) -> OpaqueNode { + fn from_layout_node(node: &LayoutNode) -> Self; + + /// Converts a thread-safe DOM node (layout view) to an `OpaqueNode`. + fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> Self; + + /// Converts a DOM node (script view) to an `OpaqueNode`. + fn from_script_node(node: TrustedNodeAddress) -> Self; + + /// Converts a DOM node to an `OpaqueNode'. + fn from_jsmanaged(node: &JS<Node>) -> Self; + + /// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type + /// of node that script expects to receive in a hit test. + fn to_untrusted_node_address(&self) -> UntrustedNodeAddress; +} + +impl OpaqueNodeMethods for OpaqueNode { + fn from_layout_node(node: &LayoutNode) -> OpaqueNode { unsafe { - OpaqueNode::from_jsmanaged(node.get_jsmanaged()) + OpaqueNodeMethods::from_jsmanaged(node.get_jsmanaged()) } } - /// Converts a thread-safe DOM node (layout view) to an `OpaqueNode`. - pub fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> OpaqueNode { + fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> OpaqueNode { unsafe { let abstract_node = node.get_jsmanaged(); - let ptr: uintptr_t = cast::transmute(abstract_node.reflector().get_jsobject()); + let ptr: uintptr_t = abstract_node.reflector().get_jsobject() as uint; OpaqueNode(ptr) } } - /// Converts a DOM node (script view) to an `OpaqueNode`. - pub fn from_script_node(node: TrustedNodeAddress) -> OpaqueNode { + fn from_script_node(node: TrustedNodeAddress) -> OpaqueNode { unsafe { - OpaqueNode::from_jsmanaged(&JS::from_trusted_node_address(node)) + OpaqueNodeMethods::from_jsmanaged(&JS::from_trusted_node_address(node)) } } - /// Converts a DOM node to an `OpaqueNode'. fn from_jsmanaged(node: &JS<Node>) -> OpaqueNode { unsafe { let ptr: uintptr_t = cast::transmute(node.reflector().get_jsobject()); @@ -241,9 +253,7 @@ impl OpaqueNode { } } - /// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type - /// of node that script expects to receive in a hit test. - pub fn to_untrusted_node_address(&self) -> UntrustedNodeAddress { + fn to_untrusted_node_address(&self) -> UntrustedNodeAddress { unsafe { let OpaqueNode(addr) = *self; let addr: UntrustedNodeAddress = cast::transmute(addr); @@ -251,11 +261,5 @@ impl OpaqueNode { } } - /// Returns the address of this node, for debugging purposes. - pub fn id(&self) -> uintptr_t { - unsafe { - cast::transmute_copy(self) - } - } } diff --git a/src/components/main/layout/wrapper.rs b/src/components/main/layout/wrapper.rs index ddd4a351b0b..755b5aa9fa7 100644 --- a/src/components/main/layout/wrapper.rs +++ b/src/components/main/layout/wrapper.rs @@ -50,7 +50,7 @@ use std::cast; use std::cell::{Ref, RefMut}; use style::{PropertyDeclarationBlock, TElement, TNode, AttrSelector, SpecificNamespace}; use style::{AnyNamespace}; - +use style::computed_values::{content, display}; use layout::util::LayoutDataWrapper; /// Allows some convenience methods on generic layout nodes. @@ -58,8 +58,9 @@ pub trait TLayoutNode { /// Creates a new layout node with the same lifetime as this layout node. unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> Self; - /// Returns the type ID of this node. Fails if this node is borrowed mutably. - fn type_id(&self) -> NodeTypeId; + /// Returns the type ID of this node. Fails if this node is borrowed mutably. Returns `None` + /// if this is a pseudo-element; otherwise, returns `Some`. + fn type_id(&self) -> Option<NodeTypeId>; /// Returns the interior of this node as a `JS`. This is highly unsafe for layout to /// call and as such is marked `unsafe`. @@ -73,14 +74,14 @@ pub trait TLayoutNode { fn node_is_element(&self) -> bool { match self.type_id() { - ElementNodeTypeId(..) => true, + Some(ElementNodeTypeId(..)) => true, _ => false } } fn node_is_document(&self) -> bool { match self.type_id() { - DocumentNodeTypeId(..) => true, + Some(DocumentNodeTypeId(..)) => true, _ => false } } @@ -114,22 +115,10 @@ pub trait TLayoutNode { /// If this is a text node, copies out the text. If this is not a text node, fails. /// /// FIXME(pcwalton): Don't copy text. Atomically reference count instead. - fn text(&self) -> ~str { - unsafe { - if !self.get().is_text() { - fail!("not text!") - } - let text: JS<Text> = self.get_jsmanaged().transmute_copy(); - (*text.unsafe_get()).characterdata.data.to_str() - } - } + fn text(&self) -> ~str; /// Returns the first child of this node. - fn first_child(&self) -> Option<Self> { - unsafe { - self.get().first_child_ref().map(|node| self.new_with_this_lifetime(node)) - } - } + fn first_child(&self) -> Option<Self>; /// Dumps this node tree, for debugging. fn dump(&self) { @@ -144,7 +133,7 @@ pub struct LayoutNode<'a> { priv node: JS<Node>, /// Being chained to a value prevents `LayoutNode`s from escaping. - priv chain: &'a (), + chain: &'a (), } impl<'ln> Clone for LayoutNode<'ln> { @@ -172,12 +161,30 @@ impl<'ln> TLayoutNode for LayoutNode<'ln> { chain: self.chain, } } - fn type_id(&self) -> NodeTypeId { - self.node.type_id() + + fn type_id(&self) -> Option<NodeTypeId> { + Some(self.node.type_id()) } + unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> { &self.node } + + fn first_child(&self) -> Option<LayoutNode<'ln>> { + unsafe { + self.get().first_child_ref().map(|node| self.new_with_this_lifetime(node)) + } + } + + fn text(&self) -> ~str { + unsafe { + if !self.get().is_text() { + fail!("not text!") + } + let text: JS<Text> = self.get_jsmanaged().transmute_copy(); + (*text.unsafe_get()).characterdata.data.to_str() + } + } } impl<'ln> LayoutNode<'ln> { @@ -205,6 +212,10 @@ impl<'ln> LayoutNode<'ln> { current_node: self.first_child(), } } + + pub unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> { + &self.node + } } impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> { @@ -371,29 +382,107 @@ impl<'le> TElement for LayoutElement<'le> { } } +fn get_content(content_list: &content::T) -> ~str { + match *content_list { + content::Content(ref value) => { + let iter = &mut value.clone().move_iter().peekable(); + match iter.next() { + Some(content::StringContent(content)) => content, + _ => ~"", + } + } + _ => ~"", + } +} + +#[deriving(Eq, Clone)] +pub enum ElementType { + Normal, + Before, + After, + BeforeBlock, + AfterBlock, +} + /// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout /// node does not allow any parents or siblings of nodes to be accessed, to avoid races. pub struct ThreadSafeLayoutNode<'ln> { /// The wrapped node. - priv node: JS<Node>, + priv node: LayoutNode<'ln>, - /// Being chained to a value prevents `ThreadSafeLayoutNode`s from escaping. - priv chain: &'ln (), + priv pseudo: ElementType, } impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> { /// Creates a new layout node with the same lifetime as this layout node. unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> ThreadSafeLayoutNode<'ln> { ThreadSafeLayoutNode { - node: node.transmute_copy(), - chain: self.chain, + node: LayoutNode { + node: node.transmute_copy(), + chain: self.node.chain, + }, + pseudo: Normal, } } - fn type_id(&self) -> NodeTypeId { + + /// Returns `None` if this is a pseudo-element. + fn type_id(&self) -> Option<NodeTypeId> { + if self.pseudo == Before || self.pseudo == After { + return None + } + self.node.type_id() } + unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> { - &self.node + self.node.get_jsmanaged() + } + + unsafe fn get<'a>(&'a self) -> &'a Node { // this change. + cast::transmute::<*mut Node,&'a Node>(self.get_jsmanaged().unsafe_get()) + } + + fn first_child(&self) -> Option<ThreadSafeLayoutNode<'ln>> { + if self.pseudo == Before || self.pseudo == After { + return None + } + + if self.has_before_pseudo() { + if self.is_block(Before) && self.pseudo == Normal { + let pseudo_before_node = ThreadSafeLayoutNode::new_with_pseudo_without_self(&self.node, BeforeBlock); + return Some(pseudo_before_node) + } else if self.pseudo == Normal || self.pseudo == BeforeBlock { + let pseudo_before_node = ThreadSafeLayoutNode::new_with_pseudo_without_self(&self.node, Before); + return Some(pseudo_before_node) + } + } + + unsafe { + self.get().first_child_ref().map(|node| self.new_with_this_lifetime(node)) + } + } + + fn text(&self) -> ~str { + if self.pseudo == Before || self.pseudo == After { + let layout_data_ref = self.borrow_layout_data(); + let node_layout_data_wrapper = layout_data_ref.get().get_ref(); + + if self.pseudo == Before { + let before_style = node_layout_data_wrapper.data.before_style.get_ref(); + return get_content(&before_style.get().Box.get().content) + } else { + let after_style = node_layout_data_wrapper.data.after_style.get_ref(); + return get_content(&after_style.get().Box.get().content) + } + } + + unsafe { + if !self.get().is_text() { + fail!("not text!") + } + let text: JS<Text> = self.get_jsmanaged().transmute_copy(); + (*text.unsafe_get()).characterdata.data.to_str() + } } } @@ -401,7 +490,7 @@ impl<'ln> Clone for ThreadSafeLayoutNode<'ln> { fn clone(&self) -> ThreadSafeLayoutNode<'ln> { ThreadSafeLayoutNode { node: self.node.clone(), - chain: self.chain, + pseudo: self.pseudo, } } } @@ -410,13 +499,33 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { /// Creates a new `ThreadSafeLayoutNode` from the given `LayoutNode`. pub fn new<'a>(node: &LayoutNode<'a>) -> ThreadSafeLayoutNode<'a> { ThreadSafeLayoutNode { - node: node.node.clone(), - chain: node.chain, + node: node.clone(), + pseudo: Normal, + } + } + + pub fn new_with_pseudo_without_self<'a>(node: &LayoutNode<'a>, element_type: ElementType) -> ThreadSafeLayoutNode<'a> { + ThreadSafeLayoutNode { + node: node.clone(), + pseudo: element_type, + } + } + + + /// Creates a new `ThreadSafeLayoutNode` from the given `LayoutNode`. + pub fn new_with_pseudo<'a>(&'a self, element_type: ElementType) -> ThreadSafeLayoutNode<'a> { + ThreadSafeLayoutNode { + node: self.node.clone(), + pseudo: element_type, } } /// Returns the next sibling of this node. Unsafe and private because this can lead to races. unsafe fn next_sibling(&self) -> Option<ThreadSafeLayoutNode<'ln>> { + if self.pseudo == Before || self.pseudo == BeforeBlock { + return self.get().first_child_ref().map(|node| self.new_with_this_lifetime(node)) + } + self.node.get().next_sibling_ref().map(|node| self.new_with_this_lifetime(node)) } @@ -424,6 +533,7 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { pub fn children(&self) -> ThreadSafeLayoutNodeChildrenIterator<'ln> { ThreadSafeLayoutNodeChildrenIterator { current_node: self.first_child(), + parent_node: Some(ThreadSafeLayoutNode::new_with_pseudo_without_self(&self.node, self.pseudo)), } } @@ -431,7 +541,7 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { #[inline] pub fn as_element(&self) -> ThreadSafeLayoutElement { unsafe { - let elem: JS<Element> = self.node.transmute_copy(); + let elem: JS<Element> = self.node.get_jsmanaged().transmute_copy(); let element = elem.unsafe_get(); // FIXME(pcwalton): Workaround until Rust gets multiple lifetime parameters on // implementations. @@ -441,6 +551,44 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { } } + pub fn get_element_type(&self) -> ElementType { + self.pseudo + } + + pub fn is_block(&self, kind: ElementType) -> bool { + let mut layout_data_ref = self.mutate_layout_data(); + let node_layout_data_wrapper = layout_data_ref.get().get_mut_ref(); + + let display = match kind { + Before | BeforeBlock => { + let before_style = node_layout_data_wrapper.data.before_style.get_ref(); + before_style.get().Box.get().display + } + After | AfterBlock => { + let after_style = node_layout_data_wrapper.data.after_style.get_ref(); + after_style.get().Box.get().display + } + Normal => { + let after_style = node_layout_data_wrapper.data.style.get_ref(); + after_style.get().Box.get().display + } + }; + + display == display::block + } + + pub fn has_before_pseudo(&self) -> bool { + let layout_data_wrapper = self.borrow_layout_data(); + let layout_data_wrapper_ref = layout_data_wrapper.get().get_ref(); + layout_data_wrapper_ref.data.before_style.is_some() + } + + pub fn has_after_pseudo(&self) -> bool { + let layout_data_wrapper = self.borrow_layout_data(); + let layout_data_wrapper_ref = layout_data_wrapper.get().get_ref(); + layout_data_wrapper_ref.data.after_style.is_some() + } + /// Borrows the layout data immutably. Fails on a conflicting borrow. #[inline(always)] pub fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> { @@ -487,16 +635,56 @@ impl<'ln> ThreadSafeLayoutNode<'ln> { pub struct ThreadSafeLayoutNodeChildrenIterator<'a> { priv current_node: Option<ThreadSafeLayoutNode<'a>>, + priv parent_node: Option<ThreadSafeLayoutNode<'a>>, } impl<'a> Iterator<ThreadSafeLayoutNode<'a>> for ThreadSafeLayoutNodeChildrenIterator<'a> { fn next(&mut self) -> Option<ThreadSafeLayoutNode<'a>> { let node = self.current_node.clone(); - self.current_node = self.current_node.clone().and_then(|node| { - unsafe { - node.next_sibling() + + match node { + Some(ref node) => { + if node.pseudo == After || node.pseudo == AfterBlock { + return None + } + + match self.parent_node { + Some(ref parent_node) => { + if parent_node.pseudo == Normal { + self.current_node = self.current_node.clone().and_then(|node| { + unsafe { + node.next_sibling() + } + }); + } else { + self.current_node = None; + } + } + None => {} + } } - }); + None => { + match self.parent_node { + Some(ref parent_node) => { + if parent_node.has_after_pseudo() { + let pseudo_after_node = if parent_node.is_block(After) && parent_node.pseudo == Normal { + let pseudo_after_node = ThreadSafeLayoutNode::new_with_pseudo_without_self(&parent_node.node, AfterBlock); + Some(pseudo_after_node) + } else if parent_node.pseudo == Normal || parent_node.pseudo == AfterBlock { + let pseudo_after_node = ThreadSafeLayoutNode::new_with_pseudo_without_self(&parent_node.node, After); + Some(pseudo_after_node) + } else { + None + }; + self.current_node = pseudo_after_node; + return self.current_node.clone() + } + } + None => {} + } + } + } + node } } 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..8518bd3024a 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(#2011, 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. + position: Rect<uint>, + /// The background color of the layer. + background_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..9d9897dfc54 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,15 @@ 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(#2003, 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/components/style/common_types.rs b/src/components/style/common_types.rs index 9650f9e83f1..655646790bb 100644 --- a/src/components/style/common_types.rs +++ b/src/components/style/common_types.rs @@ -8,6 +8,7 @@ pub use servo_util::geometry::Au; pub type CSSFloat = f64; +pub static DEFAULT_LINE_HEIGHT: CSSFloat = 1.14; pub mod specified { use std::ascii::StrAsciiExt; @@ -172,7 +173,10 @@ pub mod computed { color: longhands::color::computed_value::T, inherited_font_weight: longhands::font_weight::computed_value::T, inherited_font_size: longhands::font_size::computed_value::T, + inherited_minimum_line_height: longhands::_servo_minimum_line_height::T, + inherited_height: longhands::height::T, font_size: longhands::font_size::computed_value::T, + display: longhands::display::computed_value::T, positioned: bool, floated: bool, border_top_present: bool, diff --git a/src/components/style/properties.rs.mako b/src/components/style/properties.rs.mako index db8cf392722..118eee0f010 100644 --- a/src/components/style/properties.rs.mako +++ b/src/components/style/properties.rs.mako @@ -32,10 +32,11 @@ def to_rust_ident(name): return name class Longhand(object): - def __init__(self, name): + def __init__(self, name, derived_from=None): self.name = name self.ident = to_rust_ident(name) self.style_struct = THIS_STYLE_STRUCT + self.derived_from = None if derived_from is None else to_rust_ident(derived_from) class Shorthand(object): def __init__(self, name, sub_properties): @@ -53,6 +54,7 @@ STYLE_STRUCTS = [] THIS_STYLE_STRUCT = None LONGHANDS = [] LONGHANDS_BY_NAME = {} +DERIVED_LONGHANDS = {} SHORTHANDS = [] def new_style_struct(name, is_inherited): @@ -81,12 +83,13 @@ pub mod longhands { value } - <%def name="raw_longhand(name, no_super=False)"> + <%def name="raw_longhand(name, no_super=False, derived_from=None)"> <% - property = Longhand(name) + property = Longhand(name, derived_from=derived_from) THIS_STYLE_STRUCT.longhands.append(property) LONGHANDS.append(property) LONGHANDS_BY_NAME[name] = property + DERIVED_LONGHANDS.setdefault(derived_from, []).append(property) %> pub mod ${property.ident} { % if not no_super: @@ -94,30 +97,34 @@ pub mod longhands { % endif pub use self::computed_value::*; ${caller.body()} - pub fn parse_declared(input: &[ComponentValue], base_url: &Url) - -> Option<DeclaredValue<SpecifiedValue>> { - match CSSWideKeyword::parse(input) { - Some(Some(keyword)) => Some(CSSWideKeyword(keyword)), - Some(None) => Some(CSSWideKeyword(${ - "Inherit" if THIS_STYLE_STRUCT.inherited else "Initial"})), - None => parse_specified(input, base_url), + % if derived_from is None: + pub fn parse_declared(input: &[ComponentValue], base_url: &Url) + -> Option<DeclaredValue<SpecifiedValue>> { + match CSSWideKeyword::parse(input) { + Some(Some(keyword)) => Some(CSSWideKeyword(keyword)), + Some(None) => Some(CSSWideKeyword(${ + "Inherit" if THIS_STYLE_STRUCT.inherited else "Initial"})), + None => parse_specified(input, base_url), + } } - } + % endif } </%def> - <%def name="longhand(name, no_super=False)"> - <%self:raw_longhand name="${name}"> + <%def name="longhand(name, no_super=False, derived_from=None)"> + <%self:raw_longhand name="${name}" derived_from="${derived_from}"> ${caller.body()} - pub fn parse_specified(input: &[ComponentValue], base_url: &Url) - -> Option<DeclaredValue<SpecifiedValue>> { - parse(input, base_url).map(super::SpecifiedValue) - } + % if derived_from is None: + pub fn parse_specified(_input: &[ComponentValue], _base_url: &Url) + -> Option<DeclaredValue<SpecifiedValue>> { + parse(_input, _base_url).map(super::SpecifiedValue) + } + % endif </%self:raw_longhand> </%def> - <%def name="single_component_value(name)"> - <%self:longhand name="${name}"> + <%def name="single_component_value(name, derived_from=None)"> + <%self:longhand name="${name}" derived_from="${derived_from}"> ${caller.body()} pub fn parse(input: &[ComponentValue], base_url: &Url) -> Option<SpecifiedValue> { one_component_value(input).and_then(|c| from_component_value(c, base_url)) @@ -307,9 +314,29 @@ pub mod longhands { ${predefined_type("width", "LengthOrPercentageOrAuto", "computed::LPA_Auto", "parse_non_negative")} - ${predefined_type("height", "LengthOrPercentageOrAuto", - "computed::LPA_Auto", - "parse_non_negative")} + <%self:single_component_value name="height"> + pub type SpecifiedValue = specified::LengthOrPercentageOrAuto; + pub mod computed_value { + pub type T = super::super::computed::LengthOrPercentageOrAuto; + } + #[inline] + pub fn get_initial_value() -> computed_value::T { computed::LPA_Auto } + #[inline] + pub fn from_component_value(v: &ComponentValue, _base_url: &Url) + -> Option<SpecifiedValue> { + specified::LengthOrPercentageOrAuto::parse_non_negative(v) + } + pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) + -> computed_value::T { + match (value, context.inherited_height) { + (specified::LPA_Percentage(_), computed::LPA_Auto) + if !context.is_root_element && !context.positioned => { + computed::LPA_Auto + }, + _ => computed::compute_LengthOrPercentageOrAuto(value, context) + } + } + </%self:single_component_value> ${predefined_type("min-width", "LengthOrPercentage", "computed::LP_Length(Au(0))", @@ -318,6 +345,13 @@ pub mod longhands { "computed::LPN_None", "parse_non_negative")} + ${predefined_type("min-height", "LengthOrPercentage", + "computed::LP_Length(Au(0))", + "parse_non_negative")} + ${predefined_type("max-height", "LengthOrPercentageOrNone", + "computed::LPN_None", + "parse_non_negative")} + ${new_style_struct("InheritedBox", is_inherited=True)} <%self:single_component_value name="line-height"> @@ -366,6 +400,40 @@ pub mod longhands { } </%self:single_component_value> + <%self:longhand name="-servo-minimum-line-height" derived_from="line-height"> + use super::Au; + use super::super::common_types::DEFAULT_LINE_HEIGHT; + use super::super::longhands::display; + use super::super::longhands::line_height; + + pub use to_computed_value = super::computed_as_specified; + + pub type SpecifiedValue = line_height::SpecifiedValue; + + pub mod computed_value { + pub type T = super::super::Au; + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + Au(0) + } + + #[inline] + pub fn derive(value: line_height::computed_value::T, context: &computed::Context) + -> Au { + if context.display != display::computed_value::inline { + match value { + line_height::Normal => context.font_size.scale_by(DEFAULT_LINE_HEIGHT), + line_height::Number(percentage) => context.font_size.scale_by(percentage), + line_height::Length(length) => length, + } + } else { + context.inherited_minimum_line_height + } + } + </%self:longhand> + ${switch_to_style_struct("Box")} <%self:single_component_value name="vertical-align"> @@ -519,6 +587,81 @@ pub mod longhands { } </%self:single_component_value> + <%self:longhand name="background-position"> + use super::super::common_types::specified; + + pub mod computed_value { + use super::super::super::common_types::computed::LengthOrPercentage; + + #[deriving(Eq, Clone)] + pub struct T { + horizontal: LengthOrPercentage, + vertical: LengthOrPercentage, + } + } + + #[deriving(Clone)] + pub struct SpecifiedValue { + horizontal: specified::LengthOrPercentage, + vertical: specified::LengthOrPercentage, + } + + #[inline] + pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) + -> computed_value::T { + computed_value::T { + horizontal: computed::compute_LengthOrPercentage(value.horizontal, context), + vertical: computed::compute_LengthOrPercentage(value.vertical, context), + } + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T { + horizontal: computed::LP_Percentage(0.0), + vertical: computed::LP_Percentage(0.0), + } + } + + // FIXME(#1997, pcwalton): Support complete CSS2 syntax. + pub fn parse_horizontal_and_vertical(horiz: &ComponentValue, vert: &ComponentValue) + -> Option<SpecifiedValue> { + let horiz = match specified::LengthOrPercentage::parse_non_negative(horiz) { + None => return None, + Some(value) => value, + }; + + let vert = match specified::LengthOrPercentage::parse_non_negative(vert) { + None => return None, + Some(value) => value, + }; + + Some(SpecifiedValue { + horizontal: horiz, + vertical: vert, + }) + } + + pub fn parse(input: &[ComponentValue], _: &Url) -> Option<SpecifiedValue> { + let mut input_iter = input.skip_whitespace(); + let horizontal = input_iter.next(); + let vertical = input_iter.next(); + if input_iter.next().is_some() { + return None + } + + match (horizontal, vertical) { + (Some(horizontal), Some(vertical)) => { + parse_horizontal_and_vertical(horizontal, vertical) + } + _ => None + } + } + </%self:longhand> + + ${single_keyword("background-repeat", "repeat repeat-x repeat-y no-repeat")} + + ${single_keyword("background-attachment", "scroll fixed")} ${new_style_struct("Color", is_inherited=True)} @@ -866,29 +1009,97 @@ pub mod shorthands { </%def> // TODO: other background-* properties - <%self:shorthand name="background" sub_properties="background-color background-image"> - let mut color = None; - let mut image = None; + <%self:shorthand name="background" + sub_properties="background-color background-position background-repeat background-attachment background-image"> + use std::mem; + + let (mut color, mut image, mut position, mut repeat, mut attachment) = + (None, None, None, None, None); + let mut last_component_value = None; let mut any = false; for component_value in input.skip_whitespace() { if color.is_none() { match background_color::from_component_value(component_value, base_url) { - Some(v) => { color = Some(v); any = true; continue }, + Some(v) => { + color = Some(v); + any = true; + continue + }, None => () } } if image.is_none() { match background_image::from_component_value(component_value, base_url) { - Some(v) => { image = Some(v); any = true; continue }, + Some(v) => { + image = Some(v); + any = true; + continue + }, None => (), } } - return None; + + if repeat.is_none() { + match background_repeat::from_component_value(component_value, base_url) { + Some(v) => { + repeat = Some(v); + any = true; + continue + }, + None => () + } + } + + if attachment.is_none() { + match background_attachment::from_component_value(component_value, + base_url) { + Some(v) => { + attachment = Some(v); + any = true; + continue + }, + None => () + } + } + + match mem::replace(&mut last_component_value, None) { + Some(saved_component_value) => { + if position.is_none() { + match background_position::parse_horizontal_and_vertical( + saved_component_value, + component_value) { + Some(v) => { + position = Some(v); + any = true; + continue + }, + None => (), + } + } + + // If we get here, parsing failed. + return None + } + None => { + // Save the component value. + last_component_value = Some(component_value) + } + } + } + + if any && last_component_value.is_none() { + Some(Longhands { + background_color: color, + background_image: image, + background_position: position, + background_repeat: repeat, + background_attachment: attachment, + }) + } else { + None } - if any { Some(Longhands { background_color: color, background_image: image }) } - else { None } </%self:shorthand> ${four_sides_shorthand("margin", "margin-%s", "margin_top::from_component_value")} @@ -1137,12 +1348,16 @@ impl PropertyDeclaration { let name_lower = tmp_for_lifetime.as_str_ascii(); match name_lower.as_slice() { % for property in LONGHANDS: - "${property.name}" => result_list.push(${property.ident}_declaration( - match longhands::${property.ident}::parse_declared(value, base_url) { - Some(value) => value, - None => return InvalidValue, - } - )), + % if property.derived_from is None: + "${property.name}" => result_list.push(${property.ident}_declaration( + match longhands::${property.ident}::parse_declared(value, base_url) { + Some(value) => value, + None => return InvalidValue, + } + )), + % else: + "${property.name}" => {} + % endif % endfor % for shorthand in SHORTHANDS: "${shorthand.name}" => match CSSWideKeyword::parse(value) { @@ -1254,26 +1469,44 @@ fn cascade_with_cached_declarations(applicable_declarations: &[MatchedProperty], % for style_struct in STYLE_STRUCTS: % if style_struct.inherited: % for property in style_struct.longhands: - ${property.ident}_declaration(ref declared_value) => { - style_${style_struct.name}.get_mut().${property.ident} = - match *declared_value { - SpecifiedValue(ref specified_value) - => longhands::${property.ident}::to_computed_value( - (*specified_value).clone(), - context - ), - CSSWideKeyword(Initial) - => longhands::${property.ident}::get_initial_value(), - CSSWideKeyword(Inherit) => { - // This is a bit slow, but this is rare so it shouldn't matter. - // FIXME: is it still? - parent_style.${style_struct.name} - .get() - .${property.ident} - .clone() - } + % if property.derived_from is None: + ${property.ident}_declaration(ref declared_value) => { + let computed_value = match *declared_value { + SpecifiedValue(ref specified_value) + => longhands::${property.ident}::to_computed_value( + (*specified_value).clone(), + context + ), + CSSWideKeyword(Initial) + => longhands::${property.ident}::get_initial_value(), + CSSWideKeyword(Inherit) => { + // This is a bit slow, but this is rare so it shouldn't + // matter. + // + // FIXME: is it still? + parent_style.${style_struct.name} + .get() + .${property.ident} + .clone() + } + }; + style_${style_struct.name}.get_mut().${property.ident} = + computed_value; + + % if property.name in DERIVED_LONGHANDS: + % for derived in DERIVED_LONGHANDS[property.name]: + style_${derived.style_struct.name}.get_mut() + .${derived.ident} = + longhands::${derived.ident}::derive(computed_value, + context); + % endfor + % endif } - } + % else: + ${property.ident}_declaration(_) => { + // Ignore derived properties; they cannot be set by content. + } + % endif % endfor % endif % endfor @@ -1325,8 +1558,13 @@ pub fn cascade(applicable_declarations: &[MatchedProperty], is_root_element: is_root_element, inherited_font_weight: inherited_font_style.font_weight, inherited_font_size: inherited_font_style.font_size, + inherited_height: inherited_style.Box.get().height, + inherited_minimum_line_height: inherited_style.InheritedBox + .get() + ._servo_minimum_line_height, // To be overridden by applicable declarations: font_size: inherited_font_style.font_size, + display: longhands::display::get_initial_value(), color: inherited_style.Color.get().color, positioned: false, floated: false, @@ -1363,6 +1601,9 @@ pub fn cascade(applicable_declarations: &[MatchedProperty], color_declaration(ref value) => { context.color = get_specified!(Color, color, value); } + display_declaration(ref value) => { + context.display = get_specified!(Box, display, value); + } position_declaration(ref value) => { context.positioned = match get_specified!(Box, position, value) { longhands::position::absolute | longhands::position::fixed => true, @@ -1414,27 +1655,45 @@ pub fn cascade(applicable_declarations: &[MatchedProperty], match *declaration { % for style_struct in STYLE_STRUCTS: % for property in style_struct.longhands: - ${property.ident}_declaration(ref declared_value) => { - style_${style_struct.name}.get_mut().${property.ident} = - match *declared_value { - SpecifiedValue(ref specified_value) - => longhands::${property.ident}::to_computed_value( - (*specified_value).clone(), - &context - ), - CSSWideKeyword(Initial) - => longhands::${property.ident}::get_initial_value(), - CSSWideKeyword(Inherit) => { - // This is a bit slow, but this is rare so it shouldn't matter. - // FIXME: is it still? - cacheable = false; - inherited_style.${style_struct.name} - .get() - .${property.ident} - .clone() - } + % if property.derived_from is None: + ${property.ident}_declaration(ref declared_value) => { + let computed_value = match *declared_value { + SpecifiedValue(ref specified_value) + => longhands::${property.ident}::to_computed_value( + (*specified_value).clone(), + &context + ), + CSSWideKeyword(Initial) + => longhands::${property.ident}::get_initial_value(), + CSSWideKeyword(Inherit) => { + // This is a bit slow, but this is rare so it shouldn't + // matter. + // + // FIXME: is it still? + cacheable = false; + inherited_style.${style_struct.name} + .get() + .${property.ident} + .clone() + } + }; + style_${style_struct.name}.get_mut().${property.ident} = + computed_value; + + % if property.name in DERIVED_LONGHANDS: + % for derived in DERIVED_LONGHANDS[property.name]: + style_${derived.style_struct.name}.get_mut() + .${derived.ident} = + longhands::${derived.ident}::derive(computed_value, + &context); + % endfor + % endif } - } + % else: + ${property.ident}_declaration(_) => { + // Ignore derived properties; they cannot be set by content. + } + % endif % endfor % endfor } diff --git a/src/components/util/geometry.rs b/src/components/util/geometry.rs index e02bb8a6bb9..248ce1ba01e 100644 --- a/src/components/util/geometry.rs +++ b/src/components/util/geometry.rs @@ -300,3 +300,11 @@ pub fn to_pt(au: Au) -> f64 { (a as f64) / 60f64 * 72f64 / 96f64 } +/// Returns true if the rect contains the given point. Points on the top or left sides of the rect +/// are considered inside the rectangle, while points on the right or bottom sides of the rect are +/// not considered inside the rectangle. +pub fn rect_contains_point<T:Ord + Add<T,T>>(rect: Rect<T>, point: Point2D<T>) -> bool { + point.x >= rect.origin.x && point.x < rect.origin.x + rect.size.width && + point.y >= rect.origin.y && point.y < rect.origin.y + rect.size.height +} + diff --git a/src/components/util/smallvec.rs b/src/components/util/smallvec.rs index ab51fd961f1..84e3d55b347 100644 --- a/src/components/util/smallvec.rs +++ b/src/components/util/smallvec.rs @@ -60,6 +60,16 @@ pub trait SmallVec<T> : SmallVecPrivate<T> { } } + fn mut_iter<'a>(&'a mut self) -> SmallVecMutIterator<'a,T> { + unsafe { + SmallVecMutIterator { + ptr: cast::transmute(self.begin()), + end: cast::transmute(self.end()), + lifetime: None, + } + } + } + /// NB: For efficiency reasons (avoiding making a second copy of the inline elements), this /// actually clears out the original array instead of moving it. fn move_iter<'a>(&'a mut self) -> SmallVecMoveIterator<'a,T> { @@ -94,6 +104,12 @@ pub trait SmallVec<T> : SmallVecPrivate<T> { } } + fn push_all_move<V:SmallVec<T>>(&mut self, mut other: V) { + for value in other.move_iter() { + self.push(value) + } + } + fn grow(&mut self, new_cap: uint) { unsafe { let new_alloc: *mut T = cast::transmute(global_heap::malloc_raw(mem::size_of::<T>() * @@ -194,6 +210,30 @@ impl<'a,T> Iterator<&'a T> for SmallVecIterator<'a,T> { } } +pub struct SmallVecMutIterator<'a,T> { + priv ptr: *mut T, + priv end: *mut T, + priv lifetime: Option<&'a mut T> +} + +impl<'a,T> Iterator<&'a mut T> for SmallVecMutIterator<'a,T> { + #[inline] + fn next(&mut self) -> Option<&'a mut T> { + unsafe { + if self.ptr == self.end { + return None + } + let old = self.ptr; + self.ptr = if mem::size_of::<T>() == 0 { + cast::transmute(self.ptr as uint + 1) + } else { + self.ptr.offset(1) + }; + Some(cast::transmute(old)) + } + } +} + pub struct SmallVecMoveIterator<'a,T> { priv allocation: Option<*mut u8>, priv iter: SmallVecIterator<'static,T>, diff --git a/src/support/azure/rust-azure b/src/support/azure/rust-azure -Subproject cca153ff06af97b3a66586be65322ca3196e60f +Subproject 10b1cf8e35fa1757bcab11dcc284f77e4ceeaff diff --git a/src/support/layers/rust-layers b/src/support/layers/rust-layers -Subproject 400a01307515def5dd6874ec44ed8a017ada36f +Subproject 79e405fa59c052ff78d7a2527a92474a32ac9b4 diff --git a/src/test/ref/background_external_stylesheet.html b/src/test/ref/background_external_stylesheet.html index 791bfa41c87..9465a1b4cae 100644 --- a/src/test/ref/background_external_stylesheet.html +++ b/src/test/ref/background_external_stylesheet.html @@ -5,6 +5,6 @@ <link rel="stylesheet" href="subdirectory/background_image.css"> </head> <body> -<div class="test" style="width:200px; height:200px;"></div> +<div class="test" style="width:206px; height:206px;"></div> </body> </html> diff --git a/src/test/ref/background_position_a.html b/src/test/ref/background_position_a.html new file mode 100644 index 00000000000..23547b3f6b6 --- /dev/null +++ b/src/test/ref/background_position_a.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled FOOBIE BLETCH.</title> +<style> +#foo { + background: url(400x400_green.png); + background-position: 128px 0px; + background-repeat: no-repeat; + width: 528px; + height: 400px; + margin-left: 0; +} +</style> +</head> +<body> +<div id=foo></div> +</body> +</html> + diff --git a/src/test/ref/background_position_b.html b/src/test/ref/background_position_b.html new file mode 100644 index 00000000000..6c1d0ab9ecd --- /dev/null +++ b/src/test/ref/background_position_b.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled FOOBIE BLETCH.</title> +<style> +#foo { + background: url(400x400_green.png); + width: 400px; + height: 400px; + margin-left: 128px; +} +</style> +</head> +<body> +<div id=foo></div> +</body> +</html> + + diff --git a/src/test/ref/background_ref.html b/src/test/ref/background_ref.html index 11faa177fb7..81f0d15e1fc 100644 --- a/src/test/ref/background_ref.html +++ b/src/test/ref/background_ref.html @@ -4,6 +4,6 @@ <title></title> </head> <body> -<img class="test" src="rust-0.png" style="width:200px; height:200px;" /> +<img class="test" src="rust-0.png" style="width:206px; height:206px;" /> </body> </html> diff --git a/src/test/ref/background_repeat_both_a.html b/src/test/ref/background_repeat_both_a.html new file mode 100644 index 00000000000..2f5bb3353ce --- /dev/null +++ b/src/test/ref/background_repeat_both_a.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled XIXAXA XOXAXA XUXAXA.</title> +<style> +div { + width: 412px; + height: 412px; + background: url(rust-0.png); + background-repeat: repeat; +} +</style> +</head> +<body> +<div id=repeat></div> +</body> +</html> + + diff --git a/src/test/ref/background_repeat_both_b.html b/src/test/ref/background_repeat_both_b.html new file mode 100644 index 00000000000..4a452813855 --- /dev/null +++ b/src/test/ref/background_repeat_both_b.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled XIXAXA XOXAXA XUXAXA.</title> +<style> +.repeaty { + width: 412px; + height: 206px; + background: url(rust-0.png); +} +.repeatx { + width: 206px; + height: 206px; + background: url(rust-0.png); + float: left; +} +</style> +</head> +<body> +<div> +<div class=repeaty><div class=repeatx></div><div class=repeatx></div></div> +<div class=repeaty><div class=repeatx></div><div class=repeatx></div></div> +</div> +</body> +</html> + + diff --git a/src/test/ref/background_repeat_none_a.html b/src/test/ref/background_repeat_none_a.html new file mode 100644 index 00000000000..81fadc33961 --- /dev/null +++ b/src/test/ref/background_repeat_none_a.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled XIXAXA XOXAXA XUXAXA.</title> +<style> +div { + width: 400px; + height: 400px; + background: url(rust-0.png); + background-repeat: no-repeat; +} +</style> +</head> +<body> +<div id=repeat></div> +</body> +</html> + + diff --git a/src/test/ref/background_repeat_none_b.html b/src/test/ref/background_repeat_none_b.html new file mode 100644 index 00000000000..4448cf19eb9 --- /dev/null +++ b/src/test/ref/background_repeat_none_b.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled XIXAXA XOXAXA XUXAXA.</title> +<style> +div { + width: 200px; + height: 200px; + background: url(rust-0.png); +} +</style> +</head> +<body> +<div id=repeat></div> +</body> +</html> + + diff --git a/src/test/ref/background_repeat_x_a.html b/src/test/ref/background_repeat_x_a.html new file mode 100644 index 00000000000..eb944aa9751 --- /dev/null +++ b/src/test/ref/background_repeat_x_a.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled XIXAXA XOXAXA XUXAXA.</title> +<style> +div { + width: 412px; + height: 412px; + background: url(rust-0.png); + background-repeat: repeat-x; + position: absolute; + left: 0; + top: 0; +} +</style> +</head> +<body> +<div></div> +</body> +</html> + + diff --git a/src/test/ref/background_repeat_x_b.html b/src/test/ref/background_repeat_x_b.html new file mode 100644 index 00000000000..410bcad4dd7 --- /dev/null +++ b/src/test/ref/background_repeat_x_b.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled XIXAXA XOXAXA XUXAXA.</title> +<style> +div { + width: 206px; + height: 206px; + background: url(rust-0.png); + position: absolute; + top: 0; +} +.repeata { + left: 0; +} +.repeatb { + left: 206px; +} +</style> +</head> +<body> +<div class=repeata></div><div class=repeatb></div> +</body> +</html> + + diff --git a/src/test/ref/background_repeat_y_a.html b/src/test/ref/background_repeat_y_a.html new file mode 100644 index 00000000000..46842c9501c --- /dev/null +++ b/src/test/ref/background_repeat_y_a.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled XIXAXA XOXAXA XUXAXA.</title> +<style> +div { + width: 412px; + height: 412px; + background: url(rust-0.png); + background-repeat: repeat-y; +} +</style> +</head> +<body> +<div id=repeat></div> +</body> +</html> + + diff --git a/src/test/ref/background_repeat_y_b.html b/src/test/ref/background_repeat_y_b.html new file mode 100644 index 00000000000..706e76af3c6 --- /dev/null +++ b/src/test/ref/background_repeat_y_b.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled XIXAXA XOXAXA XUXAXA.</title> +<style> +.repeaty { + width: 206px; + height: 206px; + background: url(rust-0.png); +} +</style> +</head> +<body> +<div> +<div class=repeaty></div> +<div class=repeaty></div> +</div> +</body> +</html> + + diff --git a/src/test/ref/background_style_attr.html b/src/test/ref/background_style_attr.html index 28bc8f6b21d..1879b52816b 100644 --- a/src/test/ref/background_style_attr.html +++ b/src/test/ref/background_style_attr.html @@ -4,6 +4,6 @@ <title></title> </head> <body> -<div class="test" style="background: url(rust-0.png); width:200px; height:200px;"></div> +<div class="test" style="background: url(rust-0.png); width:206px; height:206px;"></div> </body> </html> diff --git a/src/test/ref/basic.list b/src/test/ref/basic.list index 8108ab7a9c9..4df86c52e8b 100644 --- a/src/test/ref/basic.list +++ b/src/test/ref/basic.list @@ -48,9 +48,20 @@ == position_abs_replaced_simple_a.html position_abs_replaced_simple_b.html == position_abs_static_y_a.html position_abs_static_y_b.html == position_abs_width_percentage_a.html position_abs_width_percentage_b.html -== position_fixed_a.html position_fixed_b.html -== position_fixed_simple_a.html position_fixed_simple_b.html -== position_fixed_static_y_a.html position_fixed_static_y_b.html +# commented out because multiple layers don't work with reftests --pcwalton +# == position_fixed_a.html position_fixed_b.html +# == position_fixed_simple_a.html position_fixed_simple_b.html +# == position_fixed_static_y_a.html position_fixed_static_y_b.html == position_relative_a.html position_relative_b.html == position_relative_top_percentage_a.html position_relative_top_percentage_b.html == background_none_a.html background_none_b.html +== negative_margins_a.html negative_margins_b.html +== negative_margin_uncle_a.html negative_margin_uncle_b.html +== inline_padding_a.html inline_padding_b.html +== min_max_height_a.html min_max_height_b.html +== minimum_line_height_a.html minimum_line_height_b.html +== background_position_a.html background_position_b.html +== background_repeat_x_a.html background_repeat_x_b.html +== background_repeat_y_a.html background_repeat_y_b.html +== background_repeat_none_a.html background_repeat_none_b.html +== background_repeat_both_a.html background_repeat_both_b.html diff --git a/src/test/ref/inline_padding_a.html b/src/test/ref/inline_padding_a.html new file mode 100644 index 00000000000..3caceb80cd6 --- /dev/null +++ b/src/test/ref/inline_padding_a.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled READ ME.</title> +<style> +#a { + padding: 0 0 0 64px; + color: blue; +} +</style> +</head> +<body> +<div><span id=a>blah blah blah</span></div> +</body> +</html> + diff --git a/src/test/ref/inline_padding_b.html b/src/test/ref/inline_padding_b.html new file mode 100644 index 00000000000..101efc121c4 --- /dev/null +++ b/src/test/ref/inline_padding_b.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled READ ME.</title> +<style> +#a { + padding: 0 0 0 64px; + color: blue; + margin: 0; +} +</style> +</head> +<body> +<div id=a>blah blah blah</div> +</body> +</html> + diff --git a/src/test/ref/min_max_height_a.html b/src/test/ref/min_max_height_a.html new file mode 100644 index 00000000000..ace1cac6df3 --- /dev/null +++ b/src/test/ref/min_max_height_a.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled VE FORBRYDERNE.</title> +<style> +div { + width: 50px; +} +#a { + background: red; + height: 50px; + min-height: 100px; +} +#b { + background: green; + height: 100px; + max-height: 50px; +} +#c { + background: blue; + height: 50px; + min-height: 100px; /* <-- this one overrides per the spec */ + max-height: 25px; +} +</style> +</head> +<body> +<div id=a></div> +<div id=b></div> +<div id=c></div> +</body> +</html> + diff --git a/src/test/ref/min_max_height_b.html b/src/test/ref/min_max_height_b.html new file mode 100644 index 00000000000..8723d65b258 --- /dev/null +++ b/src/test/ref/min_max_height_b.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled VE FORBRYDERNE.</title> +<style> +div { + width: 50px; +} +#a { + background: red; + height: 100px; +} +#b { + background: green; + height: 50px; +} +#c { + background: blue; + height: 100px; +} +</style> +</head> +<body> +<div id=a></div> +<div id=b></div> +<div id=c></div> +</body> +</html> + diff --git a/src/test/ref/minimum_line_height_a.html b/src/test/ref/minimum_line_height_a.html new file mode 100644 index 00000000000..819c4ecdc8f --- /dev/null +++ b/src/test/ref/minimum_line_height_a.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> +<style> +#a { + line-height: 100px; +} + +#b { + line-height: 6px; +} +</style> +<body> +<div id=a><span id=b>Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.</span></div> +</body> +</html> + diff --git a/src/test/ref/minimum_line_height_b.html b/src/test/ref/minimum_line_height_b.html new file mode 100644 index 00000000000..3805578b1ec --- /dev/null +++ b/src/test/ref/minimum_line_height_b.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> +<style> +#a { + line-height: 100px; +} + +#b { + line-height: 100px; +} +</style> +<body> +<div id=a><span id=b>Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.</span></div> +</body> +</html> + diff --git a/src/test/ref/negative_margin_uncle_a.html b/src/test/ref/negative_margin_uncle_a.html new file mode 100644 index 00000000000..a96c2bdd700 --- /dev/null +++ b/src/test/ref/negative_margin_uncle_a.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled KERNOD WEL.</title> +<style> +body { + font-size: 40px; +} +#d { + float: right; + background: green; + color: white; +} +#b { + margin: 0 0 -100px 0; +} +#c { + margin: 100px 0 0 0; + clear: both; + background-color: blue; + color: white; +} +</style> +</head> +<body> +<div id=d>Beetlejuice</div> +<div id=a><div id=b>Beetlejuice</div></div> +<div id=c>Beetlejuice</div> +</body> +</html> + diff --git a/src/test/ref/negative_margin_uncle_b.html b/src/test/ref/negative_margin_uncle_b.html new file mode 100644 index 00000000000..3269c47c2d6 --- /dev/null +++ b/src/test/ref/negative_margin_uncle_b.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled KERNOD WEL.</title> +<style> +body { + font-size: 40px; +} +#d { + float: right; + background: green; + color: white; +} +#c { + clear: both; + background-color: blue; + color: white; +} +</style> +</head> +<body> +<div id=d>Beetlejuice</div> +<div id=a>Beetlejuice</div> +<div id=c>Beetlejuice</div> +</body> +</html> + diff --git a/src/test/ref/negative_margins_a.html b/src/test/ref/negative_margins_a.html new file mode 100644 index 00000000000..3f4197f0e4a --- /dev/null +++ b/src/test/ref/negative_margins_a.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled JUYED AWK YACC.</title> +<style> +* { + line-height: 14px; +} +#b { + margin-top: -14px; +} +</style> +</head> +<body> +<div id=a>Here lies the body of Jonathan Blake.</div> +<div id=b>Stepped on the gas instead of the brake.</div> +</body> +</html> + diff --git a/src/test/ref/negative_margins_b.html b/src/test/ref/negative_margins_b.html new file mode 100644 index 00000000000..092ad0316d3 --- /dev/null +++ b/src/test/ref/negative_margins_b.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<title>You see here a scroll labeled JUYED AWK YACC.</title> +<style> +* { + line-height: 14px; +} +#a { + position: relative; +} +#b { + position: absolute; + top: 0; + left: 0; + right: 0; +} +</style> +</head> +<body> +<div id=a>Here lies the body of Jonathan Blake. +<div id=b>Stepped on the gas instead of the brake.</div></div> +</body> +</html> + |