diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2013-10-24 16:57:42 -0700 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2013-10-28 13:22:26 -0700 |
commit | 42092921c17a779eb8c0db3b056204dfaaa9dab7 (patch) | |
tree | c12417ad6538e0ba55328c4f3d254295055e133a | |
parent | 81f5ba7d059805e1c879bff1b7380c1d90ba9fd8 (diff) | |
download | servo-42092921c17a779eb8c0db3b056204dfaaa9dab7.tar.gz servo-42092921c17a779eb8c0db3b056204dfaaa9dab7.zip |
Optimize reflow by changing enums to traits and inlining more
44 files changed, 2635 insertions, 2272 deletions
diff --git a/Makefile.in b/Makefile.in index 3d372fd0500..9689d5fd121 100644 --- a/Makefile.in +++ b/Makefile.in @@ -32,7 +32,7 @@ MKFILE_DEPS := config.stamp $(call rwildcard,$(S)mk/,*) # Enable debug!() etc even without configure --enable-debug # The evaluation of these prints & their arguments is controlled # at runtime by the environment variable RUST_LOG. -CFG_RUSTC_FLAGS += --cfg debug -Z debug-info +CFG_RUSTC_FLAGS += -Z debug-info -Z no-debug-borrows ifdef CFG_DISABLE_OPTIMIZE $(info cfg: disabling rustc optimization (CFG_DISABLE_OPTIMIZE)) diff --git a/src/components/gfx/font.rs b/src/components/gfx/font.rs index 7ec40f29071..faecef29904 100644 --- a/src/components/gfx/font.rs +++ b/src/components/gfx/font.rs @@ -2,33 +2,29 @@ * 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::{AzFloat, AzScaledFontRef}; +use azure::azure_hl::{BackendType, ColorPattern}; +use azure::scaled_font::ScaledFont; +use extra::arc::Arc; +use geom::{Point2D, Rect, Size2D}; +use std::cast; +use std::ptr; +use std::str; +use std::vec; +use servo_util::cache::{Cache, HashCache}; +use servo_util::range::Range; +use servo_util::time::ProfilerChan; +use style::computed_values::text_decoration; + use color::Color; use font_context::FontContext; use servo_util::geometry::Au; use platform::font_context::FontContextHandle; use platform::font::{FontHandle, FontTable}; use render_context::RenderContext; -use servo_util::range::Range; -use std::cast; -use std::ptr; -use std::str; -use std::vec; -use servo_util::cache::{Cache, HashCache}; use text::glyph::{GlyphStore, GlyphIndex}; use text::shaping::ShaperMethods; use text::{Shaper, TextRun}; -use extra::arc::Arc; - -use azure::{AzFloat, AzScaledFontRef}; -use azure::scaled_font::ScaledFont; -use azure::azure_hl::{BackendType, ColorPattern}; -use geom::{Point2D, Rect, Size2D}; - -use servo_util::time; -use servo_util::time::profile; -use servo_util::time::ProfilerChan; - -use style::computed_values::text_decoration; // FontHandle encapsulates access to the platform's font API, // e.g. quartz, FreeType. It provides access to metrics and tables @@ -459,13 +455,11 @@ impl Font { } pub fn shape_text(@mut self, text: ~str, is_whitespace: bool) -> Arc<GlyphStore> { - do profile(time::LayoutShapingCategory, self.profiler_chan.clone()) { - let shaper = self.get_shaper(); - do self.shape_cache.find_or_create(&text) |txt| { - let mut glyphs = GlyphStore::new(text.char_len(), is_whitespace); - shaper.shape_text(*txt, &mut glyphs); - Arc::new(glyphs) - } + let shaper = self.get_shaper(); + do self.shape_cache.find_or_create(&text) |txt| { + let mut glyphs = GlyphStore::new(text.char_len(), is_whitespace); + shaper.shape_text(*txt, &mut glyphs); + Arc::new(glyphs) } } diff --git a/src/components/gfx/platform/linux/font.rs b/src/components/gfx/platform/linux/font.rs index f17ca694813..0f33720d420 100644 --- a/src/components/gfx/platform/linux/font.rs +++ b/src/components/gfx/platform/linux/font.rs @@ -276,7 +276,7 @@ impl<'self> FontHandle { } #[fixed_stack_segment] - pub fn new_from_file(fctx: &FontContextHandle, file: ~str, + pub fn new_from_file(fctx: &FontContextHandle, file: &str, style: &SpecifiedFontStyle) -> Result<FontHandle, ()> { unsafe { let ft_ctx: FT_Library = fctx.ctx.ctx; @@ -293,7 +293,7 @@ impl<'self> FontHandle { } if FontHandle::set_char_size(face, style.pt_size).is_ok() { Ok(FontHandle { - source: FontSourceFile(file), + source: FontSourceFile(file.to_str()), face: face, handle: *fctx }) diff --git a/src/components/gfx/text/glyph.rs b/src/components/gfx/text/glyph.rs index 0baa11dff51..077a393d9c3 100644 --- a/src/components/gfx/text/glyph.rs +++ b/src/components/gfx/text/glyph.rs @@ -303,7 +303,11 @@ impl Ord for DetailedGlyphRecord { // usage pattern of setting/appending all the detailed glyphs, and // then querying without setting. struct DetailedGlyphStore { + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. detail_buffer: ~[DetailedGlyph], + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. detail_lookup: ~[DetailedGlyphRecord], lookup_is_sorted: bool, } @@ -506,8 +510,12 @@ impl<'self> GlyphInfo<'self> { // Public data structure and API for storing and retrieving glyph data pub struct GlyphStore { + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. entry_buffer: ~[GlyphEntry], + detail_store: DetailedGlyphStore, + is_whitespace: bool, } @@ -602,6 +610,7 @@ impl<'self> GlyphStore { self.iter_glyphs_for_char_range(&Range::new(i, 1)) } + #[inline] pub fn iter_glyphs_for_char_range(&'self self, rang: &Range) -> GlyphIterator<'self> { if rang.begin() >= self.entry_buffer.len() { fail!("iter_glyphs_for_range: range.begin beyond length!"); @@ -682,23 +691,44 @@ pub struct GlyphIterator<'self> { priv glyph_range: Option<iter::Range<uint>>, } +impl<'self> GlyphIterator<'self> { + // Slow path when there is a glyph range. + #[inline(never)] + fn next_glyph_range(&mut self) -> Option<(uint, GlyphInfo<'self>)> { + match self.glyph_range.get_mut_ref().next() { + Some(j) => Some((self.char_index, + DetailGlyphInfo(self.store, self.char_index, j as u16))), + None => { + // No more glyphs for current character. Try to get another. + self.glyph_range = None; + self.next() + } + } + } + + // Slow path when there is a complex glyph. + #[inline(never)] + fn next_complex_glyph(&mut self, entry: &GlyphEntry, i: uint) + -> Option<(uint, GlyphInfo<'self>)> { + let glyphs = self.store.detail_store.get_detailed_glyphs_for_entry(i, entry.glyph_count()); + self.glyph_range = Some(range(0, glyphs.len())); + self.next() + } +} + impl<'self> Iterator<(uint, GlyphInfo<'self>)> for GlyphIterator<'self> { // I tried to start with something simpler and apply FlatMap, but the // inability to store free variables in the FlatMap struct was problematic. - + // + // This function consists of the fast path and is designed to be inlined into its caller. The + // slow paths, which should not be inlined, are `next_glyph_range()` and + // `next_complex_glyph()`. + #[inline(always)] fn next(&mut self) -> Option<(uint, GlyphInfo<'self>)> { // Would use 'match' here but it borrows contents in a way that // interferes with mutation. if self.glyph_range.is_some() { - match self.glyph_range.get_mut_ref().next() { - Some(j) => Some((self.char_index, - DetailGlyphInfo(self.store, self.char_index, j as u16))), - None => { - // No more glyphs for current character. Try to get another. - self.glyph_range = None; - self.next() - } - } + self.next_glyph_range() } else { // No glyph range. Look at next character. match self.char_range.next() { @@ -709,10 +739,8 @@ impl<'self> Iterator<(uint, GlyphInfo<'self>)> for GlyphIterator<'self> { if entry.is_simple() { Some((self.char_index, SimpleGlyphInfo(self.store, i))) } else { - let glyphs = self.store.detail_store - .get_detailed_glyphs_for_entry(i, entry.glyph_count()); - self.glyph_range = Some(range(0, glyphs.len())); - self.next() + // Fall back to the slow path. + self.next_complex_glyph(entry, i) } }, None => None diff --git a/src/components/gfx/text/text_run.rs b/src/components/gfx/text/text_run.rs index bcc8d3c876e..81df6f0ee3a 100644 --- a/src/components/gfx/text/text_run.rs +++ b/src/components/gfx/text/text_run.rs @@ -14,18 +14,19 @@ use style::computed_values::text_decoration; /// A text run. pub struct TextRun { - text: ~str, + text: Arc<~str>, font: @mut Font, decoration: text_decoration::T, - glyphs: ~[Arc<GlyphStore>], + glyphs: Arc<~[Arc<GlyphStore>]>, } -/// This is a hack until TextRuns are normally sendable, or we instead use Arc<TextRun> everywhere. +/// The same as a text run, but with a font descriptor instead of a font. This makes them thread +/// safe. pub struct SendableTextRun { - text: ~str, + text: Arc<~str>, font: FontDescriptor, decoration: text_decoration::T, - priv glyphs: ~[Arc<GlyphStore>], + priv glyphs: Arc<~[Arc<GlyphStore>]>, } impl SendableTextRun { @@ -51,6 +52,8 @@ pub struct SliceIterator<'self> { } impl<'self> Iterator<(&'self GlyphStore, uint, Range)> for SliceIterator<'self> { + // inline(always) due to the inefficient rt failures messing up inline heuristics, I think. + #[inline(always)] fn next(&mut self) -> Option<(&'self GlyphStore, uint, Range)> { loop { let slice_glyphs = self.glyph_iter.next(); @@ -121,10 +124,10 @@ impl<'self> TextRun { let glyphs = TextRun::break_and_shape(font, text); let run = TextRun { - text: text, + text: Arc::new(text), font: font, decoration: decoration, - glyphs: glyphs, + glyphs: Arc::new(glyphs), }; return run; } @@ -198,12 +201,14 @@ impl<'self> TextRun { } pub fn char_len(&self) -> uint { - do self.glyphs.iter().fold(0u) |len, slice_glyphs| { + do self.glyphs.get().iter().fold(0u) |len, slice_glyphs| { len + slice_glyphs.get().char_len() } } - pub fn glyphs(&'self self) -> &'self ~[Arc<GlyphStore>] { &self.glyphs } + pub fn glyphs(&'self self) -> &'self ~[Arc<GlyphStore>] { + self.glyphs.get() + } pub fn range_is_trimmable_whitespace(&self, range: &Range) -> bool { for (slice_glyphs, _, _) in self.iter_slices_for_range(range) { @@ -233,7 +238,7 @@ impl<'self> TextRun { pub fn iter_slices_for_range(&'self self, range: &Range) -> SliceIterator<'self> { SliceIterator { - glyph_iter: self.glyphs.iter(), + glyph_iter: self.glyphs.get().iter(), range: *range, offset: 0, } diff --git a/src/components/main/compositing/compositor_layer.rs b/src/components/main/compositing/compositor_layer.rs index 13d22f6602a..29b79ef06d4 100644 --- a/src/components/main/compositing/compositor_layer.rs +++ b/src/components/main/compositing/compositor_layer.rs @@ -19,7 +19,8 @@ use script::script_task::SendEventMsg; use servo_msg::compositor_msg::{LayerBuffer, LayerBufferSet, Epoch, Tile}; use servo_msg::constellation_msg::PipelineId; use std::cell::Cell; -use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent}; +use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent}; +use windowing::{MouseWindowMouseUpEvent}; #[cfg(not(target_os="macos"))] use layers::texturegl::TextureTarget2D; @@ -372,7 +373,7 @@ impl CompositorLayer { } } - // Returns whether the layer should be vertically flipped. and + // Returns whether the layer should be vertically flipped. #[cfg(target_os="macos")] fn texture_flip_and_target(cpu_painting: bool, size: Size2D<uint>) -> (Flip, TextureTarget) { let flip = if cpu_painting { diff --git a/src/components/main/css/node_style.rs b/src/components/main/css/node_style.rs index 49ab10710e8..e4c40e1e3b4 100644 --- a/src/components/main/css/node_style.rs +++ b/src/components/main/css/node_style.rs @@ -9,8 +9,6 @@ use layout::incremental::RestyleDamage; use style::ComputedValues; use script::dom::node::{AbstractNode, LayoutView}; -use servo_util::tree::TreeNodeRef; - /// Node mixin providing `style` method that returns a `NodeStyle` pub trait StyledNode { @@ -20,7 +18,8 @@ pub trait StyledNode { impl StyledNode for AbstractNode<LayoutView> { fn style(&self) -> &ComputedValues { - assert!(self.is_element()); // Only elements can have styles + // FIXME(pcwalton): Is this assertion needed for memory safety? It's slow. + //assert!(self.is_element()); // Only elements can have styles let results = self.get_css_select_results(); results } diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index 45044caa341..ba3b4746a63 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -4,11 +4,11 @@ //! CSS block layout. -use layout::box::{RenderBox}; +use layout::box::{RenderBox, RenderBoxUtils}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; -use layout::flow::{BlockFlow, FlowContext, FlowData, InlineBlockFlow, FloatFlow, InlineFlow}; -use layout::inline::InlineLayout; +use layout::flow::{BlockFlowClass, FlowClass, FlowContext, FlowData, ImmutableFlowUtils}; +use layout::flow; use layout::model::{MaybeAuto, Specified, Auto}; use layout::float_context::{FloatContext, Invalid}; @@ -20,29 +20,29 @@ use gfx::display_list::DisplayList; use servo_util::geometry::{Au, to_frac_px}; use servo_util::geometry; -pub struct BlockFlowData { +pub struct BlockFlow { /// Data common to all flows. - common: FlowData, + base: FlowData, /// The associated render box. - box: Option<RenderBox>, + box: Option<@RenderBox>, /// Whether this block flow is the root flow. is_root: bool } -impl BlockFlowData { - pub fn new(common: FlowData) -> BlockFlowData { - BlockFlowData { - common: common, +impl BlockFlow { + pub fn new(base: FlowData) -> BlockFlow { + BlockFlow { + base: base, box: None, is_root: false } } - pub fn new_root(common: FlowData) -> BlockFlowData { - BlockFlowData { - common: common, + pub fn new_root(base: FlowData) -> BlockFlow { + BlockFlow { + base: base, box: None, is_root: true } @@ -54,78 +54,18 @@ impl BlockFlowData { } self.box = None; } -} - -pub trait BlockLayout { - fn starts_root_flow(&self) -> bool; - fn starts_block_flow(&self) -> bool; -} - -impl BlockLayout for FlowContext { - fn starts_root_flow(&self) -> bool { - match *self { - BlockFlow(ref info) => info.is_root, - _ => false - } - } - - fn starts_block_flow(&self) -> bool { - match *self { - BlockFlow(*) | InlineBlockFlow(*) | FloatFlow(*) => true, - _ => false - } - } -} - -impl BlockFlowData { - /* Recursively (bottom-up) determine 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. */ - - /* TODO: floats */ - /* TODO: absolute contexts */ - /* TODO: inline-blocks */ - pub fn bubble_widths_block(&mut self, ctx: &LayoutContext) { - let mut min_width = Au(0); - let mut pref_width = Au(0); - let mut num_floats = 0; - - /* find max width from child block contexts */ - for child_ctx in self.common.child_iter() { - assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); - - do child_ctx.with_mut_base |child_node| { - min_width = geometry::max(min_width, child_node.min_width); - pref_width = geometry::max(pref_width, child_node.pref_width); - - num_floats = num_floats + child_node.num_floats; - } - } - - /* if not an anonymous block context, add in block box's widths. - these widths will not include child elements, just padding etc. */ - for box in self.box.iter() { - min_width = min_width.add(&box.get_min_width(ctx)); - pref_width = pref_width.add(&box.get_pref_width(ctx)); - } - self.common.min_width = min_width; - self.common.pref_width = pref_width; - self.common.num_floats = num_floats; - } - - /// Computes left and right margins and width based on CSS 2.1 secion 10.3.3. - /// Requires borders and padding to already be computed - fn compute_horiz( &self, - width: MaybeAuto, - left_margin: MaybeAuto, - right_margin: MaybeAuto, - available_width: Au) -> (Au, Au, Au) { - - //If width is not 'auto', and width + margins > available_width, all 'auto' margins are treated as '0' - let (left_margin, right_margin) = match width{ + /// Computes left and right margins and width based on CSS 2.1 section 10.3.3. + /// Requires borders and padding to already be computed. + fn compute_horiz(&self, + width: MaybeAuto, + left_margin: MaybeAuto, + right_margin: MaybeAuto, + available_width: Au) + -> (Au, Au, Au) { + // If width is not 'auto', and width + margins > available_width, all 'auto' margins are + // treated as 0. + let (left_margin, right_margin) = match width { Auto => (left_margin, right_margin), Specified(width) => { let left = left_margin.specified_or_zero(); @@ -152,9 +92,9 @@ impl BlockFlowData { (Specified(margin_l), Specified(width), Auto) => (margin_l, width, available_width - (margin_l + width)), //If width is set to 'auto', any other 'auto' value becomes '0', and width is solved for - (Auto, Auto, Specified(margin_r)) => (Au(0), available_width - margin_r, margin_r), - (Specified(margin_l), Auto, Auto) => (margin_l, available_width - margin_l, Au(0)), - (Auto, Auto, Auto) => (Au(0), available_width, Au(0)), + (Auto, Auto, Specified(margin_r)) => (Au::new(0), available_width - margin_r, margin_r), + (Specified(margin_l), Auto, Auto) => (margin_l, available_width - margin_l, Au::new(0)), + (Auto, Auto, Auto) => (Au::new(0), available_width, Au::new(0)), //If left and right margins are auto, they become equal (Auto, Specified(width), Auto) => { @@ -167,126 +107,32 @@ impl BlockFlowData { (width_Au, left_margin_Au, right_margin_Au) } - /// Recursively (top-down) determines the actual width of child contexts and boxes. When called - /// on this context, the context has had its width set by the parent context. - /// - /// Dual boxes consume some width first, and the remainder is assigned to all child (block) - /// contexts. - pub fn assign_widths_block(&mut self, ctx: &LayoutContext) { - debug!("assign_widths_block: assigning width for flow %?", self.common.id); - if self.is_root { - debug!("Setting root position"); - self.common.position.origin = Au::zero_point(); - self.common.position.size.width = ctx.screen_size.size.width; - self.common.floats_in = FloatContext::new(self.common.num_floats); - self.common.is_inorder = false; - } - - //position was set to the containing block by the flow's parent - let mut remaining_width = self.common.position.size.width; - let mut x_offset = Au(0); - - for &box in self.box.iter() { - let style = box.style(); - do box.with_model |model| { - //Can compute border width here since it doesn't depend on anything - model.compute_borders(style); - - // Can compute padding here since we know containing block width. - model.compute_padding(style, remaining_width); - - // Margins are 0 right now so model.noncontent_width() is just borders + padding. - let available_width = remaining_width - model.noncontent_width(); - - // Top and bottom margins for blocks are 0 if auto. - let margin_top = MaybeAuto::from_style(style.Margin.margin_top, - remaining_width).specified_or_zero(); - let margin_bottom = MaybeAuto::from_style(style.Margin.margin_bottom, - remaining_width).specified_or_zero(); - - let (width, margin_left, margin_right) = - (MaybeAuto::from_style(style.Box.width, remaining_width), - MaybeAuto::from_style(style.Margin.margin_left, remaining_width), - MaybeAuto::from_style(style.Margin.margin_right, remaining_width)); - - let (width, margin_left, margin_right) = self.compute_horiz(width, - margin_left, - margin_right, - available_width); - - model.margin.top = margin_top; - model.margin.right = margin_right; - model.margin.bottom = margin_bottom; - model.margin.left = margin_left; - - x_offset = model.offset(); - remaining_width = width; - } - - do box.with_mut_base |base| { - //The associated box is the border box of this flow - base.position.origin.x = base.model.margin.left; - - let pb = base.model.padding.left + base.model.padding.right + - base.model.border.left + base.model.border.right; - base.position.size.width = remaining_width + pb; - } - } - - let has_inorder_children = self.common.is_inorder || self.common.num_floats > 0; - for kid in self.common.child_iter() { - assert!(kid.starts_block_flow() || kid.starts_inline_flow()); - - do kid.with_mut_base |child_node| { - child_node.position.origin.x = x_offset; - child_node.position.size.width = remaining_width; - child_node.is_inorder = has_inorder_children; - - if !child_node.is_inorder { - child_node.floats_in = FloatContext::new(0); - } - } - } - } - - pub fn assign_height_inorder_block(&mut self, ctx: &mut LayoutContext) { - debug!("assign_height_inorder_block: assigning height for block %?", self.common.id); - self.assign_height_block_base(ctx, true); - } - - pub fn assign_height_block(&mut self, ctx: &mut LayoutContext) { - debug!("assign_height_block: assigning height for block %?", self.common.id); - // This is the only case in which a block flow can start an inorder - // subtraversal. - if self.is_root && self.common.num_floats > 0 { - self.assign_height_inorder_block(ctx); - return; - } - self.assign_height_block_base(ctx, false); - } - + // 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) { - let mut cur_y = Au(0); - let mut clearance = Au(0); - let mut top_offset = Au(0); - let mut bottom_offset = Au(0); - let mut left_offset = Au(0); + let mut cur_y = Au::new(0); + let mut clearance = Au::new(0); + let mut top_offset = Au::new(0); + let mut bottom_offset = Au::new(0); + let mut left_offset = Au::new(0); let mut float_ctx = Invalid; for &box in self.box.iter() { - clearance = match box.clear() { - None => Au(0), + let base = box.base(); + clearance = match base.clear() { + None => Au::new(0), Some(clear) => { - self.common.floats_in.clearance(clear) + self.base.floats_in.clearance(clear) } }; - do box.with_model |model| { - top_offset = clearance + model.margin.top + model.border.top + model.padding.top; - cur_y = cur_y + top_offset; - bottom_offset = model.margin.bottom + model.border.bottom + model.padding.bottom; - left_offset = model.offset(); - }; + let mut model_ref = base.model.mutate(); + let model = &mut model_ref.ptr; + top_offset = clearance + model.margin.top + model.border.top + model.padding.top; + cur_y = cur_y + top_offset; + bottom_offset = model.margin.bottom + model.border.bottom + model.padding.bottom; + left_offset = model.offset(); } if inorder { @@ -297,84 +143,48 @@ impl BlockFlowData { // visit child[i] // repeat until all children are visited. // last_child.floats_out -> self.floats_out (done at the end of this method) - float_ctx = self.common.floats_in.translate(Point2D(-left_offset, -top_offset)); - for kid in self.common.child_iter() { - do kid.with_mut_base |child_node| { - child_node.floats_in = float_ctx.clone(); - } + float_ctx = self.base.floats_in.translate(Point2D(-left_offset, -top_offset)); + for kid in self.base.child_iter() { + flow::mut_base(*kid).floats_in = float_ctx.clone(); kid.assign_height_inorder(ctx); - do kid.with_mut_base |child_node| { - float_ctx = child_node.floats_out.clone(); - } + float_ctx = flow::mut_base(*kid).floats_out.clone(); } } - let mut collapsible = Au(0); - let mut collapsing = Au(0); - let mut margin_top = Au(0); - let mut margin_bottom = Au(0); + let mut collapsible = Au::new(0); + let mut collapsing = Au::new(0); + let mut margin_top = Au::new(0); + let mut margin_bottom = Au::new(0); let mut top_margin_collapsible = false; let mut bottom_margin_collapsible = false; - let mut first_inflow = true; + let mut first_in_flow = true; for &box in self.box.iter() { - do box.with_model |model| { - if !self.is_root && model.border.top == Au(0) && model.padding.top == Au(0) { - collapsible = model.margin.top; - top_margin_collapsible = true; - } - if !self.is_root && model.border.bottom == Au(0) && model.padding.bottom == Au(0) { - bottom_margin_collapsible = true; - } - margin_top = model.margin.top; - margin_bottom = model.margin.bottom; + let base = box.base(); + let mut model_ref = base.model.mutate(); + let model = &mut model_ref.ptr; + if !self.is_root && model.border.top == Au::new(0) && model.padding.top == Au::new(0) { + collapsible = model.margin.top; + top_margin_collapsible = true; } - } - - for kid in self.common.child_iter() { - match *kid { - BlockFlow(ref info) => { - for &box in info.box.iter() { - do box.with_model |model| { - // The top margin collapses with its first in-flow block-level child's - // top margin if the parent has no top boder, no top padding. - if first_inflow && 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 < model.margin.top { - // TODO: The position of child floats should be updated and this - // would influence clearance as well. See #725 - let extra_margin = model.margin.top - margin_top; - top_offset = top_offset + extra_margin; - margin_top = model.margin.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(model.margin.top, collapsible); - collapsible = model.margin.bottom; - } - } - first_inflow = false; - } - InlineFlow(ref info) => { - collapsing = Au(0); - // Non-empty inline flows prevent collapsing between the previous margion and the next. - if info.common.position.size.height > Au(0) { - collapsible = Au(0); - } - } - // Margins between a floated box and any other box do not collapse. - _ => { - collapsing = Au(0); - } - // TODO: Handling for AbsoluteFlow, InlineBlockFlow and TableFlow? + if !self.is_root && model.border.bottom == Au::new(0) && model.padding.bottom == Au::new(0) { + bottom_margin_collapsible = true; } + margin_top = model.margin.top; + margin_bottom = model.margin.bottom; + } - do kid.with_mut_base |child_node| { - cur_y = cur_y - collapsing; - child_node.position.origin.y = cur_y; - cur_y = cur_y + child_node.position.size.height; - }; + for kid in self.base.child_iter() { + kid.collapse_margins(top_margin_collapsible, + &mut first_in_flow, + &mut margin_top, + &mut top_offset, + &mut collapsing, + &mut collapsible); + + let child_node = flow::mut_base(*kid); + cur_y = cur_y - collapsing; + child_node.position.origin.y = cur_y; + cur_y = cur_y + child_node.position.size.height; } // The bottom margin collapses with its last in-flow block-level child's bottom margin @@ -385,7 +195,7 @@ impl BlockFlowData { } collapsible } else { - Au(0) + Au::new(0) }; // TODO: A box's own margins collapse if the 'min-height' property is zero, and it has neither @@ -400,61 +210,67 @@ impl BlockFlowData { }; for &box in self.box.iter() { - let style = box.style(); - let maybe_height = MaybeAuto::from_style(style.Box.height, Au(0)); + let base = box.base(); + let style = base.style(); + let maybe_height = MaybeAuto::from_style(style.Box.height, Au::new(0)); let maybe_height = maybe_height.specified_or_zero(); height = geometry::max(height, maybe_height); } - let mut noncontent_height = Au(0); - for box in self.box.mut_iter() { - do box.with_mut_base |base| { - //The associated box is the border box of this flow - base.model.margin.top = margin_top; - base.model.margin.bottom = margin_bottom; + let mut noncontent_height = Au::new(0); + for box in self.box.iter() { + let base = box.base(); + let mut model_ref = base.model.mutate(); + let mut position_ref = base.position.mutate(); + let (model, position) = (&mut model_ref.ptr, &mut position_ref.ptr); + + // The associated box is the border box of this flow. + model.margin.top = margin_top; + model.margin.bottom = margin_bottom; - base.position.origin.y = clearance + base.model.margin.top; + position.origin.y = clearance + model.margin.top; - noncontent_height = base.model.padding.top + base.model.padding.bottom + - base.model.border.top + base.model.border.bottom; - base.position.size.height = height + noncontent_height; + noncontent_height = model.padding.top + model.padding.bottom + model.border.top + + model.border.bottom; + position.size.height = height + noncontent_height; - noncontent_height = noncontent_height + clearance + - base.model.margin.top + base.model.margin.bottom; - } + noncontent_height = noncontent_height + clearance + model.margin.top + + model.margin.bottom; } //TODO(eatkinson): compute heights using the 'height' property. - self.common.position.size.height = height + noncontent_height; + self.base.position.size.height = height + noncontent_height; if inorder { let extra_height = height - (cur_y - top_offset) + bottom_offset; - self.common.floats_out = float_ctx.translate(Point2D(left_offset, -extra_height)); + self.base.floats_out = float_ctx.translate(Point2D(left_offset, -extra_height)); } else { - self.common.floats_out = self.common.floats_in.clone(); + self.base.floats_out = self.base.floats_in.clone(); } } - pub fn build_display_list_block<E:ExtraDisplayListData>(&mut self, - builder: &DisplayListBuilder, - dirty: &Rect<Au>, - list: &Cell<DisplayList<E>>) - -> bool { - - if self.common.node.is_iframe_element() { - let x = self.common.abs_position.x + do self.box.as_ref().map_default(Au(0)) |box| { - box.with_model(|model| model.margin.left + model.border.left + model.padding.left) + pub fn build_display_list_block<E:ExtraDisplayListData>( + &mut self, + builder: &DisplayListBuilder, + dirty: &Rect<Au>, + list: &Cell<DisplayList<E>>) + -> bool { + if self.base.node.is_iframe_element() { + let x = self.base.abs_position.x + do self.box.map_default(Au::new(0)) |box| { + let model = box.base().model.get(); + model.margin.left + model.border.left + model.padding.left }; - let y = self.common.abs_position.y + do self.box.as_ref().map_default(Au(0)) |box| { - box.with_model(|model| model.margin.top + model.border.top + model.padding.top) + let y = self.base.abs_position.y + do self.box.map_default(Au::new(0)) |box| { + let model = box.base().model.get(); + model.margin.top + model.border.top + model.padding.top }; - let w = self.common.position.size.width - do self.box.as_ref().map_default(Au(0)) |box| { - box.with_model(|model| model.noncontent_width()) + let w = self.base.position.size.width - do self.box.map_default(Au::new(0)) |box| { + box.base().model.get().noncontent_width() }; - let h = self.common.position.size.height - do self.box.as_ref().map_default(Au(0)) |box| { - box.with_model(|model| model.noncontent_height()) + let h = self.base.position.size.height - do self.box.map_default(Au::new(0)) |box| { + box.base().model.get().noncontent_height() }; - do self.common.node.with_mut_iframe_element |iframe_element| { + do self.base.node.with_mut_iframe_element |iframe_element| { iframe_element.size.get_mut_ref().set_rect(Rect(Point2D(to_frac_px(x) as f32, to_frac_px(y) as f32), Size2D(to_frac_px(w) as f32, @@ -462,7 +278,7 @@ impl BlockFlowData { } } - let abs_rect = Rect(self.common.abs_position, self.common.position.size); + let abs_rect = Rect(self.base.abs_position, self.base.position.size); if !abs_rect.intersects(dirty) { return true; } @@ -471,18 +287,199 @@ impl BlockFlowData { // add box that starts block context for box in self.box.iter() { - box.build_display_list(builder, dirty, &self.common.abs_position, list) + box.build_display_list(builder, dirty, &self.base.abs_position, list) } // TODO: handle any out-of-flow elements - let this_position = self.common.abs_position; - for child in self.common.child_iter() { - do child.with_mut_base |base| { - base.abs_position = this_position + base.position.origin; - } + 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; } false } } +impl FlowContext for BlockFlow { + fn class(&self) -> FlowClass { + BlockFlowClass + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + self + } + + /* Recursively (bottom-up) determine 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. */ + + /* TODO: floats */ + /* 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 */ + for child_ctx in self.base.child_iter() { + assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); + + 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); + + num_floats = num_floats + child_base.num_floats; + } + + /* if not an anonymous block context, add in block box's widths. + these widths will not include child elements, just padding etc. */ + for box in self.box.iter() { + { + // Can compute border width here since it doesn't depend on anything. + let base = box.base(); + base.model.mutate().ptr.compute_borders(base.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; + } + + self.base.min_width = min_width; + self.base.pref_width = pref_width; + self.base.num_floats = num_floats; + } + + /// Recursively (top-down) determines the actual width of child contexts and boxes. When called + /// on this context, the context has had its width set by the parent context. + /// + /// Dual boxes consume some width first, and the remainder is assigned to all child (block) + /// contexts. + fn assign_widths(&mut self, ctx: &mut LayoutContext) { + debug!("assign_widths_block: assigning width for flow %?", self.base.id); + if self.is_root { + debug!("Setting root position"); + self.base.position.origin = Au::zero_point(); + self.base.position.size.width = ctx.screen_size.size.width; + self.base.floats_in = FloatContext::new(self.base.num_floats); + self.base.is_inorder = false; + } + + //position was set to the containing block by the flow's parent + let mut remaining_width = self.base.position.size.width; + let mut x_offset = Au::new(0); + + for &box in self.box.iter() { + let base = box.base(); + let style = base.style(); + let mut model_ref = base.model.mutate(); + let model = &mut model_ref.ptr; + + // Can compute padding here since we know containing block width. + model.compute_padding(style, remaining_width); + + // Margins are 0 right now so model.noncontent_width() is just borders + padding. + let available_width = remaining_width - model.noncontent_width(); + + // Top and bottom margins for blocks are 0 if auto. + let margin_top = MaybeAuto::from_style(style.Margin.margin_top, + remaining_width).specified_or_zero(); + let margin_bottom = MaybeAuto::from_style(style.Margin.margin_bottom, + remaining_width).specified_or_zero(); + + let (width, margin_left, margin_right) = + (MaybeAuto::from_style(style.Box.width, remaining_width), + MaybeAuto::from_style(style.Margin.margin_left, remaining_width), + MaybeAuto::from_style(style.Margin.margin_right, remaining_width)); + + let (width, margin_left, margin_right) = self.compute_horiz(width, + margin_left, + margin_right, + available_width); + + model.margin.top = margin_top; + model.margin.right = margin_right; + model.margin.bottom = margin_bottom; + model.margin.left = margin_left; + + x_offset = model.offset(); + remaining_width = width; + + //The associated box is the border box of this flow + let position_ref = base.position.mutate(); + position_ref.ptr.origin.x = model.margin.left; + let padding_and_borders = model.padding.left + model.padding.right + + model.border.left + model.border.right; + position_ref.ptr.size.width = remaining_width + padding_and_borders; + } + + let has_inorder_children = self.base.is_inorder || self.base.num_floats > 0; + for kid in self.base.child_iter() { + assert!(kid.starts_block_flow() || kid.starts_inline_flow()); + + let child_base = flow::mut_base(*kid); + child_base.position.origin.x = x_offset; + child_base.position.size.width = remaining_width; + child_base.is_inorder = has_inorder_children; + + if !child_base.is_inorder { + child_base.floats_in = FloatContext::new(0); + } + } + } + + fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height_inorder: assigning height for block %?", self.base.id); + self.assign_height_block_base(ctx, true); + } + + fn assign_height(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height: assigning height for block %?", self.base.id); + // This is the only case in which a block flow can start an inorder + // subtraversal. + if self.is_root && self.base.num_floats > 0 { + self.assign_height_inorder(ctx); + return; + } + self.assign_height_block_base(ctx, false); + } + + 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) { + for &box in self.box.iter() { + let base = box.base(); + let mut model_ref = base.model.mutate(); + let model = &mut model_ref.ptr; + + // 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 < model.margin.top { + // TODO: The position of child floats should be updated and this + // would influence clearance as well. See #725 + let extra_margin = model.margin.top - *margin_top; + *top_offset = *top_offset + extra_margin; + *margin_top = model.margin.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(model.margin.top, *collapsible); + *collapsible = model.margin.bottom; + } + + *first_in_flow = false; + } +} + diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index 012fd5b681f..364b6c03f70 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -4,26 +4,27 @@ //! The `RenderBox` type, which represents the leaves of the layout tree. -use css::node_style::StyledNode; -use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor}; -use layout::float_context::{ClearType, ClearLeft, ClearRight, ClearBoth}; -use layout::model::{BoxModel, MaybeAuto}; -use layout::text; - -use std::cell::Cell; -use std::cmp::ApproxEq; -use std::managed; -use std::num::Zero; +use extra::url::Url; use geom::{Point2D, Rect, Size2D, SideOffsets2D}; use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass}; use gfx::display_list::{DisplayList, ImageDisplayItem, ImageDisplayItemClass}; use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, TextDisplayItem}; use gfx::display_list::{TextDisplayItemClass}; use gfx::font::{FontStyle, FontWeight300}; -use servo_util::geometry::Au; use gfx::text::text_run::TextRun; use gfx::color::rgb; +use script::dom::node::{AbstractNode, LayoutView}; +use servo_net::image::holder::ImageHolder; +use servo_net::local_image_cache::LocalImageCache; +use servo_util::geometry::Au; +use servo_util::range::*; +use servo_util::slot::Slot; +use servo_util::tree::{TreeNodeRef, ElementLike}; +use std::cast; +use std::cell::Cell; +use std::cmp::ApproxEq; +use std::num::Zero; +use std::unstable::raw::Box; use style::ComputedValues; use style::computed_values::border_style; use style::computed_values::clear; @@ -35,19 +36,17 @@ use style::computed_values::position; use style::computed_values::text_align; use style::computed_values::text_decoration; use style::computed_values::vertical_align; -use script::dom::node::{AbstractNode, LayoutView}; -use servo_net::image::holder::ImageHolder; -use servo_net::local_image_cache::LocalImageCache; -use servo_util::range::*; -use servo_util::tree::{TreeNodeRef, ElementLike}; -use extra::url::Url; -/// Render boxes (`struct RenderBox`) are the leaves of the layout tree. They cannot position -/// themselves. In general, render boxes do not have a simple correspondence with CSS boxes as in -/// the specification: +use css::node_style::StyledNode; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor}; +use layout::float_context::{ClearType, ClearLeft, ClearRight, ClearBoth}; +use layout::model::{BoxModel, MaybeAuto}; + +/// 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: /// -/// * Several render boxes may correspond to the same CSS box or DOM node. For example, a CSS text -/// box broken across two lines is represented by two render boxes. +/// * Several boxes may correspond to the same CSS box or DOM node. For example, a CSS text box +/// broken across two lines is represented by two boxes. /// /// * Some CSS boxes are not created at all, such as some anonymous block boxes induced by inline /// boxes with block-level sibling boxes. In that case, Servo uses an `InlineFlow` with @@ -61,27 +60,168 @@ use extra::url::Url; /// A box's type influences how its styles are interpreted during layout. For example, replaced /// content such as images are resized differently from tables, text, or other content. Different /// types of boxes may also contain custom data; for example, text boxes contain text. -#[deriving(Clone)] -pub enum RenderBox { - GenericRenderBoxClass(@mut RenderBoxBase), - ImageRenderBoxClass(@mut ImageRenderBox), - TextRenderBoxClass(@mut TextRenderBox), - UnscannedTextRenderBoxClass(@mut UnscannedTextRenderBox), +pub trait RenderBox { + /// Returns the class of render box that this is. + fn class(&self) -> RenderBoxClass; + + /// If this is an image render box, returns the underlying object. Fails otherwise. + /// + /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. + fn as_image_render_box(@self) -> @ImageRenderBox { + fail!("as_text_render_box() called on a non-text-render-box") + } + + /// If this is a text render box, returns the underlying object. Fails otherwise. + /// + /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. + fn as_text_render_box(@self) -> @TextRenderBox { + fail!("as_text_render_box() called on a non-text-render-box") + } + + /// If this is an unscanned text render box, returns the underlying object. Fails otherwise. + /// + /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. + fn as_unscanned_text_render_box(@self) -> @UnscannedTextRenderBox { + fail!("as_unscanned_text_render_box() called on a non-unscanned-text-render-box") + } + + /// Cleans up all memory associated with this render box. + fn teardown(&self) {} + + /// Returns true if this element is an unscanned text box that consists entirely of whitespace. + fn is_whitespace_only(&self) -> bool { + false + } + + /// Attempts to split this box so that its width is no more than `max_width`. Fails if this box + /// is an unscanned text box. + fn split_to_width(@self, _: Au, _: bool) -> SplitBoxResult; + + /// Determines whether this box can merge with another box. + fn can_merge_with_box(&self, _: &RenderBox) -> bool { + false + } + + /// Returns the *minimum width* and *preferred width* of this render box as defined by CSS 2.1. + fn minimum_and_preferred_widths(&self) -> (Au, Au); + + fn box_height(&self) -> Au; + + /// Assigns the appropriate width. + fn assign_width(&self); + + fn debug_str(&self) -> ~str { + ~"???" + } +} + +impl Clone for @RenderBox { + fn clone(&self) -> @RenderBox { + *self + } } -impl RenderBox { - pub fn teardown(&self) { - match *self { - TextRenderBoxClass(box) => box.teardown(), - _ => () +// FIXME(pcwalton): These are botches and can be removed once Rust gets trait fields. + +pub trait RenderBoxUtils { + fn base<'a>(&'a self) -> &'a RenderBoxBase; + + /// Returns true if this element is replaced content. This is true for images, form elements, + /// and so on. + fn is_replaced(&self) -> bool; + + /// Returns true if this element can be split. This is true for text boxes. + fn can_split(&self) -> bool; + + /// Returns the amount of left and right "fringe" used by this box. This is based on margins, + /// borders, padding, and width. + fn get_used_width(&self) -> (Au, Au); + + /// Returns the amount of left and right "fringe" used by this box. This should be based on + /// margins, borders, padding, and width. + fn get_used_height(&self) -> (Au, Au); + + /// Adds the display items necessary to paint the background of this render box to the display + /// list if necessary. + fn paint_background_if_applicable<E:ExtraDisplayListData>( + &self, + list: &Cell<DisplayList<E>>, + absolute_bounds: &Rect<Au>); + + /// Adds the display items necessary to paint the borders of this render box to a display list + /// if necessary. + fn paint_borders_if_applicable<E:ExtraDisplayListData>( + &self, + list: &Cell<DisplayList<E>>, + abs_bounds: &Rect<Au>); + + /// Adds the display items for this render box to the given display list. + /// + /// Arguments: + /// * `builder`: The display list builder, which manages the coordinate system and options. + /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow. + /// * `origin`: The total offset from the display list root flow to the owning flow of this + /// 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. + fn build_display_list<E:ExtraDisplayListData>( + &self, + _: &DisplayListBuilder, + dirty: &Rect<Au>, + offset: &Point2D<Au>, + list: &Cell<DisplayList<E>>); +} + +pub trait RenderBoxRefUtils<'self> { + fn base(self) -> &'self RenderBoxBase; +} + +/// A box that represents a generic render box. +pub struct GenericRenderBox { + base: RenderBoxBase, +} + +impl GenericRenderBox { + pub fn new(base: RenderBoxBase) -> GenericRenderBox { + GenericRenderBox { + base: base, } } } +impl RenderBox for GenericRenderBox { + fn class(&self) -> RenderBoxClass { + GenericRenderBoxClass + } + + fn minimum_and_preferred_widths(&self) -> (Au, Au) { + let guessed_width = self.base.guess_width(); + (guessed_width, guessed_width) + } + + fn split_to_width(@self, _: Au, _: bool) -> SplitBoxResult { + CannotSplit(self as @RenderBox) + } + + fn box_height(&self) -> Au { + Au::new(0) + } + + fn assign_width(&self) { + // FIXME(pcwalton): This seems clownshoes; can we remove? + self.base.position.mutate().ptr.size.width = Au::from_px(45) + } +} + /// A box that represents a (replaced content) image and its accompanying borders, shadows, etc. pub struct ImageRenderBox { base: RenderBoxBase, - image: ImageHolder, + image: Slot<ImageHolder>, } impl ImageRenderBox { @@ -91,13 +231,104 @@ impl ImageRenderBox { ImageRenderBox { base: base, - image: ImageHolder::new(image_url, local_image_cache), + image: Slot::init(ImageHolder::new(image_url, local_image_cache)), } } + + // Calculate the width of an image, accounting for the width attribute + // TODO: This could probably go somewhere else + pub fn image_width(&self) -> Au { + let attr_width: Option<int> = do self.base.node.with_imm_element |elt| { + match elt.get_attr("width") { + Some(width) => { + FromStr::from_str(width) + } + None => { + None + } + } + }; + + // TODO: Consult margins and borders? + let px_width = if attr_width.is_some() { + attr_width.unwrap() + } else { + self.image.mutate().ptr.get_size().unwrap_or(Size2D(0, 0)).width + }; + + Au::from_px(px_width) + } + + // Calculate the height of an image, accounting for the height attribute + // TODO: This could probably go somewhere else + pub fn image_height(&self) -> Au { + let attr_height: Option<int> = do self.base.node.with_imm_element |elt| { + match elt.get_attr("height") { + Some(height) => { + FromStr::from_str(height) + } + None => { + None + } + } + }; + + // TODO: Consult margins and borders? + let px_height = if attr_height.is_some() { + attr_height.unwrap() + } else { + self.image.mutate().ptr.get_size().unwrap_or(Size2D(0, 0)).height + }; + + Au::from_px(px_height) + } + + /// If this is an image render box, returns the underlying object. Fails otherwise. + /// + /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. + fn as_image_render_box(@self) -> @ImageRenderBox { + self + } +} + +impl RenderBox for ImageRenderBox { + fn class(&self) -> RenderBoxClass { + ImageRenderBoxClass + } + + fn split_to_width(@self, _: Au, _: bool) -> SplitBoxResult { + CannotSplit(self as @RenderBox) + } + + fn minimum_and_preferred_widths(&self) -> (Au, Au) { + let guessed_width = self.base.guess_width(); + let image_width = self.image_width(); + (guessed_width + image_width, guessed_width + image_width) + } + + fn box_height(&self) -> Au { + let size = self.image.mutate().ptr.get_size(); + let height = Au::from_px(size.unwrap_or(Size2D(0, 0)).height); + self.base.position.mutate().ptr.size.height = height; + debug!("box_height: found image height: %?", height); + height + } + + fn assign_width(&self) { + let width = self.image_width(); + self.base.position.mutate().ptr.size.width = width; + } + + /// If this is an image render box, returns the underlying object. Fails otherwise. + /// + /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. + fn as_image_render_box(@self) -> @ImageRenderBox { + self + } } /// A box representing a single run of text with a distinct style. A `TextRenderBox` may be split -/// into two or more render boxes across line breaks. Several `TextBox`es may correspond to a +/// into two or more boxes across line breaks. Several `TextBox`es may correspond to a /// single DOM text node. Split text boxes are implemented by referring to subsets of a master /// `TextRun` object. pub struct TextRenderBox { @@ -107,9 +338,168 @@ pub struct TextRenderBox { } impl TextRenderBox { + /// Creates a TextRenderBox from a base render box, a range, and a text run. The size of the + /// the base render box is ignored and becomes the size of the text run. + /// + /// FIXME(pcwalton): This API is confusing. + pub fn new(base: RenderBoxBase, run: @TextRun, range: Range) -> TextRenderBox { + debug!("Creating textbox with span: (strlen=%u, off=%u, len=%u) of textrun (%s) (len=%u)", + run.char_len(), + range.begin(), + range.length(), + *run.text.get(), + run.char_len()); + + assert!(range.begin() < run.char_len()); + assert!(range.end() <= run.char_len()); + assert!(range.length() > 0); + + let metrics = run.metrics_for_range(&range); + + // FIXME(pcwalton): This block is necessary due to Rust #6248. If we don't have it, then + // the "currently borrowed" flag will be moved before the destructor runs, causing a + // (harmless) undefined memory write and a (very harmful) sticking of `position` in the + // "mutably borrowed" state, which will cause failures later. + { + base.position.mutate().ptr.size = metrics.bounding_box.size; + } + + TextRenderBox { + base: base, + run: run, + range: range, + } + } +} + +impl RenderBox for TextRenderBox { + fn class(&self) -> RenderBoxClass { + TextRenderBoxClass + } + + fn as_text_render_box(@self) -> @TextRenderBox { + self + } + fn teardown(&self) { self.run.teardown(); } + + fn minimum_and_preferred_widths(&self) -> (Au, Au) { + let guessed_width = self.base.guess_width(); + let min_width = self.run.min_width_for_range(&self.range); + + let mut max_line_width = Au::new(0); + for line_range in self.run.iter_natural_lines_for_range(&self.range) { + let line_metrics = self.run.metrics_for_range(&line_range); + max_line_width = Au::max(max_line_width, line_metrics.advance_width); + } + + (guessed_width + min_width, guessed_width + max_line_width) + } + + fn box_height(&self) -> Au { + let range = &self.range; + let run = &self.run; + + // Compute the height based on the line-height and font size + let text_bounds = run.metrics_for_range(range).bounding_box; + let em_size = text_bounds.size.height; + let line_height = self.base.calculate_line_height(em_size); + + line_height + } + + fn assign_width(&self) { + // Text boxes are preinitialized. + } + + /// Attempts to split this box so that its width is no more than `max_width`. Fails if this box + /// is an unscanned text box. + fn split_to_width(@self, max_width: Au, starts_line: bool) -> SplitBoxResult { + let mut pieces_processed_count: uint = 0; + let mut remaining_width: Au = max_width; + let mut left_range = Range::new(self.range.begin(), 0); + let mut right_range: Option<Range> = None; + + debug!("split_to_width: splitting text box (strlen=%u, range=%?, avail_width=%?)", + self.run.text.get().len(), + self.range, + max_width); + + for (glyphs, offset, slice_range) in self.run.iter_slices_for_range(&self.range) { + debug!("split_to_width: considering slice (offset=%?, range=%?, remain_width=%?)", + offset, + slice_range, + remaining_width); + + let metrics = self.run.metrics_for_slice(glyphs, &slice_range); + let advance = metrics.advance_width; + let should_continue: bool; + + if advance <= remaining_width { + should_continue = true; + + if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() { + debug!("split_to_width: case=skipping leading trimmable whitespace"); + left_range.shift_by(slice_range.length() as int); + } else { + debug!("split_to_width: case=enlarging span"); + remaining_width = remaining_width - advance; + left_range.extend_by(slice_range.length() as int); + } + } else { // The advance is more than the remaining width. + should_continue = false; + let slice_begin = offset + slice_range.begin(); + let slice_end = offset + slice_range.end(); + + if glyphs.is_whitespace() { + // If there are still things after the trimmable whitespace, create the + // right chunk. + if slice_end < self.range.end() { + debug!("split_to_width: case=skipping trimmable trailing \ + whitespace, then split remainder"); + let right_range_end = self.range.end() - slice_end; + right_range = Some(Range::new(slice_end, right_range_end)); + } else { + debug!("split_to_width: case=skipping trimmable trailing \ + whitespace"); + } + } else if slice_begin < self.range.end() { + // There are still some things left over at the end of the line. Create + // the right chunk. + let right_range_end = self.range.end() - slice_begin; + right_range = Some(Range::new(slice_begin, right_range_end)); + debug!("split_to_width: case=splitting remainder with right range=%?", + right_range); + } + } + + pieces_processed_count += 1; + + if !should_continue { + break + } + } + + let left_box = if left_range.length() > 0 { + let new_text_box = @TextRenderBox::new(self.base.clone(), self.run, left_range); + Some(new_text_box as @RenderBox) + } else { + None + }; + + let right_box = do right_range.map_default(None) |range: Range| { + let new_text_box = @TextRenderBox::new(self.base.clone(), self.run, range); + Some(new_text_box as @RenderBox) + }; + + if pieces_processed_count == 1 || left_box.is_none() { + SplitDidNotFit(left_box, right_box) + } else { + SplitDidFit(left_box, right_box) + } + } } /// The data for an unscanned text box. @@ -133,40 +523,94 @@ impl UnscannedTextRenderBox { // FIXME(pcwalton): If we're just looking at node data, do we have to ensure this is // a text node? UnscannedTextRenderBox { - base: base, + base: base.clone(), text: text_node.element.data.to_str(), font_style: None, text_decoration: None, } } } + + /// Copies out the text from an unscanned text box. + pub fn raw_text(&self) -> ~str { + self.text.clone() + } } -pub enum RenderBoxType { - RenderBox_Generic, - RenderBox_Image, - RenderBox_Text, +impl RenderBox for UnscannedTextRenderBox { + fn class(&self) -> RenderBoxClass { + UnscannedTextRenderBoxClass + } + + fn is_whitespace_only(&self) -> bool { + self.text.is_whitespace() + } + + fn can_merge_with_box(&self, other: &RenderBox) -> bool { + if other.class() == UnscannedTextRenderBoxClass { + let this_base = &self.base; + let other_base = other.base(); + return this_base.font_style() == other_base.font_style() && + this_base.text_decoration() == other_base.text_decoration() + } + false + } + + fn box_height(&self) -> Au { + fail!("can't get height of unscanned text box") + } + + /// Attempts to split this box so that its width is no more than `max_width`. Fails if this box + /// is an unscanned text box. + fn split_to_width(@self, _: Au, _: bool) -> SplitBoxResult { + fail!("WAT: shouldn't be an unscanned text box here.") + } + + /// Returns the *minimum width* and *preferred width* of this render box as defined by CSS 2.1. + fn minimum_and_preferred_widths(&self) -> (Au, Au) { + fail!("WAT: shouldn't be an unscanned text box here.") + } + + fn assign_width(&self) { + fail!("WAT: shouldn't be an unscanned text box here.") + } + + /// If this is an unscanned text render box, returns the underlying object. Fails otherwise. + /// + /// FIXME(pcwalton): Ugly. Replace with a real downcast operation. + fn as_unscanned_text_render_box(@self) -> @UnscannedTextRenderBox { + self + } } -/// Represents the outcome of attempting to split a render box. +#[deriving(Eq)] +pub enum RenderBoxClass { + GenericRenderBoxClass, + ImageRenderBoxClass, + TextRenderBoxClass, + UnscannedTextRenderBoxClass, +} + +/// Represents the outcome of attempting to split a box. pub enum SplitBoxResult { - CannotSplit(RenderBox), + CannotSplit(@RenderBox), // in general, when splitting the left or right side can // be zero length, due to leading/trailing trimmable whitespace - SplitDidFit(Option<RenderBox>, Option<RenderBox>), - SplitDidNotFit(Option<RenderBox>, Option<RenderBox>) + SplitDidFit(Option<@RenderBox>, Option<@RenderBox>), + SplitDidNotFit(Option<@RenderBox>, Option<@RenderBox>) } -/// Data common to all render boxes. +/// Data common to all boxes. +#[deriving(Clone)] pub struct RenderBoxBase { /// The DOM node that this `RenderBox` originates from. node: AbstractNode<LayoutView>, /// The position of this box relative to its owning flow. - position: Rect<Au>, + position: Slot<Rect<Au>>, /// The core parameters (border, padding, margin) used by the box model. - model: BoxModel, + model: Slot<BoxModel>, /// A debug ID. /// @@ -180,442 +624,307 @@ impl RenderBoxBase { -> RenderBoxBase { RenderBoxBase { node: node, - position: Au::zero_rect(), - model: Zero::zero(), + position: Slot::init(Au::zero_rect()), + model: Slot::init(Zero::zero()), id: id, } } -} -impl RenderBox { - /// Borrows this render box immutably in order to work with its common data. - #[inline(always)] - pub fn with_base<R>(&self, callback: &fn(&RenderBoxBase) -> R) -> R { - match *self { - GenericRenderBoxClass(generic_box) => callback(generic_box), - ImageRenderBoxClass(image_box) => { - callback(&image_box.base) - } - TextRenderBoxClass(text_box) => { - callback(&text_box.base) - } - UnscannedTextRenderBoxClass(unscanned_text_box) => { - callback(&unscanned_text_box.base) - } - } + pub fn id(&self) -> int { + 0 } - /// Borrows this render box mutably in order to work with its common data. - #[inline(always)] - pub fn with_mut_base<R>(&self, callback: &fn(&mut RenderBoxBase) -> R) -> R { - match *self { - GenericRenderBoxClass(generic_box) => callback(generic_box), - ImageRenderBoxClass(image_box) => { - callback(&mut image_box.base) - } - TextRenderBoxClass(text_box) => { - callback(&mut text_box.base) - } - UnscannedTextRenderBoxClass(unscanned_text_box) => { - callback(&mut unscanned_text_box.base) - } + fn guess_width(&self) -> Au { + if !self.node.is_element() { + return Au(0) } + + let style = self.style(); + let width = MaybeAuto::from_style(style.Box.width, Au::new(0)).specified_or_zero(); + let margin_left = MaybeAuto::from_style(style.Margin.margin_left, + Au::new(0)).specified_or_zero(); + let margin_right = MaybeAuto::from_style(style.Margin.margin_right, + Au::new(0)).specified_or_zero(); + + let mut model_ref = self.model.mutate(); + let model = &mut model_ref.ptr; + let padding_left = model.compute_padding_length(style.Padding.padding_left, Au::new(0)); + let padding_right = model.compute_padding_length(style.Padding.padding_right, Au::new(0)); + + width + margin_left + margin_right + padding_left + padding_right + model.border.left + + model.border.right } - /// A convenience function to return the position of this box. - pub fn position(&self) -> Rect<Au> { - do self.with_base |base| { - base.position + pub fn calculate_line_height(&self, font_size: Au) -> Au { + match self.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 } } - /// A convenience function to return the debugging ID of this box. - pub fn id(&self) -> int { - do self.with_mut_base |base| { - base.id - } + pub fn compute_padding(&self, containing_block_width: Au) { + self.model.mutate().ptr.compute_padding(self.node.style(), containing_block_width); } - /// 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 { - ImageRenderBoxClass(*) => true, - _ => false - } + pub fn get_noncontent_width(&self) -> Au { + let model_ref = self.model.mutate(); + model_ref.ptr.border.left + model_ref.ptr.padding.left + model_ref.ptr.border.right + + model_ref.ptr.padding.right } - /// Returns true if this element can be split. This is true for text boxes. - pub fn can_split(&self) -> bool { - match *self { - TextRenderBoxClass(*) => true, - _ => false - } + /// The box formed by the content edge as defined in CSS 2.1 § 8.1. Coordinates are relative to + /// the owning flow. + pub fn content_box(&self) -> Rect<Au> { + let (position, model) = (self.position.get(), self.model.get()); + let origin = Point2D(position.origin.x + model.border.left + model.padding.left, + position.origin.y); + let size = Size2D(position.size.width - self.get_noncontent_width(), position.size.height); + Rect(origin, size) } - /// Returns true if this element is an unscanned text box that consists entirely of whitespace. - pub fn is_whitespace_only(&self) -> bool { - match *self { - UnscannedTextRenderBoxClass(unscanned_text_box) => { - unscanned_text_box.text.is_whitespace() - } - _ => false - } + /// The box formed by the border edge as defined in CSS 2.1 § 8.1. Coordinates are relative to + /// the owning flow. + pub fn border_box(&self) -> Rect<Au> { + // TODO: Actually compute the content box, padding, and border. + self.content_box() } - /// Determines whether this box can merge with another render box. - pub fn can_merge_with_box(&self, other: RenderBox) -> bool { - match (self, &other) { - (&UnscannedTextRenderBoxClass(*), &UnscannedTextRenderBoxClass(*)) => { - self.font_style() == other.font_style() && self.text_decoration() == other.text_decoration() - }, - (&TextRenderBoxClass(text_box_a), &TextRenderBoxClass(text_box_b)) => { - managed::ptr_eq(text_box_a.run, text_box_b.run) + /// The box formed by the margin edge as defined in CSS 2.1 § 8.1. Coordinates are relative to + /// the owning flow. + pub fn margin_box(&self) -> Rect<Au> { + // TODO: Actually compute the content_box, padding, border, and margin. + self.content_box() + } + + /// Returns the nearest ancestor-or-self `Element` to the DOM node that this render box + /// represents. + /// + /// If there is no ancestor-or-self `Element` node, fails. + pub fn nearest_ancestor_element(&self) -> AbstractNode<LayoutView> { + let mut node = self.node; + while !node.is_element() { + match node.parent_node() { + None => fail!("no nearest element?!"), + Some(parent) => node = parent, } - (_, _) => false, } + node } - /// Attempts to split this box so that its width is no more than `max_width`. Fails if this box - /// is an unscanned text box. - pub fn split_to_width(&self, max_width: Au, starts_line: bool) - -> SplitBoxResult { - match *self { - GenericRenderBoxClass(*) | ImageRenderBoxClass(*) => CannotSplit(*self), - UnscannedTextRenderBoxClass(*) => { - fail!(~"WAT: shouldn't be an unscanned text box here.") - } + // Always inline for SCCP. + #[inline(always)] + pub fn clear(&self) -> Option<ClearType> { + let style = self.node.style(); + match style.Box.clear { + clear::none => None, + clear::left => Some(ClearLeft), + clear::right => Some(ClearRight), + clear::both => Some(ClearBoth), + } + } - TextRenderBoxClass(text_box) => { - let mut pieces_processed_count: uint = 0; - let mut remaining_width: Au = max_width; - let mut left_range = Range::new(text_box.range.begin(), 0); - let mut right_range: Option<Range> = None; - - debug!("split_to_width: splitting text box (strlen=%u, range=%?, avail_width=%?)", - text_box.run.text.len(), - text_box.range, - max_width); - - for (glyphs, offset, slice_range) in text_box.run.iter_slices_for_range(&text_box.range) { - debug!("split_to_width: considering slice (offset=%?, range=%?, remain_width=%?)", - offset, - slice_range, - remaining_width); - - let metrics = text_box.run.metrics_for_slice(glyphs, &slice_range); - let advance = metrics.advance_width; - let should_continue: bool; - - if advance <= remaining_width { - should_continue = true; - - if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() { - debug!("split_to_width: case=skipping leading trimmable whitespace"); - left_range.shift_by(slice_range.length() as int); - } else { - debug!("split_to_width: case=enlarging span"); - remaining_width = remaining_width - advance; - left_range.extend_by(slice_range.length() as int); - } - } else { // The advance is more than the remaining width. - should_continue = false; - let slice_begin = offset + slice_range.begin(); - let slice_end = offset + slice_range.end(); - - if glyphs.is_whitespace() { - // If there are still things after the trimmable whitespace, create the - // right chunk. - if slice_end < text_box.range.end() { - debug!("split_to_width: case=skipping trimmable trailing \ - whitespace, then split remainder"); - let right_range_end = - text_box.range.end() - slice_end; - right_range = Some(Range::new(slice_end, right_range_end)); - } else { - debug!("split_to_width: case=skipping trimmable trailing \ - whitespace"); - } - } else if slice_begin < text_box.range.end() { - // There are still some things left over at the end of the line. Create - // the right chunk. - let right_range_end = - text_box.range.end() - slice_begin; - right_range = Some(Range::new(slice_begin, right_range_end)); - debug!("split_to_width: case=splitting remainder with right range=%?", - right_range); - } - } + /// Converts this node's computed style to a font style used for rendering. + pub fn font_style(&self) -> FontStyle { + let my_style = self.nearest_ancestor_element().style(); - pieces_processed_count += 1; + debug!("(font style) start: %?", self.nearest_ancestor_element().type_id()); - if !should_continue { - break - } - } + // FIXME: Too much allocation here. + let font_families = do my_style.Font.font_family.map |family| { + match *family { + FamilyName(ref name) => (*name).clone(), + } + }; + let font_families = font_families.connect(", "); + debug!("(font style) font families: `%s`", font_families); - let left_box = if left_range.length() > 0 { - let new_text_box = @mut text::adapt_textbox_with_range(text_box.base, - text_box.run, - left_range); - Some(TextRenderBoxClass(new_text_box)) - } else { - None - }; + let font_size = my_style.Font.font_size.to_f64().unwrap() / 60.0; + debug!("(font style) font size: `%fpx`", font_size); - let right_box = do right_range.map_default(None) |range: Range| { - let new_text_box = @mut text::adapt_textbox_with_range(text_box.base, - text_box.run, - range); - Some(TextRenderBoxClass(new_text_box)) - }; + let (italic, oblique) = match my_style.Font.font_style { + font_style::normal => (false, false), + font_style::italic => (true, false), + font_style::oblique => (false, true), + }; - if pieces_processed_count == 1 || left_box.is_none() { - SplitDidNotFit(left_box, right_box) - } else { - SplitDidFit(left_box, right_box) - } - } + FontStyle { + pt_size: font_size, + weight: FontWeight300, + italic: italic, + oblique: oblique, + families: font_families, } } - /// Guess the intrinsic width of this box for - /// computation of min and preferred widths. - // - // TODO(eatkinson): this is unspecified in - // CSS 2.1, but we need to do something reasonable - // here. What this function does currently is - // NOT reasonable. - // - // TODO(eatkinson): integrate with - // get_min_width and get_pref_width? - fn guess_width (&self) -> Au { - do self.with_base |base| { - if(!base.node.is_element()) { - Au(0) - } else { - let style = self.style(); - let width = MaybeAuto::from_style(style.Box.width, - Au(0)).specified_or_zero(); - let margin_left = MaybeAuto::from_style(style.Margin.margin_left, - Au(0)).specified_or_zero(); - let margin_right = MaybeAuto::from_style(style.Margin.margin_right, - Au(0)).specified_or_zero(); - let padding_left = base.model.compute_padding_length(style.Padding.padding_left, - Au(0)); - let padding_right = base.model.compute_padding_length(style.Padding.padding_right, - Au(0)); - let border_left = style.Border.border_left_width; - let border_right = style.Border.border_right_width; - - width + margin_left + margin_right + padding_left + padding_right + - border_left + border_right - } - } + // FIXME(pcwalton): Why &'static??? Isn't that wildly unsafe? + #[inline(always)] + pub fn style(&self) -> &'static ComputedValues { + self.node.style() } - /// Returns the *minimum width* of this render box as defined by the CSS specification. - pub fn get_min_width(&self, _: &LayoutContext) -> Au { - // FIXME(pcwalton): I think we only need to calculate this if the damage says that CSS - // needs to be restyled. + /// Returns the text alignment of the computed style of the nearest ancestor-or-self `Element` + /// node. + pub fn text_align(&self) -> text_align::T { + self.nearest_ancestor_element().style().Text.text_align + } - self.guess_width() + match *self { - // TODO: This should account for the minimum width of the box element in isolation. - // That includes borders, margins, and padding, but not child widths. The block - // `FlowContext` will combine the width of this element and that of its children to - // arrive at the context width. - GenericRenderBoxClass(*) => Au(0), + pub fn line_height(&self) -> line_height::T { + self.nearest_ancestor_element().style().Box.line_height + } - ImageRenderBoxClass(image_box) => { - self.image_width(image_box) - } + pub fn vertical_align(&self) -> vertical_align::T { + self.nearest_ancestor_element().style().Box.vertical_align + } - TextRenderBoxClass(text_box) => { - text_box.run.min_width_for_range(&text_box.range) + /// Returns the text decoration of the computed style of the nearest `Element` node + pub fn text_decoration(&self) -> text_decoration::T { + /// Computes the propagated value of text-decoration, as specified in CSS 2.1 § 16.3.1 + /// TODO: make sure this works with anonymous box generation. + fn get_propagated_text_decoration(element: AbstractNode<LayoutView>) + -> text_decoration::T { + //Skip over non-element nodes in the DOM + if !element.is_element() { + return match element.parent_node() { + None => text_decoration::none, + Some(parent) => get_propagated_text_decoration(parent), + }; } - UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here.") - } - } + // FIXME: Implement correctly. + let display_in_flow = true; - /// Returns the *preferred width* of this render box as defined by the CSS specification. - pub fn get_pref_width(&self, _: &LayoutContext) -> Au { - self.guess_width() + match *self { - // TODO: This should account for the preferred width of the box element in isolation. - // That includes borders, margins, and padding, but not child widths. The block - // `FlowContext` will combine the width of this element and that of its children to - // arrive at the context width. - GenericRenderBoxClass(*) => Au(0), + let position = element.style().Box.position; + let float = element.style().Box.float; - ImageRenderBoxClass(image_box) => { - self.image_width(image_box) - } + let in_flow = (position == position::static_) && (float == float::none) && + display_in_flow; - TextRenderBoxClass(text_box) => { - // A text box cannot span lines, so assume that this is an unsplit text box. - // - // TODO: If text boxes have been split to wrap lines, then they could report a - // smaller preferred width during incremental reflow. Maybe text boxes should - // report nothing and the parent flow can factor in minimum/preferred widths of any - // text runs that it owns. - let mut max_line_width = Au(0); - for line_range in text_box.run.iter_natural_lines_for_range(&text_box.range) { - let line_metrics = text_box.run.metrics_for_range(&line_range); - max_line_width = Au::max(max_line_width, line_metrics.advance_width); - } + let text_decoration = element.style().Text.text_decoration; - max_line_width + if text_decoration == text_decoration::none && in_flow { + match element.parent_node() { + None => text_decoration::none, + Some(parent) => get_propagated_text_decoration(parent), + } + } + else { + text_decoration } - - UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here."), } + get_propagated_text_decoration(self.nearest_ancestor_element()) } - // Calculate the width of an image, accounting for the width attribute - // TODO: This could probably go somewhere else - pub fn image_width(&self, image_box: @mut ImageRenderBox) -> Au { - let attr_width: Option<int> = do self.with_base |base| { - do base.node.with_imm_element |elt| { - match elt.get_attr("width") { - Some(width) => { - FromStr::from_str(width) - } - None => { - None - } - } - } - }; - - // TODO: Consult margins and borders? - let px_width = if attr_width.is_some() { - attr_width.unwrap() - } else { - image_box.image.get_size().unwrap_or(Size2D(0, 0)).width - }; +} - Au::from_px(px_width) +impl RenderBoxUtils for @RenderBox { + #[inline(always)] + fn base<'a>(&'a self) -> &'a RenderBoxBase { + unsafe { + let (_, box_ptr): (uint, *Box<RenderBoxBase>) = cast::transmute(*self); + cast::transmute(&(*box_ptr).data) + } } - // Calculate the height of an image, accounting for the height attribute - // TODO: This could probably go somewhere else - pub fn image_height(&self, image_box: @mut ImageRenderBox) -> Au { - let attr_height: Option<int> = do self.with_base |base| { - do base.node.with_imm_element |elt| { - match elt.get_attr("height") { - Some(height) => { - FromStr::from_str(height) - } - None => { - None - } - } - } - }; - - // TODO: Consult margins and borders? - let px_height = if attr_height.is_some() { - attr_height.unwrap() - } else { - image_box.image.get_size().unwrap_or(Size2D(0, 0)).height - }; + fn is_replaced(&self) -> bool { + self.class() == ImageRenderBoxClass + } - Au::from_px(px_height) + fn can_split(&self) -> bool { + self.class() == TextRenderBoxClass } /// Returns the amount of left and right "fringe" used by this box. This is based on margins, /// borders, padding, and width. - pub fn get_used_width(&self) -> (Au, Au) { + fn get_used_width(&self) -> (Au, Au) { // TODO: This should actually do some computation! See CSS 2.1, Sections 10.3 and 10.4. - (Au(0), Au(0)) + (Au::new(0), Au::new(0)) } /// Returns the amount of left and right "fringe" used by this box. This should be based on /// margins, borders, padding, and width. - pub fn get_used_height(&self) -> (Au, Au) { + fn get_used_height(&self) -> (Au, Au) { // TODO: This should actually do some computation! See CSS 2.1, Sections 10.5 and 10.6. - (Au(0), Au(0)) + (Au::new(0), Au::new(0)) } - pub fn compute_padding(&self, cb_width: Au) { - do self.with_mut_base |base| { - base.model.compute_padding(base.node.style(), cb_width); - } - } + /// Adds the display items necessary to paint the background of this render box to the display + /// list if necessary. + fn paint_background_if_applicable<E:ExtraDisplayListData>( + &self, + list: &Cell<DisplayList<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 + // inefficient. What we really want is something like "nearest ancestor element that + // doesn't have a render box". + let nearest_ancestor_element = self.base().nearest_ancestor_element(); - pub fn get_noncontent_width(&self) -> Au { - do self.with_base |base| { - base.model.border.left + base.model.padding.left + - base.model.border.right + base.model.padding.right - } - } + let style = nearest_ancestor_element.style(); + let background_color = style.resolve_color(style.Background.background_color); + if !background_color.alpha.approx_eq(&0.0) { + do list.with_mut_ref |list| { + let solid_color_display_item = ~SolidColorDisplayItem { + base: BaseDisplayItem { + bounds: *absolute_bounds, + extra: ExtraDisplayListData::new(self), + }, + color: background_color.to_gfx_color(), + }; - pub fn with_model<R>(&self, callback: &fn(&mut BoxModel) -> R) -> R { - do self.with_mut_base |base| { - callback(&mut base.model) + list.append_item(SolidColorDisplayItemClass(solid_color_display_item)) + } } } - /// The box formed by the content edge as defined in CSS 2.1 § 8.1. Coordinates are relative to - /// the owning flow. - pub fn content_box(&self) -> Rect<Au> { - do self.with_base |base| { - let origin = Point2D(base.position.origin.x + - base.model.border.left + - base.model.padding.left, - base.position.origin.y); - let size = Size2D(base.position.size.width - self.get_noncontent_width(), - base.position.size.height); - Rect(origin, size) + /// Adds the display items necessary to paint the borders of this render box to a display list + /// if necessary. + fn paint_borders_if_applicable<E:ExtraDisplayListData>( + &self, + list: &Cell<DisplayList<E>>, + abs_bounds: &Rect<Au>) { + // Fast path. + let base = self.base(); + let border = base.model.get().border; + if border.is_zero() { + return } - } - /// The box formed by the border edge as defined in CSS 2.1 § 8.1. Coordinates are relative to - /// the owning flow. - pub fn border_box(&self) -> Rect<Au> { - // TODO: Actually compute the content box, padding, and border. - self.content_box() - } - - /// The box formed by the margin edge as defined in CSS 2.1 § 8.1. Coordinates are relative to - /// the owning flow. - pub fn margin_box(&self) -> Rect<Au> { - // TODO: Actually compute the content_box, padding, border, and margin. - self.content_box() - } - - /// A convenience function to access the computed style of the DOM node that this render box - /// represents. - pub fn style(&self) -> &ComputedValues { - self.with_base(|base| base.node.style()) - } + let style = base.style(); + let top_color = style.resolve_color(style.Border.border_top_color); + let right_color = style.resolve_color(style.Border.border_right_color); + let bottom_color = style.resolve_color(style.Border.border_bottom_color); + let left_color = style.resolve_color(style.Border.border_left_color); + let top_style = style.Border.border_top_style; + let right_style = style.Border.border_right_style; + let bottom_style = style.Border.border_bottom_style; + let left_style = style.Border.border_left_style; - /// A convenience function to access the DOM node that this render box represents. - pub fn node(&self) -> AbstractNode<LayoutView> { - self.with_base(|base| base.node) - } + // Append the border to the display list. + do list.with_mut_ref |list| { + let border_display_item = ~BorderDisplayItem { + base: BaseDisplayItem { + bounds: *abs_bounds, + extra: ExtraDisplayListData::new(self), + }, + border: SideOffsets2D::new(border.top, + border.right, + border.bottom, + border.left), + 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) + }; - /// Returns the nearest ancestor-or-self `Element` to the DOM node that this render box - /// represents. - /// - /// If there is no ancestor-or-self `Element` node, fails. - pub fn nearest_ancestor_element(&self) -> AbstractNode<LayoutView> { - do self.with_base |base| { - let mut node = base.node; - while !node.is_element() { - match node.parent_node() { - None => fail!(~"no nearest element?!"), - Some(parent) => node = parent, - } - } - node + list.append_item(BorderDisplayItemClass(border_display_item)) } } - // - // Painting - // - /// Adds the display items for this render box to the given display list. /// /// Arguments: @@ -630,12 +939,14 @@ impl RenderBox { /// 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, - _: &DisplayListBuilder, - dirty: &Rect<Au>, - offset: &Point2D<Au>, - list: &Cell<DisplayList<E>>) { - let box_bounds = self.position(); + fn build_display_list<E:ExtraDisplayListData>( + &self, + _: &DisplayListBuilder, + dirty: &Rect<Au>, + offset: &Point2D<Au>, + list: &Cell<DisplayList<E>>) { + let base = self.base(); + let box_bounds = base.position.get(); let absolute_box_bounds = box_bounds.translate(offset); debug!("RenderBox::build_display_list at rel=%?, abs=%?: %s", box_bounds, absolute_box_bounds, self.debug_str()); @@ -648,14 +959,15 @@ impl RenderBox { return; } - match *self { - UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here."), - TextRenderBoxClass(text_box) => { + match self.class() { + UnscannedTextRenderBoxClass => fail!("Shouldn't see unscanned boxes here."), + TextRenderBoxClass => { + let text_box = self.as_text_render_box(); // Add the background to the list, if applicable. self.paint_background_if_applicable(list, &absolute_box_bounds); - let nearest_ancestor_element = self.nearest_ancestor_element(); + let nearest_ancestor_element = base.nearest_ancestor_element(); let color = nearest_ancestor_element.style().Color.color.to_gfx_color(); // Create the text box. @@ -663,7 +975,7 @@ impl RenderBox { let text_display_item = ~TextDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(*self), + extra: ExtraDisplayListData::new(self), }, // FIXME(pcwalton): Allocation? Why?! text_run: ~text_box.run.serialize(), @@ -686,7 +998,7 @@ impl RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(*self), + extra: ExtraDisplayListData::new(self), }, border: debug_border, color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), @@ -708,7 +1020,7 @@ impl RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: baseline, - extra: ExtraDisplayListData::new(*self), + extra: ExtraDisplayListData::new(self), }, border: debug_border, color: SideOffsets2D::new_all_same(rgb(0, 200, 0)), @@ -721,8 +1033,7 @@ impl RenderBox { () }); }, - GenericRenderBoxClass(_) => { - + GenericRenderBoxClass => { // Add the background to the list, if applicable. self.paint_background_if_applicable(list, &absolute_box_bounds); @@ -735,7 +1046,7 @@ impl RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(*self), + extra: ExtraDisplayListData::new(self), }, border: debug_border, color: SideOffsets2D::new_all_same(rgb(0, 0, 200)), @@ -747,14 +1058,14 @@ impl RenderBox { () }); - }, - ImageRenderBoxClass(image_box) => { + ImageRenderBoxClass => { + let image_box = self.as_image_render_box(); // Add the background to the list, if applicable. self.paint_background_if_applicable(list, &absolute_box_bounds); - match image_box.image.get_image() { + match image_box.image.mutate().ptr.get_image() { Some(image) => { debug!("(building display list) building image box"); @@ -763,7 +1074,7 @@ impl RenderBox { let image_display_item = ~ImageDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(*self), + extra: ExtraDisplayListData::new(self), }, image: image.clone(), }; @@ -785,257 +1096,15 @@ impl RenderBox { // TODO: Outlines. self.paint_borders_if_applicable(list, &absolute_box_bounds); } +} - /// Adds the display items necessary to paint the background of this render box to the display - /// list if necessary. - pub fn paint_background_if_applicable<E:ExtraDisplayListData>(&self, - list: &Cell<DisplayList<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 - // inefficient. What we really want is something like "nearest ancestor element that - // doesn't have a render box". - let nearest_ancestor_element = self.nearest_ancestor_element(); - - let style = nearest_ancestor_element.style(); - let background_color = style.resolve_color(style.Background.background_color); - if !background_color.alpha.approx_eq(&0.0) { - do list.with_mut_ref |list| { - let solid_color_display_item = ~SolidColorDisplayItem { - base: BaseDisplayItem { - bounds: *absolute_bounds, - extra: ExtraDisplayListData::new(*self), - }, - color: background_color.to_gfx_color(), - }; - - list.append_item(SolidColorDisplayItemClass(solid_color_display_item)) - } - } - } - - pub fn clear(&self) -> Option<ClearType> { - let style = self.style(); - match style.Box.clear { - clear::none => None, - clear::left => Some(ClearLeft), - clear::right => Some(ClearRight), - clear::both => Some(ClearBoth) - } - } - - /// Converts this node's computed style to a font style used for rendering. - pub fn font_style(&self) -> FontStyle { - fn get_font_style(element: AbstractNode<LayoutView>) -> FontStyle { - let my_style = element.style(); - - debug!("(font style) start: %?", element.type_id()); - - // FIXME: Too much allocation here. - let font_families = do my_style.Font.font_family.map |family| { - match *family { - FamilyName(ref name) => (*name).clone() - } - }; - let font_families = font_families.connect(", "); - debug!("(font style) font families: `%s`", font_families); - - let font_size = my_style.Font.font_size.to_f64().unwrap() / 60.0; - debug!("(font style) font size: `%fpx`", font_size); - - let (italic, oblique) = match my_style.Font.font_style { - font_style::normal => (false, false), - font_style::italic => (true, false), - font_style::oblique => (false, true), - }; - - FontStyle { - pt_size: font_size, - weight: FontWeight300, - italic: italic, - oblique: oblique, - families: font_families, - } - } - - let font_style_cached = match *self { - UnscannedTextRenderBoxClass(ref box) => { - match box.font_style { - Some(ref style) => Some(style.clone()), - None => None - } - } - _ => None - }; - - if font_style_cached.is_some() { - return font_style_cached.unwrap(); - } else { - let font_style = get_font_style(self.nearest_ancestor_element()); - match *self { - UnscannedTextRenderBoxClass(ref box) => { - box.font_style = Some(font_style.clone()); - } - _ => () - } - return font_style; - } - } - - /// Returns the text alignment of the computed style of the nearest ancestor-or-self `Element` - /// node. - pub fn text_align(&self) -> text_align::T { - self.nearest_ancestor_element().style().Text.text_align - } - - pub fn line_height(&self) -> line_height::T { - self.nearest_ancestor_element().style().Box.line_height - } - - pub fn vertical_align(&self) -> vertical_align::T { - self.nearest_ancestor_element().style().Box.vertical_align - } - - /// Returns the text decoration of the computed style of the nearest `Element` node - pub fn text_decoration(&self) -> text_decoration::T { - /// Computes the propagated value of text-decoration, as specified in CSS 2.1 § 16.3.1 - /// TODO: make sure this works with anonymous box generation. - fn get_propagated_text_decoration(element: AbstractNode<LayoutView>) -> text_decoration::T { - //Skip over non-element nodes in the DOM - if(!element.is_element()){ - return match element.parent_node() { - None => text_decoration::none, - Some(parent) => get_propagated_text_decoration(parent), - }; - } - - //FIXME: Implement correctly - let display_in_flow = true; - - let position = element.style().Box.position; - let float = element.style().Box.float; - - let in_flow = (position == position::static_) && (float == float::none) && - display_in_flow; - - let text_decoration = element.style().Text.text_decoration; - - if text_decoration == text_decoration::none && in_flow { - match element.parent_node() { - None => text_decoration::none, - Some(parent) => get_propagated_text_decoration(parent), - } - } - else { - text_decoration - } - } - - let text_decoration_cached = match *self { - UnscannedTextRenderBoxClass(ref box) => { - match box.text_decoration { - Some(ref decoration) => Some(decoration.clone()), - None => None - } - } - _ => None - }; - - if text_decoration_cached.is_some() { - return text_decoration_cached.unwrap(); - } else { - let text_decoration = get_propagated_text_decoration(self.nearest_ancestor_element()); - match *self { - UnscannedTextRenderBoxClass(ref box) => { - box.text_decoration = Some(text_decoration.clone()); - } - _ => () - } - return text_decoration; - } - } - - /// Dumps this node, for debugging. - pub fn dump(&self) { - self.dump_indent(0); - } - - /// Dumps a render box for debugging, with indentation. - pub fn dump_indent(&self, indent: uint) { - let mut string = ~""; - for _ in range(0u, indent) { - string.push_str(" "); - } - - string.push_str(self.debug_str()); - debug!("%s", string); - } - - /// Returns a debugging string describing this box. - pub fn debug_str(&self) -> ~str { - let representation = match *self { - GenericRenderBoxClass(*) => ~"GenericRenderBox", - ImageRenderBoxClass(*) => ~"ImageRenderBox", - TextRenderBoxClass(text_box) => { - fmt!("TextRenderBox(text=%s)", text_box.run.text.slice_chars(text_box.range.begin(), - text_box.range.end())) - } - UnscannedTextRenderBoxClass(text_box) => { - fmt!("UnscannedTextRenderBox(%s)", text_box.text) - } - }; - - fmt!("box b%?: %s", self.id(), representation) - } - - // - // Painting - // - - /// Adds the display items necessary to paint the borders of this render box to a display list - /// if necessary. - pub fn paint_borders_if_applicable<E:ExtraDisplayListData>(&self, - list: &Cell<DisplayList<E>>, - abs_bounds: &Rect<Au>) { - // Fast path. - let border = do self.with_base |base| { - base.model.border - }; - if border.is_zero() { - return - } - - let style = self.style(); - let top_color = style.resolve_color(style.Border.border_top_color); - let right_color = style.resolve_color(style.Border.border_right_color); - let bottom_color = style.resolve_color(style.Border.border_bottom_color); - let left_color = style.resolve_color(style.Border.border_left_color); - let top_style = style.Border.border_top_style; - let right_style = style.Border.border_right_style; - let bottom_style = style.Border.border_bottom_style; - let left_style = style.Border.border_left_style; - // Append the border to the display list. - do list.with_mut_ref |list| { - let border_display_item = ~BorderDisplayItem { - base: BaseDisplayItem { - bounds: *abs_bounds, - extra: ExtraDisplayListData::new(*self), - }, - border: SideOffsets2D::new(border.top, - border.right, - border.bottom, - border.left), - 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) - }; - - list.append_item(BorderDisplayItemClass(border_display_item)) +impl<'self> RenderBoxRefUtils<'self> for &'self RenderBox { + #[inline(always)] + fn base(self) -> &'self RenderBoxBase { + unsafe { + let (_, box_ptr): (uint, *RenderBoxBase) = cast::transmute(self); + cast::transmute(box_ptr) } } } + diff --git a/src/components/main/layout/box_builder.rs b/src/components/main/layout/box_builder.rs index 9686ddcb6cb..7afff453df0 100644 --- a/src/components/main/layout/box_builder.rs +++ b/src/components/main/layout/box_builder.rs @@ -4,16 +4,18 @@ //! Creates CSS boxes from a DOM tree. -use layout::block::BlockFlowData; -use layout::float::FloatFlowData; -use layout::box::{GenericRenderBoxClass, ImageRenderBox, ImageRenderBoxClass, RenderBox}; -use layout::box::{RenderBoxBase, RenderBoxType, RenderBox_Generic, RenderBox_Image}; -use layout::box::{RenderBox_Text, UnscannedTextRenderBox, UnscannedTextRenderBoxClass}; +use layout::block::BlockFlow; +use layout::float::FloatFlow; +use layout::box::{GenericRenderBox, GenericRenderBoxClass, ImageRenderBox, ImageRenderBoxClass}; +use layout::box::{RenderBox, RenderBoxBase, RenderBoxClass, RenderBoxUtils, TextRenderBoxClass}; +use layout::box::{UnscannedTextRenderBox, UnscannedTextRenderBoxClass}; use layout::context::LayoutContext; -use layout::flow::{AbsoluteFlow, BlockFlow, FloatFlow, Flow_Absolute, Flow_Block, Flow_Float}; -use layout::flow::{Flow_Inline, Flow_InlineBlock, Flow_Root, Flow_Table, FlowContext}; -use layout::flow::{FlowContextType, FlowData, InlineBlockFlow, InlineFlow, TableFlow}; -use layout::inline::{InlineFlowData, InlineLayout}; +use layout::float_context::FloatType; +use layout::flow::{AbsoluteFlow, BlockFlowClass, FloatFlowClass, FlowContext, FlowData}; +use layout::flow::{ImmutableFlowUtils, InlineBlockFlow, InlineBlockFlowClass, InlineFlowClass}; +use layout::flow::{MutableFlowUtils, TableFlow}; +use layout::flow; +use layout::inline::{InlineFlow}; use layout::text::TextRunScanner; use css::node_style::StyledNode; @@ -25,8 +27,19 @@ use script::dom::node::{ElementNodeTypeId, LayoutView, TextNodeTypeId}; use script::dom::node::DocumentFragmentNodeTypeId; use servo_util::range::Range; use servo_util::tree::{TreeNodeRef, TreeNode}; +use std::cast; use std::cell::Cell; +enum FlowType { + AbsoluteFlowType, + BlockFlowType, + FloatFlowType(FloatType), + InlineBlockFlowType, + InlineFlowType, + RootFlowType, + TableFlowType, +} + pub struct LayoutTreeBuilder { next_cid: int, next_bid: int, @@ -64,12 +77,15 @@ impl<'self> BoxGenerator<'self> { } } - fn with_clone<R> (&mut self, cb: &fn(BoxGenerator<'self>) -> R) -> R { - let gen = BoxGenerator { - flow: &mut *self.flow, - range_stack: self.range_stack - }; - cb(gen) + fn with_clone<R>(&mut self, cb: &fn(BoxGenerator<'self>) -> R) -> R { + // FIXME(pcwalton): This is a hack; it can be done safely with linearity. + unsafe { + let gen = BoxGenerator { + flow: cast::transmute_copy(&self.flow), + range_stack: self.range_stack + }; + cb(gen) + } } /* Whether "spacer" boxes are needed to stand in for this DOM node */ @@ -81,7 +97,7 @@ impl<'self> BoxGenerator<'self> { fn make_inline_spacer_for_node_side(_: &LayoutContext, _: AbstractNode<LayoutView>, _: InlineSpacerSide) - -> Option<RenderBox> { + -> Option<@RenderBox> { None } @@ -89,17 +105,18 @@ impl<'self> BoxGenerator<'self> { ctx: &LayoutContext, node: AbstractNode<LayoutView>, builder: &mut LayoutTreeBuilder) { - debug!("BoxGenerator[f%d]: pushing node: %s", self.flow.id(), node.debug_str()); + debug!("BoxGenerator[f%d]: pushing node: %s", flow::base(self.flow).id, node.debug_str()); // TODO: remove this once UA styles work let box_type = self.decide_box_type(node); - debug!("BoxGenerator[f%d]: point a", self.flow.id()); + debug!("BoxGenerator[f%d]: point a", flow::base(self.flow).id); let range_stack = &mut self.range_stack; // depending on flow, make a box for this node. - match *self.flow { - InlineFlow(ref mut inline) => { + match self.flow.class() { + InlineFlowClass => { + let inline = self.flow.as_inline(); let node_range_start = inline.boxes.len(); range_stack.push(node_range_start); @@ -116,42 +133,43 @@ impl<'self> BoxGenerator<'self> { } // TODO: cases for inline-block, etc. }, - BlockFlow(ref mut block) => { - debug!("BoxGenerator[f%d]: point b", block.common.id); + BlockFlowClass => { + let block = self.flow.as_block(); + debug!("BoxGenerator[f%d]: point b", block.base.id); let new_box = BoxGenerator::make_box(ctx, box_type, node, builder); debug!("BoxGenerator[f%d]: attaching box[b%d] to block flow (node: %s)", - block.common.id, - new_box.id(), + block.base.id, + new_box.base().id(), node.debug_str()); assert!(block.box.is_none()); block.box = Some(new_box); } - FloatFlow(ref mut float) => { - debug!("BoxGenerator[f%d]: point b", float.common.id); + FloatFlowClass => { + let float = self.flow.as_float(); + debug!("BoxGenerator[f%d]: point b", float.base.id); let new_box = BoxGenerator::make_box(ctx, box_type, node, builder); debug!("BoxGenerator[f%d]: attaching box[b%d] to float flow (node: %s)", - float.common.id, - new_box.id(), + float.base.id, + new_box.base().id(), node.debug_str()); assert!(float.box.is_none() && float.index.is_none()); float.box = Some(new_box); } - _ => warn!("push_node() not implemented for flow f%d", self.flow.id()), + _ => warn!("push_node() not implemented for flow f%d", flow::base(self.flow).id), } } - pub fn pop_node(&mut self, - ctx: &LayoutContext, - node: AbstractNode<LayoutView>) { - debug!("BoxGenerator[f%d]: popping node: %s", self.flow.id(), node.debug_str()); + pub fn pop_node(&mut self, ctx: &LayoutContext, node: AbstractNode<LayoutView>) { + debug!("BoxGenerator[f%d]: popping node: %s", flow::base(self.flow).id, node.debug_str()); - match *self.flow { - InlineFlow(ref mut inline) => { + match self.flow.class() { + InlineFlowClass => { + let inline = self.flow.as_inline(); let inline = &mut *inline; if BoxGenerator::inline_spacers_needed_for_node(node) { @@ -173,24 +191,26 @@ impl<'self> BoxGenerator<'self> { debug!("BoxGenerator: adding element range=%?", node_range); inline.elems.add_mapping(node, &node_range); }, - BlockFlow(*) => assert!(self.range_stack.len() == 0), - FloatFlow(*) => assert!(self.range_stack.len() == 0), - _ => warn!("pop_node() not implemented for flow %?", self.flow.id()), + BlockFlowClass => assert!(self.range_stack.len() == 0), + FloatFlowClass => assert!(self.range_stack.len() == 0), + _ => warn!("pop_node() not implemented for flow %?", flow::base(self.flow).id), } } /// Disambiguate between different methods here instead of inlining, since each case has very /// different complexity. fn make_box(layout_ctx: &LayoutContext, - ty: RenderBoxType, + ty: RenderBoxClass, node: AbstractNode<LayoutView>, builder: &mut LayoutTreeBuilder) - -> RenderBox { + -> @RenderBox { let base = RenderBoxBase::new(node, builder.next_box_id()); let result = match ty { - RenderBox_Generic => GenericRenderBoxClass(@mut base), - RenderBox_Text => UnscannedTextRenderBoxClass(@mut UnscannedTextRenderBox::new(base)), - RenderBox_Image => BoxGenerator::make_image_box(layout_ctx, node, base), + GenericRenderBoxClass => @GenericRenderBox::new(base) as @RenderBox, + TextRenderBoxClass | UnscannedTextRenderBoxClass => { + @UnscannedTextRenderBox::new(base) as @RenderBox + } + ImageRenderBoxClass => BoxGenerator::make_image_box(layout_ctx, node, base), }; debug!("BoxGenerator: created box: %s", result.debug_str()); result @@ -199,36 +219,36 @@ impl<'self> BoxGenerator<'self> { fn make_image_box(layout_ctx: &LayoutContext, node: AbstractNode<LayoutView>, base: RenderBoxBase) - -> RenderBox { + -> @RenderBox { assert!(node.is_image_element()); do node.with_imm_image_element |image_element| { if image_element.image.is_some() { // FIXME(pcwalton): Don't copy URLs. let url = (*image_element.image.get_ref()).clone(); - ImageRenderBoxClass(@mut ImageRenderBox::new(base, url, layout_ctx.image_cache)) + @ImageRenderBox::new(base.clone(), url, layout_ctx.image_cache) as @RenderBox } else { info!("Tried to make image box, but couldn't find image. Made generic box \ instead."); - GenericRenderBoxClass(@mut base) + @GenericRenderBox::new(base.clone()) as @RenderBox } } } - fn decide_box_type(&self, node: AbstractNode<LayoutView>) -> RenderBoxType { + fn decide_box_type(&self, node: AbstractNode<LayoutView>) -> RenderBoxClass { if node.is_text() { - RenderBox_Text + TextRenderBoxClass } else if node.is_image_element() { do node.with_imm_image_element |image_element| { match image_element.image { - Some(_) => RenderBox_Image, - None => RenderBox_Generic, + Some(_) => ImageRenderBoxClass, + None => GenericRenderBoxClass, } } } else if node.is_element() { - RenderBox_Generic + GenericRenderBoxClass } else { - fail!(~"Hey, doctypes and comments shouldn't get here! They are display:none!") + fail!("Hey, doctypes and comments shouldn't get here! They are display:none!") } } @@ -261,13 +281,14 @@ impl LayoutTreeBuilder { /// Creates necessary box(es) and flow context(s) for the current DOM node, /// and recurses on its children. - pub fn construct_recursively<'a>(&mut self, - layout_ctx: &LayoutContext, - cur_node: AbstractNode<LayoutView>, - mut grandparent_generator: Option<BoxGenerator<'a>>, - mut parent_generator: BoxGenerator<'a>, - mut prev_sibling_generator: Option<BoxGenerator<'a>>) - -> BoxConstructResult<'a> { + pub fn construct_recursively<'a>( + &mut self, + layout_ctx: &LayoutContext, + cur_node: AbstractNode<LayoutView>, + mut grandparent_generator: Option<BoxGenerator<'a>>, + mut parent_generator: BoxGenerator<'a>, + mut prev_sibling_generator: Option<BoxGenerator<'a>>) + -> BoxConstructResult<'a> { debug!("Considering node: %s", cur_node.debug_str()); let box_gen_result = { let grandparent_gen_ref = match grandparent_generator { @@ -285,32 +306,29 @@ impl LayoutTreeBuilder { debug!("result from generator_for_node: %?", &box_gen_result); // Skip over nodes that don't belong in the flow tree - let (this_generator, next_generator) = - match box_gen_result { - NoGenerator => return Normal(prev_sibling_generator), + let (this_generator, next_generator) = match box_gen_result { + NoGenerator => return Normal(prev_sibling_generator), + ParentGenerator => { + do parent_generator.with_clone |clone| { + (clone, None) + } + } + SiblingGenerator => (prev_sibling_generator.take_unwrap(), None), + NewGenerator(gen) => (gen, None), + ReparentingGenerator(gen) => { + reparent = true; + (gen, None) + } + Mixed(gen, next_gen) => (gen, Some(match *next_gen { ParentGenerator => { do parent_generator.with_clone |clone| { - (clone, None) + clone } } - SiblingGenerator => (prev_sibling_generator.take_unwrap(), None), - NewGenerator(gen) => (gen, None), - ReparentingGenerator(gen) => { - reparent = true; - (gen, None) - } - Mixed(gen, next_gen) => (gen, Some(match *next_gen { - ParentGenerator => { - do parent_generator.with_clone |clone| { - clone - } - } - SiblingGenerator => prev_sibling_generator.take_unwrap(), - _ => fail!("Unexpect BoxGenResult") - })) - }; - - + SiblingGenerator => prev_sibling_generator.take_unwrap(), + _ => fail!("Unexpect BoxGenResult") + })) + }; let mut this_generator = this_generator; @@ -395,7 +413,12 @@ impl LayoutTreeBuilder { } }; - let sibling_flow: Option<&mut FlowContext> = sibling_generator.as_mut().map(|gen| &mut *gen.flow); + // FIXME(pcwalton): Unsafe. + let sibling_flow: Option<&mut FlowContext> = sibling_generator.as_mut().map(|gen| { + unsafe { + cast::transmute_copy(&gen.flow) + } + }); let is_float = if (node.is_element()) { match node.style().Box.float { @@ -406,85 +429,100 @@ impl LayoutTreeBuilder { } else { None }; - - let new_generator = match (display, &mut parent_generator.flow, sibling_flow) { + let sibling_flow_class = match sibling_flow { + None => None, + Some(flow) => Some(flow.class()), + }; + + let new_generator = match (display, parent_generator.flow.class(), sibling_flow_class) { // Floats - (display::block, & &BlockFlow(_), _) | - (display::block, & &FloatFlow(_), _) if is_float.is_some() => { - self.create_child_generator(node, parent_generator, Flow_Float(is_float.unwrap())) + (display::block, BlockFlowClass, _) | + (display::block, FloatFlowClass, _) if is_float.is_some() => { + self.create_child_generator(node, + parent_generator, + FloatFlowType(is_float.unwrap())) } // If we're placing a float after an inline, append the float to the inline flow, // then continue building from the inline flow in case there are more inlines // afterward. - (display::block, _, Some(&InlineFlow(_))) if is_float.is_some() => { + (display::block, _, Some(InlineFlowClass)) if is_float.is_some() => { + let float_type = FloatFlowType(is_float.unwrap()); let float_generator = self.create_child_generator(node, sibling_generator.unwrap(), - Flow_Float(is_float.unwrap())); + float_type); return Mixed(float_generator, ~SiblingGenerator); } // This is a catch-all case for when: // a) sibling_flow is None // b) sibling_flow is a BlockFlow - (display::block, & &InlineFlow(_), _) if is_float.is_some() => { - self.create_child_generator(node, parent_generator, Flow_Float(is_float.unwrap())) + (display::block, InlineFlowClass, _) if is_float.is_some() => { + self.create_child_generator(node, + parent_generator, + FloatFlowType(is_float.unwrap())) } - (display::block, & &BlockFlow(ref info), _) => match (info.is_root, node.parent_node().is_some()) { - // If this is the root node, then use the root flow's - // context. Otherwise, make a child block context. - (true, true) => self.create_child_generator(node, parent_generator, Flow_Block), - (true, false) => { return ParentGenerator } - (false, _) => { - self.create_child_generator(node, parent_generator, Flow_Block) + (display::block, BlockFlowClass, _) => { + match (parent_generator.flow.as_block().is_root, node.parent_node()) { + // If this is the root node, then use the root flow's + // context. Otherwise, make a child block context. + (true, Some(_)) => { + self.create_child_generator(node, parent_generator, BlockFlowType) + } + (true, None) => return ParentGenerator, + (false, _) => { + self.create_child_generator(node, parent_generator, BlockFlowType) + } } - }, + } - (display::block, & &FloatFlow(*), _) => { - self.create_child_generator(node, parent_generator, Flow_Block) + (display::block, FloatFlowClass, _) => { + self.create_child_generator(node, parent_generator, BlockFlowType) } // Inlines that are children of inlines are part of the same flow - (display::inline, & &InlineFlow(*), _) => return ParentGenerator, - (display::inline_block, & &InlineFlow(*), _) => return ParentGenerator, + (display::inline, InlineFlowClass, _) => return ParentGenerator, + (display::inline_block, InlineFlowClass, _) => return ParentGenerator, // Inlines that are children of blocks create new flows if their // previous sibling was a block. - (display::inline, & &BlockFlow(*), Some(&BlockFlow(*))) | - (display::inline_block, & &BlockFlow(*), Some(&BlockFlow(*))) => { - self.create_child_generator(node, parent_generator, Flow_Inline) + (display::inline, BlockFlowClass, Some(BlockFlowClass)) | + (display::inline_block, BlockFlowClass, Some(BlockFlowClass)) => { + self.create_child_generator(node, parent_generator, InlineFlowType) } // The first two cases should only be hit when a FloatFlow // is the first child of a BlockFlow. Other times, we will - (display::inline, _, Some(&FloatFlow(*))) | - (display::inline_block, _, Some(&FloatFlow(*))) | - (display::inline, & &FloatFlow(*), _) | - (display::inline_block, & &FloatFlow(*), _) => { - self.create_child_generator(node, parent_generator, Flow_Inline) + (display::inline, _, Some(FloatFlowClass)) | + (display::inline_block, _, Some(FloatFlowClass)) | + (display::inline, FloatFlowClass, _) | + (display::inline_block, FloatFlowClass, _) => { + self.create_child_generator(node, parent_generator, InlineFlowType) } // Inlines whose previous sibling was not a block try to use their // sibling's flow context. - (display::inline, & &BlockFlow(*), _) | - (display::inline_block, & &BlockFlow(*), _) => { + (display::inline, BlockFlowClass, _) | + (display::inline_block, BlockFlowClass, _) => { return match sibling_generator { None => NewGenerator(self.create_child_generator(node, parent_generator, - Flow_Inline)), + InlineFlowType)), Some(*) => SiblingGenerator } } // blocks that are children of inlines need to split their parent // flows. - (display::block, & &InlineFlow(*), _) => { + (display::block, InlineFlowClass, _) => { match grandparent_generator { None => fail!("expected to have a grandparent block flow"), Some(grandparent_gen) => { assert!(grandparent_gen.flow.is_block_like()); - let block_gen = self.create_child_generator(node, grandparent_gen, Flow_Block); + let block_gen = self.create_child_generator(node, + grandparent_gen, + BlockFlowType); return ReparentingGenerator(block_gen); } } @@ -496,15 +534,16 @@ impl LayoutTreeBuilder { NewGenerator(new_generator) } - pub fn create_child_generator<'a>(&mut self, - node: AbstractNode<LayoutView>, - parent_generator: &mut BoxGenerator<'a>, - ty: FlowContextType) - -> BoxGenerator<'a> { - + pub fn create_child_generator<'a>( + &mut self, + node: AbstractNode<LayoutView>, + parent_generator: &mut BoxGenerator<'a>, + ty: FlowType) + -> BoxGenerator<'a> { let new_flow = self.make_flow(ty, node); parent_generator.flow.add_new_child(new_flow); - BoxGenerator::new(parent_generator.flow.last_child().unwrap()) + let flow_ref = flow::last_child(parent_generator.flow).unwrap(); + BoxGenerator::new(*flow_ref) } /// Fix up any irregularities such as: @@ -516,15 +555,15 @@ impl LayoutTreeBuilder { /// The latter can only be done immediately adjacent to, or at the beginning or end of a block /// flow. Otherwise, the whitespace might affect whitespace collapsing with adjacent text. pub fn simplify_children_of_flow(&self, ctx: &LayoutContext, parent_flow: &mut FlowContext) { - match *parent_flow { - InlineFlow(*) => { + match parent_flow.class() { + InlineFlowClass => { let mut found_child_inline = false; let mut found_child_block = false; - for child_ctx in parent_flow.child_iter() { - match child_ctx { - &InlineFlow(*) | &InlineBlockFlow(*) => found_child_inline = true, - &BlockFlow(*) => found_child_block = true, + for child_ctx in flow::child_iter(parent_flow) { + match child_ctx.class() { + InlineFlowClass | InlineBlockFlowClass => found_child_inline = true, + BlockFlowClass => found_child_block = true, _ => {} } } @@ -532,23 +571,27 @@ impl LayoutTreeBuilder { if found_child_block && found_child_inline { self.fixup_split_inline(parent_flow) } - }, - BlockFlow(*) | FloatFlow(*) => { + } + BlockFlowClass | FloatFlowClass => { // check first/last child for whitespace-ness let mut do_remove = false; - let p_id = parent_flow.id(); + let p_id = flow::base(parent_flow).id; do parent_flow.with_first_child |mut first_child| { for first_flow in first_child.mut_iter() { if first_flow.starts_inline_flow() { // FIXME: workaround for rust#6393 { - let boxes = &first_flow.imm_inline().boxes; - if boxes.len() == 1 && boxes[0].is_whitespace_only() { - debug!("LayoutTreeBuilder: pruning whitespace-only first child \ - flow f%d from parent f%d", - first_flow.id(), - p_id); - do_remove = true; + let first_inline_flow = first_flow.as_inline(); + let boxes = &first_inline_flow.boxes; + if boxes.len() == 1 { + let first_box = boxes[0]; // FIXME(pcwalton): Rust bug + if first_box.is_whitespace_only() { + debug!("LayoutTreeBuilder: pruning whitespace-only first \ + child flow f%d from parent f%d", + first_inline_flow.base.id, + p_id); + do_remove = true; + } } } } @@ -560,19 +603,23 @@ impl LayoutTreeBuilder { do_remove = false; - let p_id = parent_flow.id(); + let p_id = flow::base(parent_flow).id; do parent_flow.with_last_child |mut last_child| { for last_flow in last_child.mut_iter() { if last_flow.starts_inline_flow() { // FIXME: workaround for rust#6393 { - let boxes = &last_flow.imm_inline().boxes; + let last_inline_flow = last_flow.as_inline(); + let boxes = &last_inline_flow.boxes; if boxes.len() == 1 && boxes.last().is_whitespace_only() { - debug!("LayoutTreeBuilder: pruning whitespace-only last child \ - flow f%d from parent f%d", - last_flow.id(), - p_id); - do_remove = true; + let last_box = boxes.last(); // FIXME(pcwalton): Rust bug + if last_box.is_whitespace_only() { + debug!("LayoutTreeBuilder: pruning whitespace-only last \ + child flow f%d from parent f%d", + last_inline_flow.base.id, + p_id); + do_remove = true; + } } } } @@ -584,16 +631,16 @@ impl LayoutTreeBuilder { // Issue 543: We only need to do this if there are inline child // flows, but there's not a quick way to check at the moment. - for child_flow in (*parent_flow).child_iter() { - match *child_flow { - InlineFlow(*) | InlineBlockFlow(*) => { + for child_flow in flow::child_iter(parent_flow) { + match child_flow.class() { + InlineFlowClass | InlineBlockFlowClass => { let mut scanner = TextRunScanner::new(); - scanner.scan_for_runs(ctx, child_flow); + scanner.scan_for_runs(ctx, *child_flow); } _ => {} } } - }, + } _ => {} } } @@ -605,29 +652,30 @@ impl LayoutTreeBuilder { /// Entry point for box creation. Should only be called on the root DOM element. pub fn construct_trees(&mut self, layout_ctx: &LayoutContext, root: AbstractNode<LayoutView>) - -> Result<FlowContext, ()> { + -> Result<~FlowContext:, ()> { debug!("Constructing flow tree for DOM: "); - root.dump(); + debug!("%?", root.dump()); - let mut new_flow = self.make_flow(Flow_Root, root); + let mut new_flow = self.make_flow(RootFlowType, root); { - let new_generator = BoxGenerator::new(&mut new_flow); + let new_generator = BoxGenerator::new(new_flow); self.construct_recursively(layout_ctx, root, None, new_generator, None); } return Ok(new_flow) } /// Creates a flow of the given type for the supplied node. - pub fn make_flow(&mut self, ty: FlowContextType, node: AbstractNode<LayoutView>) -> FlowContext { + pub fn make_flow(&mut self, flow_type: FlowType, node: AbstractNode<LayoutView>) + -> ~FlowContext: { let info = FlowData::new(self.next_flow_id(), node); - let result = match ty { - Flow_Absolute => AbsoluteFlow(~info), - Flow_Block => BlockFlow(~BlockFlowData::new(info)), - Flow_Float(f_type) => FloatFlow(~FloatFlowData::new(info, f_type)), - Flow_InlineBlock => InlineBlockFlow(~info), - Flow_Inline => InlineFlow(~InlineFlowData::new(info)), - Flow_Root => BlockFlow(~BlockFlowData::new_root(info)), - Flow_Table => TableFlow(~info), + let result = match flow_type { + AbsoluteFlowType => ~AbsoluteFlow::new(info) as ~FlowContext:, + BlockFlowType => ~BlockFlow::new(info) as ~FlowContext:, + FloatFlowType(f_type) => ~FloatFlow::new(info, f_type) as ~FlowContext:, + InlineBlockFlowType => ~InlineBlockFlow::new(info) as ~FlowContext:, + InlineFlowType => ~InlineFlow::new(info) as ~FlowContext:, + RootFlowType => ~BlockFlow::new_root(info) as ~FlowContext:, + TableFlowType => ~TableFlow::new(info) as ~FlowContext:, }; debug!("LayoutTreeBuilder: created flow: %s", result.debug_str()); result diff --git a/src/components/main/layout/display_list_builder.rs b/src/components/main/layout/display_list_builder.rs index 5dc6a7ebec2..ac0cfaf716a 100644 --- a/src/components/main/layout/display_list_builder.rs +++ b/src/components/main/layout/display_list_builder.rs @@ -4,7 +4,7 @@ //! Constructs display lists from render boxes. -use layout::box::RenderBox; +use layout::box::{RenderBox, RenderBoxUtils}; use layout::context::LayoutContext; use std::cast::transmute; use script::dom::node::AbstractNode; @@ -16,28 +16,28 @@ use style; /// that nodes in this view shoud not really be touched. The idea is to /// store the nodes in the display list and have layout transmute them. pub trait ExtraDisplayListData { - fn new(box: RenderBox) -> Self; + fn new(box: &@RenderBox) -> Self; } pub type Nothing = (); impl ExtraDisplayListData for AbstractNode<()> { - fn new (box: RenderBox) -> AbstractNode<()> { + fn new(box: &@RenderBox) -> AbstractNode<()> { unsafe { - transmute(box.node()) + transmute(box.base().node) } } } impl ExtraDisplayListData for Nothing { - fn new(_: RenderBox) -> Nothing { + fn new(_: &@RenderBox) -> Nothing { () } } -impl ExtraDisplayListData for RenderBox { - fn new(box: RenderBox) -> RenderBox { - box +impl ExtraDisplayListData for @RenderBox { + fn new(box: &@RenderBox) -> @RenderBox { + *box } } diff --git a/src/components/main/layout/float.rs b/src/components/main/layout/float.rs index 857f03dc4c5..df97ebf9112 100644 --- a/src/components/main/layout/float.rs +++ b/src/components/main/layout/float.rs @@ -2,10 +2,11 @@ * 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 layout::box::{RenderBox}; +use layout::box::{RenderBox, RenderBoxUtils}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; -use layout::flow::{FlowData}; +use layout::flow::{FloatFlowClass, FlowClass, FlowContext, FlowData}; +use layout::flow; use layout::model::{MaybeAuto}; use layout::float_context::{FloatContext, PlacementInfo, FloatType}; @@ -16,12 +17,12 @@ use gfx::display_list::DisplayList; use servo_util::geometry::Au; use servo_util::geometry; -pub struct FloatFlowData { +pub struct FloatFlow { /// Data common to all flows. - common: FlowData, + base: FlowData, /// The associated render box. - box: Option<RenderBox>, + box: Option<@RenderBox>, containing_width: Au, @@ -39,10 +40,10 @@ pub struct FloatFlowData { } -impl FloatFlowData { - pub fn new(common: FlowData, float_type: FloatType) -> FloatFlowData { - FloatFlowData { - common: common, +impl FloatFlow { + pub fn new(base: FlowData, float_type: FloatType) -> FloatFlow { + FloatFlow { + base: base, containing_width: Au(0), box: None, index: None, @@ -59,119 +60,157 @@ impl FloatFlowData { self.box = None; self.index = None; } + + pub fn build_display_list_float<E:ExtraDisplayListData>(&mut self, + builder: &DisplayListBuilder, + dirty: &Rect<Au>, + list: &Cell<DisplayList<E>>) + -> bool { + //TODO: implement iframe size messaging + if self.base.node.is_iframe_element() { + error!("float iframe size messaging not implemented yet"); + } + let abs_rect = Rect(self.base.abs_position, self.base.position.size); + if !abs_rect.intersects(dirty) { + return true; + } + + + let offset = self.base.abs_position + self.rel_pos; + // add box that starts block context + for box in self.box.iter() { + box.build_display_list(builder, dirty, &offset, list) + } + + + // 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; + } + + false + } } -impl FloatFlowData { - pub fn bubble_widths_float(&mut self, ctx: &LayoutContext) { +impl FlowContext for FloatFlow { + fn class(&self) -> FlowClass { + FloatFlowClass + } + + fn as_float<'a>(&'a mut self) -> &'a mut FloatFlow { + self + } + + 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 child_ctx in self.common.child_iter() { + for child_ctx in self.base.child_iter() { //assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); - do child_ctx.with_mut_base |child_node| { - min_width = geometry::max(min_width, child_node.min_width); - pref_width = geometry::max(pref_width, child_node.pref_width); - num_floats = num_floats + child_node.num_floats; - } + 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); + num_floats = num_floats + child_base.num_floats; } - self.common.num_floats = 1; + self.base.num_floats = 1; self.floated_children = num_floats; - for box in self.box.iter() { - let style = box.style(); - do box.with_model |model| { - model.compute_borders(style) + { + let base = box.base(); + base.model.mutate().ptr.compute_borders(base.style()); } - min_width = min_width.add(&box.get_min_width(ctx)); - pref_width = pref_width.add(&box.get_pref_width(ctx)); + 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; } - self.common.min_width = min_width; - self.common.pref_width = pref_width; + self.base.min_width = min_width; + self.base.pref_width = pref_width; } - pub fn assign_widths_float(&mut self) { - debug!("assign_widths_float: assigning width for flow %?", self.common.id); + fn assign_widths(&mut self, _: &mut LayoutContext) { + debug!("assign_widths_float: assigning width for flow %?", self.base.id); // position.size.width is set by parent even though we don't know // position.origin yet. - let mut remaining_width = self.common.position.size.width; + let mut remaining_width = self.base.position.size.width; self.containing_width = remaining_width; let mut x_offset = Au(0); // Parent usually sets this, but floats are never inorder - self.common.is_inorder = false; + self.base.is_inorder = false; for &box in self.box.iter() { - let style = box.style(); - do box.with_model |model| { - // Can compute padding here since we know containing block width. - model.compute_padding(style, remaining_width); + let base = box.base(); + let style = base.style(); + let mut position_ref = base.position.mutate(); + let mut model_ref = base.model.mutate(); + let (position, model) = (&mut position_ref.ptr, &mut model_ref.ptr); - // Margins for floats are 0 if auto. - let margin_top = MaybeAuto::from_style(style.Margin.margin_top, - remaining_width).specified_or_zero(); - let margin_bottom = MaybeAuto::from_style(style.Margin.margin_bottom, - remaining_width).specified_or_zero(); - let margin_left = MaybeAuto::from_style(style.Margin.margin_left, - remaining_width).specified_or_zero(); - let margin_right = MaybeAuto::from_style(style.Margin.margin_right, - remaining_width).specified_or_zero(); + // Can compute padding here since we know containing block width. + model.compute_padding(style, remaining_width); + // Margins for floats are 0 if auto. + let margin_top = MaybeAuto::from_style(style.Margin.margin_top, + remaining_width).specified_or_zero(); + let margin_bottom = MaybeAuto::from_style(style.Margin.margin_bottom, + remaining_width).specified_or_zero(); + let margin_left = MaybeAuto::from_style(style.Margin.margin_left, + remaining_width).specified_or_zero(); + let margin_right = MaybeAuto::from_style(style.Margin.margin_right, + remaining_width).specified_or_zero(); - let shrink_to_fit = geometry::min(self.common.pref_width, - geometry::max(self.common.min_width, - remaining_width)); + let shrink_to_fit = geometry::min(self.base.pref_width, + geometry::max(self.base.min_width, remaining_width)); - let width = MaybeAuto::from_style(style.Box.width, - remaining_width).specified_or_default(shrink_to_fit); - debug!("assign_widths_float -- width: %?", width); + let width = MaybeAuto::from_style(style.Box.width, + remaining_width).specified_or_default(shrink_to_fit); + debug!("assign_widths_float -- width: %?", width); - model.margin.top = margin_top; - model.margin.right = margin_right; - model.margin.bottom = margin_bottom; - model.margin.left = margin_left; + model.margin.top = margin_top; + model.margin.right = margin_right; + model.margin.bottom = margin_bottom; + model.margin.left = margin_left; - x_offset = model.offset(); - remaining_width = width; - } + x_offset = model.offset(); + remaining_width = width; - do box.with_mut_base |base| { - //The associated box is the border box of this flow - base.position.origin.x = base.model.margin.left; + // The associated box is the border box of this flow. + position.origin.x = model.margin.left; - let pb = base.model.padding.left + base.model.padding.right + - base.model.border.left + base.model.border.right; - base.position.size.width = remaining_width + pb; - } + let padding_and_borders = model.padding.left + model.padding.right + + model.border.left + model.border.right; + position.size.width = remaining_width + padding_and_borders; } - self.common.position.size.width = remaining_width; + self.base.position.size.width = remaining_width; - let has_inorder_children = self.common.num_floats > 0; - for kid in self.common.child_iter() { + let has_inorder_children = self.base.num_floats > 0; + for kid in self.base.child_iter() { //assert!(kid.starts_block_flow() || kid.starts_inline_flow()); - do kid.with_mut_base |child_node| { - child_node.position.origin.x = x_offset; - child_node.position.size.width = remaining_width; - child_node.is_inorder = has_inorder_children; + let child_base = flow::mut_base(*kid); + child_base.position.origin.x = x_offset; + child_base.position.size.width = remaining_width; + child_base.is_inorder = has_inorder_children; - if !child_node.is_inorder { - child_node.floats_in = FloatContext::new(0); - } + if !child_base.is_inorder { + child_base.floats_in = FloatContext::new(0); } } } - pub fn assign_height_inorder_float(&mut self) { - debug!("assign_height_inorder_float: assigning height for float %?", self.common.id); + fn assign_height_inorder(&mut self, _: &mut LayoutContext) { + debug!("assign_height_inorder_float: assigning height for float %?", self.base.id); // assign_height_float was already called by the traversal function // so this is well-defined @@ -181,28 +220,23 @@ impl FloatFlowData { let mut margin_height = Au(0); for box in self.box.iter() { - height = do box.with_base |base| { - base.position.size.height - }; - clearance = match box.clear() { + let base = box.base(); + height = base.position.borrow().ptr.size.height; + clearance = match base.clear() { None => Au(0), - Some(clear) => { - self.common.floats_in.clearance(clear) - } + Some(clear) => self.base.floats_in.clearance(clear), }; - do box.with_base |base| { - let noncontent_width = base.model.padding.left + base.model.padding.right + - base.model.border.left + base.model.border.right; - - full_noncontent_width = noncontent_width + base.model.margin.left + base.model.margin.right; - margin_height = base.model.margin.top + base.model.margin.bottom; - } + let model = base.model.get(); + let noncontent_width = model.padding.left + model.padding.right + model.border.left + + model.border.right; + full_noncontent_width = noncontent_width + model.margin.left + model.margin.right; + margin_height = model.margin.top + model.margin.bottom; } let info = PlacementInfo { - width: self.common.position.size.width + full_noncontent_width, + width: self.base.position.size.width + full_noncontent_width, height: height + margin_height, ceiling: clearance, max_width: self.containing_width, @@ -211,23 +245,19 @@ impl FloatFlowData { // Place the float and return the FloatContext back to the parent flow. // After, grab the position and use that to set our position. - self.common.floats_out = self.common.floats_in.add_float(&info); - self.rel_pos = self.common.floats_out.last_float_pos(); + self.base.floats_out = self.base.floats_in.add_float(&info); + self.rel_pos = self.base.floats_out.last_float_pos(); } - pub fn assign_height_float(&mut self, ctx: &mut LayoutContext) { - debug!("assign_height_float: assigning height for float %?", self.common.id); - let has_inorder_children = self.common.num_floats > 0; + fn assign_height(&mut self, ctx: &mut LayoutContext) { + debug!("assign_height_float: assigning height for float %?", self.base.id); + let has_inorder_children = self.base.num_floats > 0; if has_inorder_children { let mut float_ctx = FloatContext::new(self.floated_children); - for kid in self.common.child_iter() { - do kid.with_mut_base |child_node| { - child_node.floats_in = float_ctx.clone(); - } + for kid in self.base.child_iter() { + flow::mut_base(*kid).floats_in = float_ctx.clone(); kid.assign_height_inorder(ctx); - do kid.with_mut_base |child_node| { - float_ctx = child_node.floats_out.clone(); - } + float_ctx = flow::mut_base(*kid).floats_out.clone(); } } @@ -235,83 +265,54 @@ impl FloatFlowData { let mut top_offset = Au(0); for &box in self.box.iter() { - do box.with_model |model| { - top_offset = model.margin.top + model.border.top + model.padding.top; - cur_y = cur_y + top_offset; - } + let base = box.base(); + let model_ref = base.model.borrow(); + top_offset = model_ref.ptr.margin.top + model_ref.ptr.border.top + + model_ref.ptr.padding.top; + cur_y = cur_y + top_offset; } - for kid in self.common.child_iter() { - do kid.with_mut_base |child_node| { - child_node.position.origin.y = cur_y; - cur_y = cur_y + child_node.position.size.height; - }; + for kid in self.base.child_iter() { + let child_base = flow::mut_base(*kid); + child_base.position.origin.y = cur_y; + cur_y = cur_y + child_base.position.size.height; } let mut height = cur_y - top_offset; - let mut noncontent_width = Au(0); - let mut noncontent_height = Au(0); - for box in self.box.mut_iter() { - do box.with_mut_base |base| { - //The associated box is the border box of this flow - base.position.origin.y = base.model.margin.top; + let mut noncontent_height; + for box in self.box.iter() { + let base = box.base(); + let mut model_ref = base.model.mutate(); + let mut position_ref = base.position.mutate(); + let (model, position) = (&mut model_ref.ptr, &mut position_ref.ptr); - noncontent_width = base.model.padding.left + base.model.padding.right + - base.model.border.left + base.model.border.right; - noncontent_height = base.model.padding.top + base.model.padding.bottom + - base.model.border.top + base.model.border.bottom; - base.position.size.height = height + noncontent_height; + // The associated box is the border box of this flow. + position.origin.y = model.margin.top; - } - } + noncontent_height = model.padding.top + model.padding.bottom + model.border.top + + model.border.bottom; - //TODO(eatkinson): compute heights properly using the 'height' property. - for &box in self.box.iter() { - let height_prop = - MaybeAuto::from_style(box.style().Box.height, - Au(0)).specified_or_zero(); + //TODO(eatkinson): compute heights properly using the 'height' property. + let height_prop = MaybeAuto::from_style(base.style().Box.height, + Au::new(0)).specified_or_zero(); height = geometry::max(height, height_prop) + noncontent_height; debug!("assign_height_float -- height: %?", height); - do box.with_mut_base |base| { - base.position.size.height = height; - } - } - } - - pub fn build_display_list_float<E:ExtraDisplayListData>(&mut self, - builder: &DisplayListBuilder, - dirty: &Rect<Au>, - list: &Cell<DisplayList<E>>) - -> bool { - - //TODO: implement iframe size messaging - if self.common.node.is_iframe_element() { - error!("float iframe size messaging not implemented yet"); - } - let abs_rect = Rect(self.common.abs_position, self.common.position.size); - if !abs_rect.intersects(dirty) { - return true; - } - - - let offset = self.common.abs_position + self.rel_pos; - // add box that starts block context - for box in self.box.iter() { - box.build_display_list(builder, dirty, &offset, list) - } - - // TODO: handle any out-of-flow elements - // go deeper into the flow tree - for child in self.common.child_iter() { - do child.with_mut_base |base| { - base.abs_position = offset + base.position.origin; - } + position.size.height = height; } + } - false + fn collapse_margins(&mut self, + _: bool, + _: &mut bool, + _: &mut Au, + _: &mut Au, + collapsing: &mut Au, + _: &mut Au) { + // Margins between a floated box and any other box do not collapse. + *collapsing = Au::new(0); } } diff --git a/src/components/main/layout/float_context.rs b/src/components/main/layout/float_context.rs index e9f39586081..5786cf369ae 100644 --- a/src/components/main/layout/float_context.rs +++ b/src/components/main/layout/float_context.rs @@ -11,7 +11,7 @@ use std::vec; use std::i32::max_value; #[deriving(Clone)] -pub enum FloatType{ +pub enum FloatType { FloatLeft, FloatRight } @@ -22,11 +22,12 @@ pub enum ClearType { ClearBoth } -struct FloatContextBase{ - float_data: ~[Option<FloatData>], +struct FloatContextBase { + /// This is an option of a vector to avoid allocation in the fast path (no floats). + float_data: Option<~[Option<FloatData>]>, floats_used: uint, - max_y : Au, - offset: Point2D<Au> + max_y: Au, + offset: Point2D<Au>, } #[deriving(Clone)] @@ -48,12 +49,12 @@ pub struct PlacementInfo{ /// destroy the context on modification. pub enum FloatContext { Invalid, - Valid(~FloatContextBase) + Valid(FloatContextBase) } impl FloatContext { pub fn new(num_floats: uint) -> FloatContext { - Valid(~FloatContextBase::new(num_floats)) + Valid(FloatContextBase::new(num_floats)) } #[inline(always)] @@ -68,7 +69,7 @@ impl FloatContext { fn with_mut_base<R>(&mut self, callback: &fn(&mut FloatContextBase) -> R) -> R { match *self { Invalid => fail!("Float context no longer available"), - Valid(ref mut base) => callback(&mut **base) + Valid(ref mut base) => callback(&mut *base) } } @@ -76,7 +77,7 @@ impl FloatContext { pub fn with_base<R>(&self, callback: &fn(&FloatContextBase) -> R) -> R { match *self { Invalid => fail!("Float context no longer available"), - Valid(ref base) => callback(& **base) + Valid(ref base) => callback(&*base) } } @@ -128,9 +129,12 @@ impl FloatContext { impl FloatContextBase{ fn new(num_floats: uint) -> FloatContextBase { debug!("Creating float context of size %?", num_floats); - let new_data = vec::from_elem(num_floats, None); FloatContextBase { - float_data: new_data, + float_data: if num_floats == 0 { + None + } else { + Some(vec::from_elem(num_floats, None)) + }, floats_used: 0, max_y: Au(0), offset: Point2D(Au(0), Au(0)) @@ -144,7 +148,7 @@ impl FloatContextBase{ fn last_float_pos(&self) -> Point2D<Au> { assert!(self.floats_used > 0, "Error: tried to access FloatContext with no floats in it"); - match self.float_data[self.floats_used - 1] { + match self.float_data.get_ref()[self.floats_used - 1] { None => fail!("FloatContext error: floats should never be None here"), Some(float) => { debug!("Returning float position: %?", float.bounds.origin + self.offset); @@ -176,36 +180,38 @@ impl FloatContextBase{ let mut r_bottom = None; // Find the float collisions for the given vertical range. - for float in self.float_data.iter() { - debug!("available_rect: Checking for collision against float"); - match *float{ - None => (), - Some(data) => { - let float_pos = data.bounds.origin; - let float_size = data.bounds.size; - debug!("float_pos: %?, float_size: %?", float_pos, float_size); - match data.f_type { - FloatLeft => { - if(float_pos.x + float_size.width > max_left && - float_pos.y + float_size.height > top && float_pos.y < top + height) { - max_left = float_pos.x + float_size.width; - - l_top = Some(float_pos.y); - l_bottom = Some(float_pos.y + float_size.height); - - debug!("available_rect: collision with left float: new max_left is %?", - max_left); + for floats in self.float_data.iter() { + for float in floats.iter() { + debug!("available_rect: Checking for collision against float"); + match *float { + None => (), + Some(data) => { + let float_pos = data.bounds.origin; + let float_size = data.bounds.size; + debug!("float_pos: %?, float_size: %?", float_pos, float_size); + match data.f_type { + FloatLeft => { + if(float_pos.x + float_size.width > max_left && + float_pos.y + float_size.height > top && float_pos.y < top + height) { + max_left = float_pos.x + float_size.width; + + l_top = Some(float_pos.y); + l_bottom = Some(float_pos.y + float_size.height); + + debug!("available_rect: collision with left float: new max_left is %?", + max_left); + } } - } - FloatRight => { - if(float_pos.x < min_right && - float_pos.y + float_size.height > top && float_pos.y < top + height) { - min_right = float_pos.x; - - r_top = Some(float_pos.y); - r_bottom = Some(float_pos.y + float_size.height); - debug!("available_rect: collision with right float: new min_right is %?", - min_right); + FloatRight => { + if(float_pos.x < min_right && + float_pos.y + float_size.height > top && float_pos.y < top + height) { + min_right = float_pos.x; + + r_top = Some(float_pos.y); + r_bottom = Some(float_pos.y + float_size.height); + debug!("available_rect: collision with right float: new min_right is %?", + min_right); + } } } } @@ -242,9 +248,12 @@ impl FloatContextBase{ } fn add_float(&mut self, info: &PlacementInfo) { - debug!("Floats_used: %?, Floats available: %?", self.floats_used, self.float_data.len()); - assert!(self.floats_used < self.float_data.len() && - self.float_data[self.floats_used].is_none()); + assert!(self.float_data.is_some()); + debug!("Floats_used: %?, Floats available: %?", + self.floats_used, + self.float_data.get_ref().len()); + assert!(self.floats_used < self.float_data.get_ref().len() && + self.float_data.get_ref()[self.floats_used].is_none()); let new_info = PlacementInfo { width: info.width, @@ -263,22 +272,24 @@ impl FloatContextBase{ }, f_type: info.f_type }; - self.float_data[self.floats_used] = Some(new_float); + self.float_data.get_mut_ref()[self.floats_used] = Some(new_float); self.max_y = max(self.max_y, new_float.bounds.origin.y); self.floats_used += 1; } /// Returns true if the given rect overlaps with any floats. fn collides_with_float(&self, bounds: &Rect<Au>) -> bool { - for float in self.float_data.iter() { - match *float{ - None => (), - Some(data) => { - if data.bounds.translate(&self.offset).intersects(bounds) { - return true; + for floats in self.float_data.iter() { + for float in floats.iter() { + match *float { + None => (), + Some(data) => { + if data.bounds.translate(&self.offset).intersects(bounds) { + return true; + } } - } - }; + }; + } } return false; @@ -292,16 +303,18 @@ impl FloatContextBase{ let left = left - self.offset.x; let mut max_height = None; - for float in self.float_data.iter() { - match *float { - None => (), - Some(f_data) => { - if f_data.bounds.origin.y + f_data.bounds.size.height > top && - f_data.bounds.origin.x + f_data.bounds.size.width > left && - f_data.bounds.origin.x < left + width { - let new_y = f_data.bounds.origin.y; - max_height = Some(min(max_height.unwrap_or(new_y), new_y)); - } + for floats in self.float_data.iter() { + for float in floats.iter() { + match *float { + None => (), + Some(f_data) => { + if f_data.bounds.origin.y + f_data.bounds.size.height > top && + f_data.bounds.origin.x + f_data.bounds.size.width > left && + f_data.bounds.origin.x < left + width { + let new_y = f_data.bounds.origin.y; + max_height = Some(min(max_height.unwrap_or(new_y), new_y)); + } + } } } } @@ -361,19 +374,21 @@ impl FloatContextBase{ fn clearance(&self, clear: ClearType) -> Au { let mut clearance = Au(0); - for float in self.float_data.iter() { - match *float { - None => (), - Some(f_data) => { - match (clear, f_data.f_type) { - (ClearLeft, FloatLeft) | - (ClearRight, FloatRight) | - (ClearBoth, _) => { - clearance = max( - clearance, - self.offset.y + f_data.bounds.origin.y + f_data.bounds.size.height); + for floats in self.float_data.iter() { + for float in floats.iter() { + match *float { + None => (), + Some(f_data) => { + match (clear, f_data.f_type) { + (ClearLeft, FloatLeft) | + (ClearRight, FloatRight) | + (ClearBoth, _) => { + clearance = max( + clearance, + self.offset.y + f_data.bounds.origin.y + f_data.bounds.size.height); + } + _ => () } - _ => () } } } diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index 273272fbe04..472950b4a96 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -25,207 +25,275 @@ /// line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and /// similar methods. -use layout::block::BlockFlowData; -use layout::float::FloatFlowData; +use css::node_style::StyledNode; +use layout::block::BlockFlow; use layout::box::RenderBox; use layout::context::LayoutContext; +use layout::float::FloatFlow; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; -use layout::inline::{InlineFlowData}; -use layout::float_context::{FloatContext, Invalid, FloatType}; +use layout::float_context::{FloatContext, Invalid}; use layout::incremental::RestyleDamage; -use css::node_style::StyledNode; +use layout::inline::InlineFlow; + use extra::dlist::{DList,MutDListIterator}; use extra::container::Deque; - -use std::cell::Cell; -use std::io::stderr; use geom::point::Point2D; use geom::rect::Rect; use gfx::display_list::DisplayList; use servo_util::geometry::Au; use script::dom::node::{AbstractNode, LayoutView}; +use std::cast; +use std::cell::Cell; -/// The type of the formatting context and data specific to each context, such as line box -/// structures or float lists. -pub enum FlowContext { - AbsoluteFlow(~FlowData), - BlockFlow(~BlockFlowData), - FloatFlow(~FloatFlowData), - InlineBlockFlow(~FlowData), - InlineFlow(~InlineFlowData), - TableFlow(~FlowData), -} +/// Virtual methods that make up a float context. +/// +/// Note that virtual methods have a cost; we should not overuse them in Servo. Consider adding +/// methods to `ImmutableFlowUtils` or `MutableFlowUtils` before adding more methods here. +pub trait FlowContext { + // RTTI + // + // TODO(pcwalton): Use Rust's RTTI, once that works. -pub enum FlowContextType { - Flow_Absolute, - Flow_Block, - Flow_Float(FloatType), - Flow_InlineBlock, - Flow_Inline, - Flow_Root, - Flow_Table -} + /// Returns the class of flow that this is. + fn class(&self) -> FlowClass; -impl FlowContext { - pub fn each_bu_sub_inorder (&mut self, callback: &fn(&mut FlowContext) -> bool) -> bool { - for kid in self.child_iter() { - // FIXME: Work around rust#2202. We should be able to pass the callback directly. - if !kid.each_bu_sub_inorder(|a| callback(a)) { - return false; - } - } + /// If this is a block flow, returns the underlying object. Fails otherwise. + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + fail!("called as_block() on a non-block flow") + } - if !self.is_inorder() { - callback(self) - } else { - true - } + /// If this is an inline flow, returns the underlying object, borrowed immutably. Fails + /// otherwise. + fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow { + fail!("called as_immutable_inline() on a non-inline flow") } - pub fn each_preorder_prune(&mut self, prune: &fn(&mut FlowContext) -> bool, - callback: &fn(&mut FlowContext) -> bool) - -> bool { - if prune(self) { - return true; - } + /// If this is an inline flow, returns the underlying object. Fails otherwise. + fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow { + fail!("called as_inline() on a non-inline flow") + } - if !callback(self) { - return false; - } + /// If this is a float flow, returns the underlying object. Fails otherwise. + fn as_float<'a>(&'a mut self) -> &'a mut FloatFlow { + fail!("called as_float() on a non-float flow") + } - for kid in self.child_iter() { - // FIXME: Work around rust#2202. We should be able to pass the callback directly. - if !kid.each_preorder_prune(|a| prune(a), |a| callback(a)) { - return false; - } - } + // Main methods - true + /// Pass 1 of reflow: computes minimum and preferred widths. + fn bubble_widths(&mut self, _ctx: &mut LayoutContext) { + fail!("bubble_widths not yet implemented") } - pub fn each_postorder_prune(&mut self, prune: &fn(&mut FlowContext) -> bool, - callback: &fn(&mut FlowContext) -> bool) - -> bool { - if prune(self) { - return true; - } + /// Pass 2 of reflow: computes width. + fn assign_widths(&mut self, _ctx: &mut LayoutContext) { + fail!("assign_widths not yet implemented") + } - for kid in self.child_iter() { - // FIXME: Work around rust#2202. We should be able to pass the callback directly. - if !kid.each_postorder_prune(|a| prune(a), |a| callback(a)) { - return false; - } - } + /// Pass 3 of reflow: computes height. + fn assign_height(&mut self, _ctx: &mut LayoutContext) { + fail!("assign_height not yet implemented") + } - callback(self) + /// In-order version of pass 3 of reflow: computes heights with floats present. + fn assign_height_inorder(&mut self, _ctx: &mut LayoutContext) { + fail!("assign_height_inorder not yet implemented") } - pub fn each_preorder(&mut self, callback: &fn(&mut FlowContext) -> bool) -> bool { - self.each_preorder_prune(|_| false, callback) + /// 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") } - pub fn each_postorder(&mut self, callback: &fn(&mut FlowContext) -> bool) -> bool { - self.each_postorder_prune(|_| false, callback) + /// Returns a debugging string describing this flow. + fn debug_str(&self) -> ~str { + ~"???" } } -impl<'self> FlowContext { - pub fn is_block_like(&self) -> bool { - match *self { - BlockFlow(*) | FloatFlow(*) => true, - _ => false, - } +// Base access + +#[inline(always)] +pub fn base<'a>(this: &'a FlowContext) -> &'a FlowData { + unsafe { + let (_, ptr): (uint, &FlowData) = cast::transmute(this); + ptr } +} - pub fn is_leaf(&self) -> bool { - do self.with_base |base| { - base.children.len() == 0 - } +#[inline(always)] +pub fn mut_base<'a>(this: &'a mut FlowContext) -> &'a mut FlowData { + unsafe { + let (_, ptr): (uint, &mut FlowData) = cast::transmute(this); + ptr } +} - pub fn add_new_child(&mut self, new_child: FlowContext) { - let cell = Cell::new(new_child); - do self.with_mut_base |base| { - base.children.push_back(cell.take()); +/// Returns the last child of this flow. +pub fn last_child<'a>(flow: &'a mut FlowContext) -> Option<&'a mut ~FlowContext:> { + mut_base(flow).children.back_mut() +} + +/// Iterates over the children of this flow. +pub fn child_iter<'a>(flow: &'a mut FlowContext) -> MutDListIterator<'a,~FlowContext:> { + mut_base(flow).children.mut_iter() +} + +pub trait ImmutableFlowUtils { + // Convenience functions + + /// Returns true if this flow is a block or a float flow. + fn is_block_like(self) -> bool; + + /// Returns true if this flow has no children. + fn is_leaf(self) -> bool; + + /// Returns true if this flow is a block flow, an inline flow, or a float flow. + fn starts_block_flow(self) -> bool; + + /// Returns true if this flow is an inline flow. + fn starts_inline_flow(self) -> bool; + + /// Dumps the flow tree for debugging. + fn dump(self); +} + +pub trait MutableFlowUtils { + // Traversals + + /// Traverses the tree in preorder. + fn traverse_preorder<T:PreorderFlowTraversal>(self, traversal: &mut T) -> bool; + + /// Traverses the tree in postorder. + fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &mut T) -> bool; + + // Mutators + + /// Adds a new flow as a child of this flow. + fn add_new_child(self, new_child: ~FlowContext:); + + /// Invokes a closure with the first child of this flow. + fn with_first_child<R>(self, f: &fn(Option<&mut ~FlowContext:>) -> R) -> R; + + /// Invokes a closure with the last child of this flow. + fn with_last_child<R>(self, f: &fn(Option<&mut ~FlowContext:>) -> R) -> R; + + /// Removes the first child of this flow and destroys it. + fn remove_first(self); + + /// Removes the last child of this flow and destroys it. + fn remove_last(self); + + /// Builds a display list for this flow and its children. + fn build_display_list<E:ExtraDisplayListData>( + self, + builder: &DisplayListBuilder, + dirty: &Rect<Au>, + list: &Cell<DisplayList<E>>) + -> bool; +} + +pub enum FlowClass { + AbsoluteFlowClass, + BlockFlowClass, + FloatFlowClass, + InlineBlockFlowClass, + InlineFlowClass, + TableFlowClass, +} + +// Miscellaneous flows that are not yet implemented. + +pub struct AbsoluteFlow { + base: FlowData, +} + +impl AbsoluteFlow { + pub fn new(base: FlowData) -> AbsoluteFlow { + AbsoluteFlow { + base: base, } } +} - pub fn with_first_child<R>(&mut self, cb: &fn(Option<&mut FlowContext>) -> R) -> R { - do self.with_mut_base |base| { - cb(base.children.front_mut()) - } +impl FlowContext for AbsoluteFlow { + fn class(&self) -> FlowClass { + AbsoluteFlowClass } +} + +pub struct InlineBlockFlow { + base: FlowData, +} - pub fn with_last_child<R>(&mut self, cb: &fn(Option<&mut FlowContext>) -> R) -> R { - do self.with_mut_base |base| { - cb(base.children.back_mut()) +impl InlineBlockFlow { + pub fn new(base: FlowData) -> InlineBlockFlow { + InlineBlockFlow { + base: base, } } +} - pub fn last_child(&'self mut self) -> Option<&'self mut FlowContext> { - self.mut_base().children.back_mut() +impl FlowContext for InlineBlockFlow { + fn class(&self) -> FlowClass { + InlineBlockFlowClass } +} - pub fn remove_first(&mut self) { - do self.with_mut_base |base| { - base.children.pop_front(); - } - } +pub struct TableFlow { + base: FlowData, +} - pub fn remove_last(&mut self) { - do self.with_mut_base |base| { - base.children.pop_back(); +impl TableFlow { + pub fn new(base: FlowData) -> TableFlow { + TableFlow { + base: base, } } +} - pub fn child_iter<'a>(&'a mut self) -> MutDListIterator<'a, FlowContext> { - self.mut_base().children.mut_iter() +impl FlowContext for TableFlow { + fn class(&self) -> FlowClass { + TableFlowClass } - } -impl<'self> FlowContext { - pub fn with_base<R>(&self, callback: &fn(&FlowData) -> R) -> R { - match *self { - AbsoluteFlow(ref info) => callback(&**info), - BlockFlow(ref info) => { - callback(&info.common) - } - FloatFlow(ref info) => callback(&info.common), - InlineBlockFlow(ref info) => callback(&**info), - InlineFlow(ref info) => { - callback(&info.common) - } - TableFlow(ref info) => callback(&**info) - } +/// A top-down traversal. +pub trait PreorderFlowTraversal { + /// The operation to perform. Return true to continue or false to stop. + fn process(&mut self, flow: &mut FlowContext) -> bool; + + /// Returns true if this node should be pruned. If this returns true, we skip the operation + /// entirely and do not process any descendant nodes. This is called *before* child nodes are + /// visited. The default implementation never prunes any nodes. + fn should_prune(&mut self, _flow: &mut FlowContext) -> bool { + false } - pub fn with_mut_base<R>(&mut self, callback: &fn(&mut FlowData) -> R) -> R { - match *self { - AbsoluteFlow(ref mut info) => callback(&mut **info), - BlockFlow(ref mut info) => { - callback(&mut info.common) - } - FloatFlow(ref mut info) => callback(&mut info.common), - InlineBlockFlow(ref mut info) => callback(&mut **info), - InlineFlow(ref mut info) => { - callback(&mut info.common) - } - TableFlow(ref mut info) => callback(&mut **info), - } +} + +/// A bottom-up traversal, with a optional in-order pass. +pub trait PostorderFlowTraversal { + /// The operation to perform. Return true to continue or false to stop. + fn process(&mut self, flow: &mut FlowContext) -> bool; + + /// Returns false if this node must be processed in-order. If this returns false, we skip the + /// operation for this node, but continue processing the descendants. This is called *after* + /// child nodes are visited. + fn should_process(&mut self, _flow: &mut FlowContext) -> bool { + true } - pub fn mut_base(&'self mut self) -> &'self mut FlowData { - match *self { - AbsoluteFlow(ref mut info) => &mut(**info), - BlockFlow(ref mut info) => { - &mut info.common - } - FloatFlow(ref mut info) => &mut info.common, - InlineBlockFlow(ref mut info) => &mut(**info), - InlineFlow(ref mut info) => { - &mut info.common - } - TableFlow(ref mut info) => &mut(**info), - } + + /// Returns true if this node should be pruned. If this returns true, we skip the operation + /// entirely and do not process any descendant nodes. This is called *before* child nodes are + /// visited. The default implementation never prunes any nodes. + fn should_prune(&mut self, _flow: &mut FlowContext) -> bool { + false } } @@ -237,7 +305,7 @@ pub struct FlowData { node: AbstractNode<LayoutView>, restyle_damage: RestyleDamage, - children: DList<FlowContext>, + children: DList<~FlowContext:>, /* TODO (Issue #87): debug only */ id: int, @@ -256,11 +324,12 @@ pub struct FlowData { } pub struct BoxIterator { - priv boxes: ~[RenderBox], + priv boxes: ~[@RenderBox], priv index: uint, } -impl Iterator<RenderBox> for BoxIterator { - fn next(&mut self) -> Option<RenderBox> { + +impl Iterator<@RenderBox> for BoxIterator { + fn next(&mut self) -> Option<@RenderBox> { if self.index >= self.boxes.len() { None } else { @@ -270,6 +339,7 @@ impl Iterator<RenderBox> for BoxIterator { } } } + impl FlowData { pub fn new(id: int, node: AbstractNode<LayoutView>) -> FlowData { FlowData { @@ -280,237 +350,135 @@ impl FlowData { id: id, - min_width: Au(0), - pref_width: Au(0), + min_width: Au::new(0), + pref_width: Au::new(0), position: Au::zero_rect(), floats_in: Invalid, floats_out: Invalid, num_floats: 0, - abs_position: Point2D(Au(0), Au(0)), + abs_position: Point2D(Au::new(0), Au::new(0)), is_inorder: false } } - pub fn child_iter<'a>(&'a mut self) -> MutDListIterator<'a, FlowContext> { + pub fn child_iter<'a>(&'a mut self) -> MutDListIterator<'a,~FlowContext:> { self.children.mut_iter() } - } -impl<'self> FlowContext { - /// A convenience method to return the position of this flow. Fails if the flow is currently - /// being borrowed mutably. - #[inline(always)] - pub fn position(&self) -> Rect<Au> { - do self.with_base |common_info| { - common_info.position - } - } - - #[inline(always)] - pub fn is_inorder(&self) -> bool { - do self.with_base |common_info| { - common_info.is_inorder +impl<'self> ImmutableFlowUtils for &'self FlowContext { + /// Returns true if this flow is a block or a float flow. + fn is_block_like(self) -> bool { + match self.class() { + BlockFlowClass | FloatFlowClass => true, + AbsoluteFlowClass | InlineBlockFlowClass | InlineFlowClass | TableFlowClass => false, } } - /// A convenience method to return the ID of this flow. Fails if the flow is currently being - /// borrowed mutably. - #[inline(always)] - pub fn id(&self) -> int { - do self.with_base |info| { - info.id - } + /// Returns true if this flow has no children. + fn is_leaf(self) -> bool { + base(self).children.len() == 0 } - pub fn inline(&'self mut self) -> &'self mut InlineFlowData { - match *self { - InlineFlow(ref mut info) => &mut (**info), - _ => fail!(fmt!("Tried to access inline data of non-inline: f%d", self.id())) + /// Returns true if this flow is a block flow, an inline-block flow, or a float flow. + fn starts_block_flow(self) -> bool { + match self.class() { + BlockFlowClass | InlineBlockFlowClass | FloatFlowClass => true, + AbsoluteFlowClass | InlineFlowClass | TableFlowClass => false, } } - pub fn imm_inline(&'self self) -> &'self InlineFlowData { - match *self { - InlineFlow(ref info) => &**info, - _ => fail!(fmt!("Tried to access inline data of non-inline: f%d", self.id())) + /// Returns true if this flow is a block flow, an inline flow, or a float flow. + fn starts_inline_flow(self) -> bool { + match self.class() { + InlineFlowClass => true, + AbsoluteFlowClass | BlockFlowClass | FloatFlowClass | InlineBlockFlowClass | + TableFlowClass => false, } } - pub fn block(&'self mut self) -> &'self mut BlockFlowData { - match *self { - BlockFlow(ref mut info) => &mut (**info), - _ => fail!(fmt!("Tried to access block data of non-block: f%d", self.id())) - } + /// Dumps the flow tree for debugging. + fn dump(self) { + // TODO(pcwalton): Fill this in. } +} - pub fn root(&'self mut self) -> &'self mut BlockFlowData { - match *self { - BlockFlow(ref mut info) if info.is_root => &mut (**info), - _ => fail!(fmt!("Tried to access root block data of non-root: f%d", self.id())) +impl<'self> MutableFlowUtils for &'self mut FlowContext { + /// Traverses the tree in preorder. + fn traverse_preorder<T:PreorderFlowTraversal>(self, traversal: &mut T) -> bool { + if traversal.should_prune(self) { + return true } - } - pub fn bubble_widths(&mut self, ctx: &mut LayoutContext) { - - debug!("FlowContext: bubbling widths for f%?", self.id()); - match *self { - BlockFlow(ref mut info) => info.bubble_widths_block(ctx), - InlineFlow(ref mut info) => info.bubble_widths_inline(ctx), - FloatFlow(ref mut info) => info.bubble_widths_float(ctx), - _ => fail!(fmt!("Tried to bubble_widths of flow: f%d", self.id())) + if !traversal.process(self) { + return false } - } - pub fn assign_widths(&mut self, ctx: &mut LayoutContext) { - - debug!("FlowContext: assigning widths for f%?", self.id()); - match *self { - BlockFlow(ref mut info) => info.assign_widths_block(ctx), - InlineFlow(ref mut info) => info.assign_widths_inline(ctx), - FloatFlow(ref mut info) => info.assign_widths_float(), - _ => fail!(fmt!("Tried to assign_widths of flow: f%d", self.id())) + for kid in child_iter(self) { + if !kid.traverse_preorder(traversal) { + return false + } } - } - - pub fn assign_height(&mut self, ctx: &mut LayoutContext) { - debug!("FlowContext: assigning height for f%?", self.id()); - match *self { - BlockFlow(ref mut info) => info.assign_height_block(ctx), - InlineFlow(ref mut info) => info.assign_height_inline(ctx), - FloatFlow(ref mut info) => info.assign_height_float(ctx), - _ => fail!(fmt!("Tried to assign_height of flow: f%d", self.id())) - } + true } - pub fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { - match *self { - BlockFlow(ref mut info) => info.assign_height_inorder_block(ctx), - InlineFlow(ref mut info) => info.assign_height_inorder_inline(ctx), - FloatFlow(ref mut info) => info.assign_height_inorder_float(), - _ => fail!(fmt!("Tried to assign_height of flow: f%d", self.id())) + /// Traverses the tree in postorder. + fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &mut T) -> bool { + if traversal.should_prune(self) { + return true } - } - pub fn build_display_list<E:ExtraDisplayListData>(&mut self, - builder: &DisplayListBuilder, - dirty: &Rect<Au>, - list: &Cell<DisplayList<E>>) - -> bool { - - - debug!("FlowContext: building display list for f%?", self.id()); - match *self { - BlockFlow(ref mut info) => info.build_display_list_block(builder, dirty, list), - InlineFlow(ref mut info) => info.build_display_list_inline(builder, dirty, list), - FloatFlow(ref mut info) => info.build_display_list_float(builder, dirty, list), - _ => { - fail!("Tried to build_display_list_recurse of flow: %?", self) + for kid in child_iter(self) { + if !kid.traverse_postorder(traversal) { + return false } } - } - /// A convenience method to return the restyle damage of this flow. Fails if the flow is - /// currently being borrowed mutably. - #[inline(always)] - pub fn restyle_damage(&self) -> RestyleDamage { - do self.with_base |info| { - info.restyle_damage + if !traversal.should_process(self) { + return true } - } - - // Actual methods that do not require much flow-specific logic - pub fn foldl_all_boxes<B:Clone>(&mut self, seed: B, cb: &fn(a: B, b: RenderBox) -> B) -> B { - match *self { - BlockFlow(ref mut block) => { - do block.box.as_ref().map_default(seed.clone()) |box| { - cb(seed.clone(), *box) - } - } - InlineFlow(ref mut inline) => { - do inline.boxes.iter().fold(seed) |acc, box| { - cb(acc.clone(), *box) - } - } - _ => fail!(fmt!("Don't know how to iterate node's RenderBoxes for %?", self)), - } + traversal.process(self) } - pub fn foldl_boxes_for_node<B:Clone>(&mut self, - node: AbstractNode<LayoutView>, - seed: B, - callback: &fn(a: B, RenderBox) -> B) - -> B { - do self.foldl_all_boxes(seed) |acc, box| { - if box.node() == node { - callback(acc, box) - } else { - acc - } - } + /// Adds a new flow as a child of this flow. + fn add_new_child(self, new_child: ~FlowContext:) { + mut_base(self).children.push_back(new_child) } - pub fn iter_all_boxes(&mut self) -> BoxIterator { - BoxIterator { - boxes: match *self { - BlockFlow(ref mut block) => block.box.as_ref().map_default(~[], |&x| ~[x]), - InlineFlow(ref mut inline) => inline.boxes.clone(), - _ => fail!(fmt!("Don't know how to iterate node's RenderBoxes for %?", self)) - }, - index: 0, - } + /// Invokes a closure with the first child of this flow. + fn with_first_child<R>(self, f: &fn(Option<&mut ~FlowContext:>) -> R) -> R { + f(mut_base(self).children.front_mut()) } - /// Dumps the flow tree for debugging. - pub fn dump(&mut self) { - self.dump_indent(0); + /// Invokes a closure with the last child of this flow. + fn with_last_child<R>(self, f: &fn(Option<&mut ~FlowContext:>) -> R) -> R { + f(mut_base(self).children.back_mut()) } - /// Dumps the flow tree, for debugging, with indentation. - pub fn dump_indent(&mut self, indent: uint) { - let mut s = ~"|"; - for _ in range(0, indent) { - s.push_str("---- "); - } - - s.push_str(self.debug_str()); - stderr().write_line(s); + /// Removes the first child of this flow and destroys it. + fn remove_first(self) { + let _ = mut_base(self).children.pop_front(); + } - // FIXME: this should have a pure/const version? - for child in self.child_iter() { - child.dump_indent(indent + 1) - } + /// Removes the last child of this flow and destroys it. + fn remove_last(self) { + let _ = mut_base(self).children.pop_back(); } - - pub fn debug_str(&self) -> ~str { - let repr = match *self { - InlineFlow(ref inline) => { - let mut s = inline.boxes.iter().fold(~"InlineFlow(children=", |s, box| { - fmt!("%s b%d", s, box.id()) - }); - s.push_str(")"); - s - }, - BlockFlow(ref block) => { - match block.box { - Some(box) => fmt!("BlockFlow(box=b%d)", box.id()), - None => ~"BlockFlow", - } - }, - FloatFlow(ref float) => { - match float.box { - Some(box) => fmt!("FloatFlow(box=b%d)", box.id()), - None => ~"FloatFlow", - } - }, - _ => ~"(Unknown flow)" - }; - - do self.with_base |base| { - fmt!("f%? %? floats %? size %? damage %?", base.id, repr, base.num_floats, - base.position, base.restyle_damage) + + fn build_display_list<E:ExtraDisplayListData>( + self, + builder: &DisplayListBuilder, + dirty: &Rect<Au>, + list: &Cell<DisplayList<E>>) + -> bool { + debug!("FlowContext: building display list for f%?", base(self).id); + match self.class() { + BlockFlowClass => self.as_block().build_display_list_block(builder, dirty, list), + InlineFlowClass => self.as_inline().build_display_list_inline(builder, dirty, list), + FloatFlowClass => self.as_float().build_display_list_float(builder, dirty, list), + _ => fail!("Tried to build_display_list_recurse of flow: %?", self), } } } diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index cad2ec09a2f..037b782edf0 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -3,28 +3,27 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use css::node_style::StyledNode; -use std::cell::Cell; use layout::box::{CannotSplit, GenericRenderBoxClass, ImageRenderBoxClass, RenderBox}; -use layout::box::{SplitDidFit, SplitDidNotFit, TextRenderBoxClass}; +use layout::box::{RenderBoxUtils, SplitDidFit, SplitDidNotFit, TextRenderBoxClass}; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; -use layout::flow::{FlowContext, FlowData, InlineFlow}; +use layout::flow::{FlowClass, FlowContext, FlowData, InlineFlowClass}; +use layout::flow; use layout::float_context::FloatContext; use layout::util::{ElementMapping}; use layout::float_context::{PlacementInfo, FloatLeft}; -use std::u16; -use std::util; +use extra::container::Deque; +use extra::ringbuf::RingBuf; use geom::{Point2D, Rect, Size2D}; use gfx::display_list::DisplayList; -use servo_util::geometry::Au; -use style::computed_values::line_height; use style::computed_values::text_align; use style::computed_values::vertical_align; +use servo_util::geometry::Au; use servo_util::range::Range; -use servo_util::tree::TreeNodeRef; -use extra::container::Deque; -use extra::ringbuf::RingBuf; +use std::cell::Cell; +use std::u16; +use std::util; /* Lineboxes are represented as offsets into the child list, rather than @@ -62,13 +61,15 @@ struct LineBox { struct LineboxScanner { floats: FloatContext, - new_boxes: ~[RenderBox], - work_list: @mut RingBuf<RenderBox>, + new_boxes: ~[@RenderBox], + work_list: @mut RingBuf<@RenderBox>, pending_line: LineBox, lines: ~[LineBox], cur_y: Au, } +local_data_key!(local_linebox_scanner: LineboxScanner) + impl LineboxScanner { pub fn new(float_ctx: FloatContext) -> LineboxScanner { LineboxScanner { @@ -77,33 +78,45 @@ impl LineboxScanner { work_list: @mut RingBuf::new(), pending_line: LineBox { range: Range::empty(), - bounds: Rect(Point2D(Au(0), Au(0)), Size2D(Au(0), Au(0))), - green_zone: Size2D(Au(0), Au(0)) + bounds: Rect(Point2D(Au::new(0), Au::new(0)), Size2D(Au::new(0), Au::new(0))), + green_zone: Size2D(Au::new(0), Au::new(0)) }, lines: ~[], - cur_y: Au(0) + cur_y: Au::new(0) } } + + fn reinitialize(&mut self, float_ctx: FloatContext) { + self.floats = float_ctx; + self.new_boxes.truncate(0); + self.work_list.clear(); + self.pending_line.range = Range::empty(); + self.pending_line.bounds = Rect(Point2D(Au::new(0), Au::new(0)), + Size2D(Au::new(0), Au::new(0))); + self.pending_line.green_zone = Size2D(Au::new(0), Au::new(0)); + self.lines.truncate(0); + self.cur_y = Au::new(0); + } pub fn floats_out(&mut self) -> FloatContext { self.floats.clone() } - fn reset_scanner(&mut self, flow: &mut InlineFlowData) { - debug!("Resetting line box scanner's state for flow f%d.", flow.common.id); + fn reset_scanner(&mut self, flow: &mut InlineFlow) { + debug!("Resetting line box scanner's state for flow f%d.", flow.base.id); self.lines = ~[]; self.new_boxes = ~[]; - self.cur_y = Au(0); + self.cur_y = Au::new(0); self.reset_linebox(); } fn reset_linebox(&mut self) { self.pending_line.range.reset(0,0); - self.pending_line.bounds = Rect(Point2D(Au(0), self.cur_y), Size2D(Au(0), Au(0))); - self.pending_line.green_zone = Size2D(Au(0), Au(0)) + self.pending_line.bounds = Rect(Point2D(Au::new(0), self.cur_y), Size2D(Au::new(0), Au::new(0))); + self.pending_line.green_zone = Size2D(Au::new(0), Au::new(0)) } - pub fn scan_for_lines(&mut self, flow: &mut InlineFlowData) { + pub fn scan_for_lines(&mut self, flow: &mut InlineFlow) { self.reset_scanner(flow); let mut i = 0u; @@ -115,11 +128,11 @@ impl LineboxScanner { break } let box = flow.boxes[i]; i += 1; - debug!("LineboxScanner: Working with box from box list: b%d", box.id()); + debug!("LineboxScanner: Working with box from box list: b%d", box.base().id()); box } else { - let box = self.work_list.pop_front().unwrap(); - debug!("LineboxScanner: Working with box from work list: b%d", box.id()); + let box = self.work_list.pop_front().unwrap(); + debug!("LineboxScanner: Working with box from work list: b%d", box.base().id()); box }; @@ -139,16 +152,15 @@ impl LineboxScanner { self.flush_current_line(); } - flow.elems.repair_for_box_changes(flow.boxes, self.new_boxes); self.swap_out_results(flow); } - fn swap_out_results(&mut self, flow: &mut InlineFlowData) { + fn swap_out_results(&mut self, flow: &mut InlineFlow) { debug!("LineboxScanner: Propagating scanned lines[n=%u] to inline flow f%d", self.lines.len(), - flow.common.id); + flow.base.id); util::swap(&mut flow.boxes, &mut self.new_boxes); util::swap(&mut flow.lines, &mut self.lines); @@ -165,47 +177,10 @@ impl LineboxScanner { self.reset_linebox(); } - fn calculate_line_height(&self, box: RenderBox, font_size: Au) -> Au { - match box.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 - } - } - - fn box_height(&self, box: RenderBox) -> Au { - match box { - ImageRenderBoxClass(image_box) => { - let size = image_box.image.get_size(); - let height = Au::from_px(size.unwrap_or(Size2D(0, 0)).height); - image_box.base.position.size.height = height; - debug!("box_height: found image height: %?", height); - height - } - TextRenderBoxClass(text_box) => { - let range = &text_box.range; - let run = &text_box.run; - - // Compute the height based on the line-height and font size - let text_bounds = run.metrics_for_range(range).bounding_box; - let em_size = text_bounds.size.height; - let line_height = self.calculate_line_height(box, em_size); - - line_height - } - GenericRenderBoxClass(_) => { - Au(0) - } - _ => { - fail!(fmt!("Tried to get height of unknown Box variant: %s", box.debug_str())) - } - } - } - // FIXME(eatkinson): this assumes that the tallest box in the line determines the line height // This might not be the case with some weird text fonts. - fn new_height_for_line(&self, new_box: RenderBox) -> Au { - let box_height = self.box_height(new_box); + fn new_height_for_line(&self, new_box: &RenderBox) -> Au { + let box_height = new_box.box_height(); if box_height > self.pending_line.bounds.size.height { box_height } else { @@ -216,9 +191,12 @@ impl LineboxScanner { /// Computes the position of a line that has only the provided RenderBox. /// Returns: the bounding rect of the line's green zone (whose origin coincides /// with the line's origin) and the actual width of the first box after splitting. - fn initial_line_placement (&self, first_box: RenderBox, ceiling: Au, flow: &mut InlineFlowData) -> (Rect<Au>, Au) { + fn initial_line_placement(&self, first_box: @RenderBox, ceiling: Au, flow: &mut InlineFlow) + -> (Rect<Au>, Au) { debug!("LineboxScanner: Trying to place first box of line %?", self.lines.len()); - debug!("LineboxScanner: box size: %?", first_box.position().size); + + let first_box_size = first_box.base().position.get().size; + debug!("LineboxScanner: box size: %?", first_box_size); let splitable = first_box.can_split(); let line_is_empty: bool = self.pending_line.range.length() == 0; @@ -226,34 +204,36 @@ impl LineboxScanner { // We will move it later if it has nonzero width // and that causes problems. let placement_width = if splitable { - Au(0) + Au::new(0) } else { - first_box.position().size.width + first_box_size.width }; let mut info = PlacementInfo { width: placement_width, - height: first_box.position().size.height, + height: first_box_size.height, ceiling: ceiling, - max_width: flow.common.position.size.width, + max_width: flow.base.position.size.width, f_type: FloatLeft }; let line_bounds = self.floats.place_between_floats(&info); - debug!("LineboxScanner: found position for line: %? using placement_info: %?", line_bounds, info); + debug!("LineboxScanner: found position for line: %? using placement_info: %?", + line_bounds, + info); // Simple case: if the box fits, then we can stop here - if line_bounds.size.width > first_box.position().size.width { + if line_bounds.size.width > first_box_size.width { debug!("LineboxScanner: case=box fits"); - return (line_bounds, first_box.position().size.width); + return (line_bounds, first_box_size.width); } // If not, but we can't split the box, then we'll place // the line here and it will overflow. if !splitable { debug!("LineboxScanner: case=line doesn't fit, but is unsplittable"); - return (line_bounds, first_box.position().size.width); + return (line_bounds, first_box_size.width); } // Otherwise, try and split the box @@ -264,15 +244,15 @@ impl LineboxScanner { CannotSplit(_) => { error!("LineboxScanner: Tried to split unsplittable render box! %s", first_box.debug_str()); - return (line_bounds, first_box.position().size.width); + return (line_bounds, first_box_size.width); } SplitDidFit(left, right) => { debug!("LineboxScanner: case=box split and fit"); let actual_box_width = match (left, right) { - (Some(l_box), Some(_)) => l_box.position().size.width, - (Some(l_box), None) => l_box.position().size.width, - (None, Some(r_box)) => r_box.position().size.width, + (Some(l_box), Some(_)) => l_box.base().position.get().size.width, + (Some(l_box), None) => l_box.base().position.get().size.width, + (None, Some(r_box)) => r_box.base().position.get().size.width, (None, None) => fail!("This case makes no sense.") }; return (line_bounds, actual_box_width); @@ -284,9 +264,9 @@ impl LineboxScanner { debug!("LineboxScanner: case=box split and fit didn't fit; trying to push it down"); let actual_box_width = match (left, right) { - (Some(l_box), Some(_)) => l_box.position().size.width, - (Some(l_box), None) => l_box.position().size.width, - (None, Some(r_box)) => r_box.position().size.width, + (Some(l_box), Some(_)) => l_box.base().position.get().size.width, + (Some(l_box), None) => l_box.base().position.get().size.width, + (None, Some(r_box)) => r_box.base().position.get().size.width, (None, None) => fail!("This case makes no sense.") }; @@ -301,7 +281,7 @@ impl LineboxScanner { } /// Returns false only if we should break the line. - fn try_append_to_line(&mut self, in_box: RenderBox, flow: &mut InlineFlowData) -> bool { + fn try_append_to_line(&mut self, in_box: @RenderBox, flow: &mut InlineFlow) -> bool { let line_is_empty: bool = self.pending_line.range.length() == 0; if line_is_empty { @@ -313,7 +293,7 @@ impl LineboxScanner { debug!("LineboxScanner: Trying to append box to line %u (box size: %?, green zone: \ %?): %s", self.lines.len(), - in_box.position().size, + in_box.base().position.get().size, self.pending_line.green_zone, in_box.debug_str()); @@ -326,6 +306,8 @@ impl LineboxScanner { let new_height = self.new_height_for_line(in_box); if new_height > green_zone.height { + debug!("LineboxScanner: entering float collision avoider!"); + // Uh-oh. Adding this box is going to increase the height, // and because of that we will collide with some floats. @@ -367,9 +349,10 @@ impl LineboxScanner { // horizontally. We'll try to place the whole box on this line and break somewhere // if it doesn't fit. - let new_width = self.pending_line.bounds.size.width + in_box.position().size.width; + let new_width = self.pending_line.bounds.size.width + + in_box.base().position.get().size.width; - if(new_width <= green_zone.width){ + if new_width <= green_zone.width { debug!("LineboxScanner: case=box fits without splitting"); self.push_box_to_line(in_box); return true; @@ -441,28 +424,29 @@ impl LineboxScanner { } // unconditional push - fn push_box_to_line(&mut self, box: RenderBox) { - debug!("LineboxScanner: Pushing box b%d to line %u", box.id(), self.lines.len()); + fn push_box_to_line(&mut self, box: @RenderBox) { + debug!("LineboxScanner: Pushing box b%d to line %u", box.base().id(), self.lines.len()); if self.pending_line.range.length() == 0 { assert!(self.new_boxes.len() <= (u16::max_value as uint)); self.pending_line.range.reset(self.new_boxes.len(), 0); } self.pending_line.range.extend_by(1); - self.pending_line.bounds.size.width = self.pending_line.bounds.size.width + box.position().size.width; + self.pending_line.bounds.size.width = self.pending_line.bounds.size.width + + box.base().position.get().size.width; self.pending_line.bounds.size.height = Au::max(self.pending_line.bounds.size.height, - box.position().size.height); + box.base().position.get().size.height); self.new_boxes.push(box); } } -pub struct InlineFlowData { +pub struct InlineFlow { /// Data common to all flows. - common: FlowData, + base: FlowData, // A vec of all inline render boxes. Several boxes may // correspond to one Node/Element. - boxes: ~[RenderBox], + boxes: ~[@RenderBox], // vec of ranges into boxes that represents line positions. // these ranges are disjoint, and are the result of inline layout. // also some metadata used for positioning lines @@ -473,10 +457,10 @@ pub struct InlineFlowData { elems: ElementMapping } -impl InlineFlowData { - pub fn new(common: FlowData) -> InlineFlowData { - InlineFlowData { - common: common, +impl InlineFlow { + pub fn new(base: FlowData) -> InlineFlow { + InlineFlow { + base: base, boxes: ~[], lines: ~[], elems: ElementMapping::new(), @@ -489,87 +473,103 @@ impl InlineFlowData { } self.boxes = ~[]; } -} -pub trait InlineLayout { - fn starts_inline_flow(&self) -> bool; -} + pub fn build_display_list_inline<E:ExtraDisplayListData>(&self, + builder: &DisplayListBuilder, + dirty: &Rect<Au>, + list: &Cell<DisplayList<E>>) + -> bool { -impl InlineLayout for FlowContext { - fn starts_inline_flow(&self) -> bool { - match *self { - InlineFlow(*) => true, - _ => false + //TODO: implement inline iframe size messaging + if self.base.node.is_iframe_element() { + error!("inline iframe size messaging not implemented yet"); } + + let abs_rect = Rect(self.base.abs_position, self.base.position.size); + if !abs_rect.intersects(dirty) { + return true; + } + + // TODO(#228): Once we form line boxes and have their cached bounds, we can be smarter and + // not recurse on a line if nothing in it can intersect the dirty region. + debug!("FlowContext[%d]: building display list for %u inline boxes", + self.base.id, + self.boxes.len()); + + for box in self.boxes.iter() { + box.build_display_list(builder, dirty, &self.base.abs_position, list) + } + + // 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 + true } } -impl InlineFlowData { - pub fn bubble_widths_inline(&mut self, ctx: &mut LayoutContext) { +impl FlowContext for InlineFlow { + fn class(&self) -> FlowClass { + InlineFlowClass + } + + fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow { + self + } + + fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow { + self + } + + fn bubble_widths(&mut self, _: &mut LayoutContext) { let mut num_floats = 0; - for kid in self.common.child_iter() { - do kid.with_mut_base |base| { - num_floats += base.num_floats; - base.floats_in = FloatContext::new(base.num_floats); - } + for kid in self.base.child_iter() { + let child_base = flow::mut_base(*kid); + num_floats += child_base.num_floats; + child_base.floats_in = FloatContext::new(child_base.num_floats); } { let this = &mut *self; - let mut min_width = Au(0); - let mut pref_width = Au(0); + let mut min_width = Au::new(0); + let mut pref_width = Au::new(0); for box in this.boxes.iter() { - debug!("FlowContext[%d]: measuring %s", self.common.id, box.debug_str()); - min_width = Au::max(min_width, box.get_min_width(ctx)); - pref_width = Au::max(pref_width, box.get_pref_width(ctx)); + debug!("FlowContext[%d]: measuring %s", self.base.id, box.debug_str()); + 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); } - this.common.min_width = min_width; - this.common.pref_width = pref_width; - this.common.num_floats = num_floats; + this.base.min_width = min_width; + this.base.pref_width = pref_width; + this.base.num_floats = num_floats; } } /// Recursively (top-down) determines the actual width of child contexts and boxes. When called /// on this context, the context has had its width set by the parent context. - pub fn assign_widths_inline(&mut self, _: &LayoutContext) { + fn assign_widths(&mut self, _: &mut LayoutContext) { // Initialize content box widths if they haven't been initialized already. // // TODO: Combine this with `LineboxScanner`'s walk in the box list, or put this into // `RenderBox`. - debug!("assign_widths_inline: floats_in: %?", self.common.floats_in); + debug!("assign_widths_inline: floats_in: %?", self.base.floats_in); { let this = &mut *self; for &box in this.boxes.iter() { - match box { - ImageRenderBoxClass(image_box) => { - let width = box.image_width(image_box); - image_box.base.position.size.width = width; - } - TextRenderBoxClass(_) => { - // Text boxes are preinitialized. - } - GenericRenderBoxClass(generic_box) => { - // TODO(#225): There will be different cases here for `inline-block` and - // other replaced content. - // FIXME(pcwalton): This seems clownshoes; can we remove? - generic_box.position.size.width = Au::from_px(45); - } - // FIXME(pcwalton): This isn't very type safe! - _ => fail!(fmt!("Tried to assign width to unknown Box variant: %?", box)), - } - } // End of for loop. + box.assign_width(); + } } - for kid in self.common.child_iter() { - do kid.with_mut_base |base| { - base.position.size.width = self.common.position.size.width; - base.is_inorder = self.common.is_inorder; - } + for kid in self.base.child_iter() { + let child_base = flow::mut_base(*kid); + child_base.position.size.width = self.base.position.size.width; + child_base.is_inorder = self.base.is_inorder; } // There are no child contexts, so stop here. @@ -580,16 +580,15 @@ impl InlineFlowData { // 'inline-block' box that created this flow before recursing. } - pub fn assign_height_inorder_inline(&mut self, ctx: &mut LayoutContext) { - for kid in self.common.child_iter() { + fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) { + for kid in self.base.child_iter() { kid.assign_height_inorder(ctx); } - self.assign_height_inline(ctx); + self.assign_height(ctx); } - pub fn assign_height_inline(&mut self, _: &LayoutContext) { - - debug!("assign_height_inline: assigning height for flow %?", self.common.id); + fn assign_height(&mut self, _: &mut LayoutContext) { + debug!("assign_height_inline: assigning height for flow %?", self.base.id); // Divide the boxes into lines // TODO(#226): Get the CSS `line-height` property from the containing block's style to @@ -597,33 +596,37 @@ impl InlineFlowData { // // TODO(#226): Get the CSS `line-height` property from each non-replaced inline element to // determine its height for computing linebox height. - debug!("assign_height_inline: floats_in: %?", self.common.floats_in); - let scanner_floats = self.common.floats_in.clone(); + // + // TODO(pcwalton): Cache the linebox scanner? + debug!("assign_height_inline: floats_in: %?", self.base.floats_in); + + let scanner_floats = self.base.floats_in.clone(); let mut scanner = LineboxScanner::new(scanner_floats); + + // Access the linebox scanner. scanner.scan_for_lines(self); - let mut line_height_offset = Au(0); + let mut line_height_offset = Au::new(0); // Now, go through each line and lay out the boxes inside for line in self.lines.mut_iter() { // We need to distribute extra width based on text-align. let mut slack_width = line.green_zone.width - line.bounds.size.width; - if slack_width < Au(0) { - slack_width = Au(0); + if slack_width < Au::new(0) { + slack_width = Au::new(0); } - //assert!(slack_width >= Au(0), "Too many boxes on line"); + //assert!(slack_width >= Au::new(0), "Too many boxes on line"); // Get the text alignment. // TODO(Issue #222): use 'text-align' property from InlineFlow's // block container, not from the style of the first box child. - let linebox_align; - if line.range.begin() < self.boxes.len() { + let linebox_align = if line.range.begin() < self.boxes.len() { let first_box = self.boxes[line.range.begin()]; - linebox_align = first_box.text_align(); + first_box.base().nearest_ancestor_element().style().Text.text_align } else { // Nothing to lay out, so assume left alignment. - linebox_align = text_align::left; - } + text_align::left + }; // Set the box x positions let mut offset_x = line.bounds.origin.x; @@ -632,28 +635,25 @@ impl InlineFlowData { // TODO(Issue #213): implement `text-align: justify` text_align::left | text_align::justify => { for i in line.range.eachi() { - do self.boxes[i].with_mut_base |base| { - base.position.origin.x = offset_x; - offset_x = offset_x + base.position.size.width; - } + let box = self.boxes[i].base(); + box.position.mutate().ptr.origin.x = offset_x; + offset_x = offset_x + box.position.get().size.width; } } text_align::center => { offset_x = offset_x + slack_width.scale_by(0.5); for i in line.range.eachi() { - do self.boxes[i].with_mut_base |base| { - base.position.origin.x = offset_x; - offset_x = offset_x + base.position.size.width; - } + let box = self.boxes[i].base(); + box.position.mutate().ptr.origin.x = offset_x; + offset_x = offset_x + box.position.get().size.width; } } text_align::right => { offset_x = offset_x + slack_width; for i in line.range.eachi() { - do self.boxes[i].with_mut_base |base| { - base.position.origin.x = offset_x; - offset_x = offset_x + base.position.size.width; - } + let box = self.boxes[i].base(); + box.position.mutate().ptr.origin.x = offset_x; + offset_x = offset_x + box.position.get().size.width; } } }; @@ -663,46 +663,53 @@ impl InlineFlowData { line.bounds.origin.y = line.bounds.origin.y + line_height_offset; // Calculate the distance from baseline to the top of the linebox. - let mut topmost = Au(0); + let mut topmost = Au::new(0); // Calculate the distance from baseline to the bottom of the linebox. - let mut bottommost = Au(0); + let mut bottommost = Au::new(0); // Calculate the biggest height among boxes with 'top' value. - let mut biggest_top = Au(0); + let mut biggest_top = Au::new(0); // Calculate the biggest height among boxes with 'bottom' value. - let mut biggest_bottom = Au(0); + let mut biggest_bottom = Au::new(0); for box_i in line.range.eachi() { let cur_box = self.boxes[box_i]; - let (top_from_base, bottom_from_base, ascent) = match cur_box { - ImageRenderBoxClass(image_box) => { - let mut height = cur_box.image_height(image_box); + let (top_from_base, bottom_from_base, ascent) = match cur_box.class() { + ImageRenderBoxClass => { + let image_box = cur_box.as_image_render_box(); + let mut height = image_box.image_height(); // TODO: margin, border, padding's top and bottom should be calculated in advance, // since baseline of image is bottom margin edge. - let mut top = Au(0); - let mut bottom = Au(0); - do cur_box.with_model |model| { + let mut top; + let mut bottom; + { + let model = image_box.base.model.get(); top = model.border.top + model.padding.top + model.margin.top; - bottom = model.border.bottom + model.padding.bottom + model.margin.bottom; + bottom = model.border.bottom + model.padding.bottom + + model.margin.bottom; } + let noncontent_height = top + bottom; height = height + noncontent_height; - image_box.base.position.size.height = height; - image_box.base.position.translate(&Point2D(Au(0), -height)); + + let position_ref = image_box.base.position.mutate(); + position_ref.ptr.size.height = height; + position_ref.ptr.translate(&Point2D(Au::new(0), -height)); let ascent = height + bottom; - (height, Au(0), ascent) + (height, Au::new(0), ascent) }, - TextRenderBoxClass(text_box) => { + TextRenderBoxClass => { + let text_box = cur_box.as_text_render_box(); let range = &text_box.range; let run = &text_box.run; // Compute the height based on the line-height and font size let text_bounds = run.metrics_for_range(range).bounding_box; let em_size = text_bounds.size.height; - let line_height = scanner.calculate_line_height(cur_box, em_size); + let line_height = text_box.base.calculate_line_height(em_size); // Find the top and bottom of the content area. // Those are used in text-top and text-bottom value of 'vertical-align' @@ -710,12 +717,15 @@ impl InlineFlowData { // Offset from the top of the box is 1/2 of the leading + ascent let text_offset = text_ascent + (line_height - em_size).scale_by(0.5); - text_bounds.translate(&Point2D(text_box.base.position.origin.x, Au(0))); + text_bounds.translate(&Point2D(text_box.base.position.get().origin.x, + Au::new(0))); (text_offset, line_height - text_offset, text_ascent) }, - GenericRenderBoxClass(generic_box) => { - (generic_box.position.size.height, Au(0), generic_box.position.size.height) + GenericRenderBoxClass => { + let base = cur_box.base(); + let height = base.position.get().size.height; + (height, Au::new(0), height) }, // FIXME(pcwalton): This isn't very type safe! _ => { @@ -734,44 +744,40 @@ impl InlineFlowData { // It should calculate the distance from baseline to the top of parent's content area. // But, it is assumed now as font size of parent. - let mut parent_text_top = Au(0); + let mut parent_text_top; // It should calculate the distance from baseline to the bottom of parent's content area. // But, it is assumed now as 0. - let parent_text_bottom = Au(0); - do cur_box.with_mut_base |base| { - // Get parent node - let parent = match base.node.parent_node() { - None => base.node, - Some(parent) => parent, - }; - - let font_size = parent.style().Font.font_size; - parent_text_top = font_size; - } + let parent_text_bottom = Au::new(0); + let cur_box_base = cur_box.base(); + // Get parent node + let parent = cur_box_base.node.parent_node(); + + let font_size = parent.unwrap().style().Font.font_size; + parent_text_top = font_size; // This flag decides whether topmost and bottommost are updated or not. // That is, if the box has top or bottom value, no_update_flag becomes true. let mut no_update_flag = false; // Calculate a relative offset from baseline. - let offset = match cur_box.vertical_align() { + let offset = match cur_box_base.vertical_align() { vertical_align::baseline => { -ascent }, vertical_align::middle => { // TODO: x-height value should be used from font info. - let xheight = Au(0); - -(xheight + scanner.box_height(cur_box)).scale_by(0.5) + let xheight = Au::new(0); + -(xheight + cur_box.box_height()).scale_by(0.5) }, vertical_align::sub => { // TODO: The proper position for subscripts should be used. // Lower the baseline to the proper position for subscripts - let sub_offset = Au(0); + let sub_offset = Au::new(0); (sub_offset - ascent) }, vertical_align::super_ => { // TODO: The proper position for superscripts should be used. // Raise the baseline to the proper position for superscripts - let super_offset = Au(0); + let super_offset = Au::new(0); (-super_offset - ascent) }, vertical_align::text_top => { @@ -808,8 +814,9 @@ impl InlineFlowData { -(length + ascent) }, vertical_align::Percentage(p) => { - let pt_size = cur_box.font_style().pt_size; - let line_height = scanner.calculate_line_height(cur_box, Au::from_pt(pt_size)); + let pt_size = cur_box.base().font_style().pt_size; + let line_height = cur_box.base() + .calculate_line_height(Au::from_pt(pt_size)); let percent_offset = line_height.scale_by(p); -(percent_offset + ascent) } @@ -824,9 +831,7 @@ impl InlineFlowData { bottommost = bottom_from_base; } - do cur_box.with_mut_base |base| { - base.position.origin.y = line.bounds.origin.y + offset; - } + cur_box.base().position.mutate().ptr.origin.y = line.bounds.origin.y + offset; } // Calculate the distance from baseline to the top of the biggest box with 'bottom' value. @@ -849,21 +854,15 @@ impl InlineFlowData { // All boxes' y position is updated following the new baseline offset. for box_i in line.range.eachi() { let cur_box = self.boxes[box_i]; - let adjust_offset = match cur_box.vertical_align() { - vertical_align::top => { - Au(0) - }, - vertical_align::bottom => { - baseline_offset + bottommost - }, - _ => { - baseline_offset - } + let cur_box_base = cur_box.base(); + let adjust_offset = match cur_box_base.vertical_align() { + vertical_align::top => Au::new(0), + vertical_align::bottom => baseline_offset + bottommost, + _ => baseline_offset, }; - do cur_box.with_mut_base |base| { - base.position.origin.y = base.position.origin.y + adjust_offset; - } + cur_box_base.position.mutate().ptr.origin.y = + cur_box_base.position.get().origin.y + adjust_offset; } // This is used to set the top y position of the next linebox in the next loop. @@ -871,48 +870,30 @@ impl InlineFlowData { line.bounds.size.height = topmost + bottommost; } // End of `lines.each` loop. - self.common.position.size.height = + self.base.position.size.height = if self.lines.len() > 0 { self.lines.last().bounds.origin.y + self.lines.last().bounds.size.height } else { - Au(0) + Au::new(0) }; - self.common.floats_out = scanner.floats_out().translate(Point2D(Au(0), - -self.common.position.size.height)); + self.base.floats_out = scanner.floats_out() + .translate(Point2D(Au::new(0), + -self.base.position.size.height)); } - pub fn build_display_list_inline<E:ExtraDisplayListData>(&self, - builder: &DisplayListBuilder, - dirty: &Rect<Au>, - list: &Cell<DisplayList<E>>) - -> bool { - - //TODO: implement inline iframe size messaging - if self.common.node.is_iframe_element() { - error!("inline iframe size messaging not implemented yet"); - } - - let abs_rect = Rect(self.common.abs_position, self.common.position.size); - if !abs_rect.intersects(dirty) { - return true; + 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); } - - // TODO(#228): Once we form line boxes and have their cached bounds, we can be smarter and - // not recurse on a line if nothing in it can intersect the dirty region. - debug!("FlowContext[%d]: building display list for %u inline boxes", - self.common.id, - self.boxes.len()); - - for box in self.boxes.iter() { - box.build_display_list(builder, dirty, &self.common.abs_position, list) - } - - // 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 - true } } diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index 07a638a2604..cb029942710 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -11,7 +11,9 @@ use layout::aux::LayoutAuxMethods; use layout::box_builder::LayoutTreeBuilder; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder}; -use layout::flow::FlowContext; +use layout::flow::{FlowContext, ImmutableFlowUtils, MutableFlowUtils, PreorderFlowTraversal}; +use layout::flow::{PostorderFlowTraversal}; +use layout::flow; use layout::incremental::{RestyleDamage, BubbleWidths}; use std::cast::transmute; @@ -42,7 +44,7 @@ use script::layout_interface::{ReflowForDisplay, ReflowMsg}; use script::script_task::{ReflowCompleteMsg, ScriptChan, SendEventMsg}; use servo_msg::constellation_msg::{ConstellationChan, PipelineId}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; -use servo_net::local_image_cache::LocalImageCache; +use servo_net::local_image_cache::{ImageResponder, LocalImageCache}; use servo_util::tree::TreeNodeRef; use servo_util::time::{ProfilerChan, profile}; use servo_util::time; @@ -67,6 +69,129 @@ struct LayoutTask { profiler_chan: ProfilerChan, } +/// The damage computation traversal. +#[deriving(Clone)] +struct ComputeDamageTraversal; + +impl PostorderFlowTraversal for ComputeDamageTraversal { + #[inline] + fn process(&mut self, flow: &mut FlowContext) -> bool { + let mut damage = flow::base(flow).restyle_damage; + for child in flow::child_iter(flow) { + damage.union_in_place(flow::base(*child).restyle_damage) + } + flow::mut_base(flow).restyle_damage = damage; + true + } +} + +/// Propagates restyle damage up and down the tree as appropriate. +/// +/// FIXME(pcwalton): Merge this with flow tree building and/or other traversals. +struct PropagateDamageTraversal { + resized: bool, +} + +impl PreorderFlowTraversal for PropagateDamageTraversal { + #[inline] + fn process(&mut self, flow: &mut FlowContext) -> bool { + // Also set any damage implied by resize. + if self.resized { + flow::mut_base(flow).restyle_damage.union_in_place(RestyleDamage::for_resize()) + } + + let prop = flow::base(flow).restyle_damage.propagate_down(); + if prop.is_nonempty() { + for kid_ctx in flow::child_iter(flow) { + flow::mut_base(*kid_ctx).restyle_damage.union_in_place(prop) + } + } + true + } +} + +/// The bubble-widths traversal, the first part of layout computation. This computes preferred +/// and intrinsic widths and bubbles them up the tree. +struct BubbleWidthsTraversal<'self>(&'self mut LayoutContext); + +impl<'self> PostorderFlowTraversal for BubbleWidthsTraversal<'self> { + #[inline] + fn process(&mut self, flow: &mut FlowContext) -> bool { + flow.bubble_widths(**self); + true + } + + #[inline] + fn should_prune(&mut self, flow: &mut FlowContext) -> bool { + flow::mut_base(flow).restyle_damage.lacks(BubbleWidths) + } +} + +/// The assign-widths traversal. In Gecko this corresponds to `Reflow`. +struct AssignWidthsTraversal<'self>(&'self mut LayoutContext); + +impl<'self> PreorderFlowTraversal for AssignWidthsTraversal<'self> { + #[inline] + fn process(&mut self, flow: &mut FlowContext) -> bool { + flow.assign_widths(**self); + true + } +} + +/// The assign-heights traversal, the last (and most expensive) part of layout computation. +/// Determines the final heights for all layout objects. In Gecko this corresponds to +/// `FinishAndStoreOverflow`. +struct AssignHeightsTraversal<'self>(&'self mut LayoutContext); + +impl<'self> PostorderFlowTraversal for AssignHeightsTraversal<'self> { + #[inline] + fn process(&mut self, flow: &mut FlowContext) -> bool { + flow.assign_height(**self); + true + } + + #[inline] + fn should_process(&mut self, flow: &mut FlowContext) -> bool { + !flow::base(flow).is_inorder + } +} + +/// The display list building traversal. In WebKit this corresponds to `paint`. In Gecko this +/// corresponds to `BuildDisplayListForChild`. +struct DisplayListBuildingTraversal<'self> { + builder: DisplayListBuilder<'self>, + root_pos: Rect<Au>, + display_list: ~Cell<DisplayList<AbstractNode<()>>>, +} + +impl<'self> PreorderFlowTraversal for DisplayListBuildingTraversal<'self> { + #[inline] + fn process(&mut self, _: &mut FlowContext) -> bool { + true + } + + #[inline] + fn should_prune(&mut self, flow: &mut FlowContext) -> bool { + flow.build_display_list(&self.builder, &self.root_pos, self.display_list) + } +} + +struct LayoutImageResponder { + id: PipelineId, + script_chan: ScriptChan, +} + +impl ImageResponder for LayoutImageResponder { + fn respond(&self) -> ~fn(ImageResponseMsg) { + let id = self.id.clone(); + let script_chan = self.script_chan.clone(); + let f: ~fn(ImageResponseMsg) = |_| { + script_chan.send(SendEventMsg(id.clone(), ReflowEvent)) + }; + f + } +} + impl LayoutTask { pub fn create(id: PipelineId, port: Port<Msg>, @@ -180,14 +305,13 @@ impl LayoutTask { // FIXME: Bad copy! let doc_url = data.url.clone(); - let script_chan = data.script_chan.clone(); debug!("layout: received layout request for: %s", doc_url.to_str()); debug!("layout: damage is %?", data.damage); debug!("layout: parsed Node tree"); debug!("%?", node.dump()); // Reset the image cache. - self.local_image_cache.next_round(self.make_on_image_available_cb(script_chan)); + self.local_image_cache.next_round(self.make_on_image_available_cb()); self.doc_url = Some(doc_url); let screen_size = Size2D(Au::from_px(data.window_size.width as int), @@ -218,10 +342,10 @@ impl LayoutTask { } // Construct the flow tree. - let mut layout_root: FlowContext = do profile(time::LayoutTreeBuilderCategory, - self.profiler_chan.clone()) { + let mut layout_root: ~FlowContext: = do profile(time::LayoutTreeBuilderCategory, + self.profiler_chan.clone()) { let mut builder = LayoutTreeBuilder::new(); - let layout_root: FlowContext = match builder.construct_trees(&layout_ctx, *node) { + let layout_root: ~FlowContext: = match builder.construct_trees(&layout_ctx, *node) { Ok(root) => root, Err(*) => fail!(~"Root flow should always exist") }; @@ -229,41 +353,11 @@ impl LayoutTask { layout_root }; - // Propagate restyle damage up and down the tree, as appropriate. - // FIXME: Merge this with flow tree building and/or the other traversals. - do layout_root.each_preorder |flow| { - // Also set any damage implied by resize. - if resized { - do flow.with_mut_base |base| { - base.restyle_damage.union_in_place(RestyleDamage::for_resize()); - } - } - - let prop = flow.with_base(|base| base.restyle_damage.propagate_down()); - if prop.is_nonempty() { - for kid_ctx in flow.child_iter() { - do kid_ctx.with_mut_base |kid| { - kid.restyle_damage.union_in_place(prop); - } - } - } - true - }; - - do layout_root.each_postorder |flow| { - let mut damage = do flow.with_base |base| { - base.restyle_damage - }; - for child in flow.child_iter() { - do child.with_base |child_base| { - damage.union_in_place(child_base.restyle_damage); - } - } - do flow.with_mut_base |base| { - base.restyle_damage = damage; - } - true - }; + // Propagate damage. + layout_root.traverse_preorder(&mut PropagateDamageTraversal { + resized: resized, + }); + layout_root.traverse_postorder(&mut ComputeDamageTraversal.clone()); debug!("layout: constructed Flow tree"); debug!("%?", layout_root.dump()); @@ -271,51 +365,37 @@ impl LayoutTask { // Perform the primary layout passes over the flow tree to compute the locations of all // the boxes. do profile(time::LayoutMainCategory, self.profiler_chan.clone()) { - do layout_root.each_postorder_prune(|f| f.restyle_damage().lacks(BubbleWidths)) |flow| { - flow.bubble_widths(&mut layout_ctx); - true - }; + let _ = layout_root.traverse_postorder(&mut BubbleWidthsTraversal(&mut layout_ctx)); - // FIXME: We want to do + // FIXME(kmc): We want to do // for flow in layout_root.traverse_preorder_prune(|f| f.restyle_damage().lacks(Reflow)) // but FloatContext values can't be reused, so we need to recompute them every time. // NOTE: this currently computes borders, so any pruning should separate that operation out. - debug!("assigning widths"); - do layout_root.each_preorder |flow| { - flow.assign_widths(&mut layout_ctx); - true - }; + let _ = layout_root.traverse_preorder(&mut AssignWidthsTraversal(&mut layout_ctx)); // For now, this is an inorder traversal // FIXME: prune this traversal as well - debug!("assigning height"); - do layout_root.each_bu_sub_inorder |flow| { - flow.assign_height(&mut layout_ctx); - true - }; + let _ = layout_root.traverse_postorder(&mut AssignHeightsTraversal(&mut layout_ctx)); } // Build the display list if necessary, and send it to the renderer. if data.goal == ReflowForDisplay { do profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone()) { - let builder = DisplayListBuilder { - ctx: &layout_ctx, - }; - - let display_list = ~Cell::new(DisplayList::<AbstractNode<()>>::new()); - // TODO: Set options on the builder before building. // TODO: Be smarter about what needs painting. - let root_pos = &layout_root.position().clone(); - layout_root.each_preorder_prune(|flow| { - flow.build_display_list(&builder, root_pos, display_list) - }, |_| { true } ); - - let root_size = do layout_root.with_base |base| { - base.position.size + let mut traversal = DisplayListBuildingTraversal { + builder: DisplayListBuilder { + ctx: &layout_ctx, + }, + root_pos: flow::base(layout_root).position.clone(), + display_list: ~Cell::new(DisplayList::<AbstractNode<()>>::new()), }; - let display_list = Arc::new(display_list.take()); + let _ = layout_root.traverse_preorder(&mut traversal); + + let root_size = flow::base(layout_root).position.size; + + let display_list = Arc::new(traversal.display_list.take()); for i in range(0,display_list.get().list.len()) { let node: AbstractNode<LayoutView> = unsafe { @@ -445,7 +525,7 @@ impl LayoutTask { Some(ref list) => { let display_list = list.get(); let (x, y) = (Au::from_frac_px(point.x as f64), - Au::from_frac_px(point.y as f64)); + Au::from_frac_px(point.y as f64)); let mut resp = Err(()); // iterate in reverse to ensure we have the most recently painted render box for display_item in display_list.list.rev_iter() { @@ -481,21 +561,15 @@ impl LayoutTask { // to the script task, and ultimately cause the image to be // re-requested. We probably don't need to go all the way back to // the script task for this. - fn make_on_image_available_cb(&self, script_chan: ScriptChan) - -> ~fn() -> ~fn(ImageResponseMsg) { + fn make_on_image_available_cb(&self) -> @ImageResponder { // This has a crazy signature because the image cache needs to // make multiple copies of the callback, and the dom event // channel is not a copyable type, so this is actually a // little factory to produce callbacks - let id = self.id.clone(); - let f: ~fn() -> ~fn(ImageResponseMsg) = || { - let script_chan = script_chan.clone(); - let f: ~fn(ImageResponseMsg) = |_| { - script_chan.send(SendEventMsg(id.clone(), ReflowEvent)) - }; - f - }; - return f; + @LayoutImageResponder { + id: self.id.clone(), + script_chan: self.script_chan.clone(), + } as @ImageResponder } } diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs index 9225c8408fd..cc66ec1f70d 100644 --- a/src/components/main/layout/model.rs +++ b/src/components/main/layout/model.rs @@ -11,6 +11,7 @@ use style::ComputedValues; use style::properties::common_types::computed; /// Encapsulates the borders, padding, and margins, which we collectively call the "box model". +#[deriving(Clone)] pub struct BoxModel { border: SideOffsets2D<Au>, padding: SideOffsets2D<Au>, @@ -26,7 +27,9 @@ pub enum MaybeAuto { } impl MaybeAuto { - pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au) -> MaybeAuto { + #[inline] + pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au) + -> MaybeAuto { match length { computed::LPA_Auto => Auto, computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)), @@ -34,6 +37,7 @@ impl MaybeAuto { } } + #[inline] pub fn specified_or_default(&self, default: Au) -> Au { match *self { Auto => default, @@ -41,8 +45,9 @@ impl MaybeAuto { } } + #[inline] pub fn specified_or_zero(&self) -> Au { - self.specified_or_default(Au(0)) + self.specified_or_default(Au::new(0)) } } @@ -62,20 +67,24 @@ impl Zero for BoxModel { } impl BoxModel { - /// Populates the box model parameters from the given computed style. + /// Populates the box model border parameters from the given computed style. pub fn compute_borders(&mut self, style: &ComputedValues) { - // Compute the borders. self.border.top = style.Border.border_top_width; self.border.right = style.Border.border_right_width; self.border.bottom = style.Border.border_bottom_width; self.border.left = style.Border.border_left_width; } + /// Populates the box model padding parameters from the given computed style. pub fn compute_padding(&mut self, style: &ComputedValues, containing_width: Au) { - self.padding.top = self.compute_padding_length(style.Padding.padding_top, containing_width); - self.padding.right = self.compute_padding_length(style.Padding.padding_right, containing_width); - self.padding.bottom = self.compute_padding_length(style.Padding.padding_bottom, containing_width); - self.padding.left = self.compute_padding_length(style.Padding.padding_left, containing_width); + self.padding.top = self.compute_padding_length(style.Padding.padding_top, + containing_width); + self.padding.right = self.compute_padding_length(style.Padding.padding_right, + containing_width); + self.padding.bottom = self.compute_padding_length(style.Padding.padding_bottom, + containing_width); + self.padding.left = self.compute_padding_length(style.Padding.padding_left, + containing_width); } pub fn noncontent_width(&self) -> Au { diff --git a/src/components/main/layout/text.rs b/src/components/main/layout/text.rs index a14dd2bf74a..77a8831d137 100644 --- a/src/components/main/layout/text.rs +++ b/src/components/main/layout/text.rs @@ -8,51 +8,11 @@ use std::vec; use gfx::text::text_run::TextRun; use gfx::text::util::{CompressWhitespaceNewline, transform_text}; -use layout::box::{RenderBox, RenderBoxBase, TextRenderBox}; -use layout::box::{TextRenderBoxClass, UnscannedTextRenderBoxClass}; +use layout::box::{RenderBox, RenderBoxUtils, TextRenderBox, UnscannedTextRenderBoxClass}; use layout::context::LayoutContext; use layout::flow::FlowContext; use servo_util::range::Range; - -/// Creates a TextRenderBox from a range and a text run. -pub fn adapt_textbox_with_range(mut base: RenderBoxBase, run: @TextRun, range: Range) - -> TextRenderBox { - debug!("Creating textbox with span: (strlen=%u, off=%u, len=%u) of textrun (%s) (len=%u)", - run.char_len(), - range.begin(), - range.length(), - run.text, - run.char_len()); - - assert!(range.begin() < run.char_len()); - assert!(range.end() <= run.char_len()); - assert!(range.length() > 0); - - let metrics = run.metrics_for_range(&range); - base.position.size = metrics.bounding_box.size; - - TextRenderBox { - base: base, - run: run, - range: range, - } -} - -pub trait UnscannedMethods { - /// Copies out the text from an unscanned text box. Fails if this is not an unscanned text box. - fn raw_text(&self) -> ~str; -} - -impl UnscannedMethods for RenderBox { - fn raw_text(&self) -> ~str { - match *self { - UnscannedTextRenderBoxClass(text_box) => text_box.text.clone(), - _ => fail!(~"unsupported operation: box.raw_text() on non-unscanned text box."), - } - } -} - /// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextBox`es. struct TextRunScanner { clump: Range, @@ -67,7 +27,7 @@ impl TextRunScanner { pub fn scan_for_runs(&mut self, ctx: &LayoutContext, flow: &mut FlowContext) { { - let inline = flow.imm_inline(); + let inline = flow.as_immutable_inline(); // FIXME: this assertion fails on wikipedia, but doesn't seem // to cause problems. // assert!(inline.boxes.len() > 0); @@ -76,9 +36,11 @@ impl TextRunScanner { let mut last_whitespace = true; let mut out_boxes = ~[]; - for box_i in range(0, flow.imm_inline().boxes.len()) { - debug!("TextRunScanner: considering box: %?", flow.imm_inline().boxes[box_i].debug_str()); - if box_i > 0 && !can_coalesce_text_nodes(flow.imm_inline().boxes, box_i-1, box_i) { + for box_i in range(0, flow.as_immutable_inline().boxes.len()) { + debug!("TextRunScanner: considering box: %u", box_i); + if box_i > 0 && !can_coalesce_text_nodes(flow.as_immutable_inline().boxes, + box_i - 1, + box_i) { last_whitespace = self.flush_clump_to_list(ctx, flow, last_whitespace, &mut out_boxes); } self.clump.extend_by(1); @@ -91,17 +53,17 @@ impl TextRunScanner { debug!("TextRunScanner: swapping out boxes."); // Swap out the old and new box list of the flow. - flow.inline().boxes = out_boxes; + flow.as_inline().boxes = out_boxes; // A helper function. - fn can_coalesce_text_nodes(boxes: &[RenderBox], left_i: uint, right_i: uint) -> bool { + fn can_coalesce_text_nodes(boxes: &[@RenderBox], left_i: uint, right_i: uint) -> bool { assert!(left_i < boxes.len()); assert!(right_i > 0 && right_i < boxes.len()); assert!(left_i != right_i); let (left, right) = (boxes[left_i], boxes[right_i]); - match (left, right) { - (UnscannedTextRenderBoxClass(*), UnscannedTextRenderBoxClass(*)) => { + match (left.class(), right.class()) { + (UnscannedTextRenderBoxClass, UnscannedTextRenderBoxClass) => { left.can_merge_with_box(right) } (_, _) => false @@ -123,18 +85,17 @@ impl TextRunScanner { ctx: &LayoutContext, flow: &mut FlowContext, last_whitespace: bool, - out_boxes: &mut ~[RenderBox]) -> bool { - let inline = flow.inline(); + out_boxes: &mut ~[@RenderBox]) + -> bool { + let inline = flow.as_inline(); let in_boxes = &inline.boxes; assert!(self.clump.length() > 0); debug!("TextRunScanner: flushing boxes in range=%?", self.clump); let is_singleton = self.clump.length() == 1; - let is_text_clump = match in_boxes[self.clump.begin()] { - UnscannedTextRenderBoxClass(*) => true, - _ => false - }; + let possible_text_clump = in_boxes[self.clump.begin()]; // FIXME(pcwalton): Rust bug + let is_text_clump = possible_text_clump.class() == UnscannedTextRenderBoxClass; let mut new_whitespace = last_whitespace; @@ -148,9 +109,9 @@ impl TextRunScanner { }, (true, true) => { let old_box = in_boxes[self.clump.begin()]; - let text = old_box.raw_text(); - let font_style = old_box.font_style(); - let decoration = old_box.text_decoration(); + let text = old_box.as_unscanned_text_render_box().raw_text(); + let font_style = old_box.base().font_style(); + let decoration = old_box.base().text_decoration(); // TODO(#115): Use the actual CSS `white-space` property of the relevant style. let compression = CompressWhitespaceNewline; @@ -166,12 +127,10 @@ impl TextRunScanner { let run = @fontgroup.create_textrun(transformed_text, decoration); debug!("TextRunScanner: pushing single text box in range: %? (%?)", self.clump, text); - let new_box = do old_box.with_base |old_box_base| { - let range = Range::new(0, run.char_len()); - @mut adapt_textbox_with_range(*old_box_base, run, range) - }; + let range = Range::new(0, run.char_len()); + let new_box = @TextRenderBox::new((*old_box.base()).clone(), run, range); - out_boxes.push(TextRenderBoxClass(new_box)); + out_boxes.push(new_box as @RenderBox); } }, (false, true) => { @@ -185,7 +144,8 @@ impl TextRunScanner { // `transform_text`, so that boxes starting and/or ending with whitespace can // be compressed correctly with respect to the text run. let idx = i + self.clump.begin(); - let (new_str, new_whitespace) = transform_text(in_boxes[idx].raw_text(), + let in_box = in_boxes[idx].as_unscanned_text_render_box().raw_text(); + let (new_str, new_whitespace) = transform_text(in_box, compression, last_whitespace_in_clump); last_whitespace_in_clump = new_whitespace; @@ -210,9 +170,10 @@ impl TextRunScanner { // TODO(#177): Text run creation must account for the renderability of text by // font group fonts. This is probably achieved by creating the font group above // and then letting `FontGroup` decide which `Font` to stick into the text run. - let font_style = in_boxes[self.clump.begin()].font_style(); + let in_box = in_boxes[self.clump.begin()]; + let font_style = in_box.base().font_style(); let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); - let decoration = in_boxes[self.clump.begin()].text_decoration(); + let decoration = in_box.base().text_decoration(); // TextRuns contain a cycle which is usually resolved by the teardown // sequence. If no clump takes ownership, however, it will leak. @@ -234,10 +195,10 @@ impl TextRunScanner { continue } - do in_boxes[i].with_base |base| { - let new_box = @mut adapt_textbox_with_range(*base, run.unwrap(), range); - out_boxes.push(TextRenderBoxClass(new_box)); - } + let new_box = @TextRenderBox::new((*in_boxes[i].base()).clone(), + run.unwrap(), + range); + out_boxes.push(new_box as @RenderBox); } } } // End of match. diff --git a/src/components/main/layout/util.rs b/src/components/main/layout/util.rs index c45ca1f3631..7701aa2bd66 100644 --- a/src/components/main/layout/util.rs +++ b/src/components/main/layout/util.rs @@ -2,7 +2,7 @@ * 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 layout::box::{RenderBox}; +use layout::box::{RenderBox, RenderBoxUtils}; use script::dom::node::{AbstractNode, LayoutView}; use servo_util::range::Range; @@ -46,7 +46,7 @@ impl ElementMapping { self.entries.iter().enumerate() } - pub fn repair_for_box_changes(&mut self, old_boxes: &[RenderBox], new_boxes: &[RenderBox]) { + pub fn repair_for_box_changes(&mut self, old_boxes: &[@RenderBox], new_boxes: &[@RenderBox]) { let entries = &mut self.entries; debug!("--- Old boxes: ---"); @@ -88,18 +88,8 @@ impl ElementMapping { repair_stack.push(item); entries_k += 1; } - // XXX: the following loop form causes segfaults; assigning to locals doesn't. - // while new_j < new_boxes.len() && old_boxes[old_i].d().node != new_boxes[new_j].d().node { - while new_j < new_boxes.len() { - let should_leave = do old_boxes[old_i].with_base |old_box_base| { - do new_boxes[new_j].with_base |new_box_base| { - old_box_base.node != new_box_base.node - } - }; - if should_leave { - break - } - + while new_j < new_boxes.len() && + old_boxes[old_i].base().node != new_boxes[new_j].base().node { debug!("repair_for_box_changes: Slide through new box %u", new_j); new_j += 1; } diff --git a/src/components/main/pipeline.rs b/src/components/main/pipeline.rs index fc34ecd9f29..9c89a07fb4e 100644 --- a/src/components/main/pipeline.rs +++ b/src/components/main/pipeline.rs @@ -44,7 +44,6 @@ impl Pipeline { opts: Opts, script_pipeline: &Pipeline, size_future: Future<Size2D<uint>>) -> Pipeline { - let (layout_port, layout_chan) = special_stream!(LayoutChan); let (render_port, render_chan) = special_stream!(RenderChan); @@ -78,7 +77,6 @@ impl Pipeline { script_pipeline.script_chan.clone(), layout_chan, render_chan) - } pub fn create(id: PipelineId, diff --git a/src/components/main/platform/common/glfw_windowing.rs b/src/components/main/platform/common/glfw_windowing.rs index 6dee5c7f97b..88032a221b4 100644 --- a/src/components/main/platform/common/glfw_windowing.rs +++ b/src/components/main/platform/common/glfw_windowing.rs @@ -11,6 +11,8 @@ use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEven use windowing::{Forward, Back}; use alert::{Alert, AlertMethods}; +use extra::time::Timespec; +use extra::time; use std::libc::c_int; use std::local_data; use geom::point::Point2D; @@ -20,8 +22,6 @@ use servo_msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayou use glfw; -static THROBBER: [char, ..8] = [ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' ]; - /// A structure responsible for setting up and tearing down the entire windowing system. pub struct Application; @@ -57,7 +57,8 @@ pub struct Window { ready_state: ReadyState, render_state: RenderState, - throbber_frame: u8, + + last_title_set_time: Timespec, } impl WindowMethods<Application> for Window { @@ -81,7 +82,8 @@ impl WindowMethods<Application> for Window { ready_state: Blank, render_state: IdleRenderState, - throbber_frame: 0, + + last_title_set_time: Timespec::new(0, 0), }; install_local_window(window); @@ -141,8 +143,7 @@ impl WindowMethods<Application> for Window { return self.event_queue.shift() } glfw::poll_events(); - self.throbber_frame = (self.throbber_frame + 1) % (THROBBER.len() as u8); - self.update_window_title(false); + if self.glfw_window.should_close() { QuitWindowEvent } else if !self.event_queue.is_empty() { @@ -155,7 +156,7 @@ impl WindowMethods<Application> for Window { /// Sets the ready state. fn set_ready_state(@mut self, ready_state: ReadyState) { self.ready_state = ready_state; - self.update_window_title(true) + self.update_window_title() } /// Sets the render state. @@ -168,7 +169,7 @@ impl WindowMethods<Application> for Window { } self.render_state = render_state; - self.update_window_title(true) + self.update_window_title() } fn hidpi_factor(@mut self) -> f32 { @@ -180,25 +181,30 @@ impl WindowMethods<Application> for Window { impl Window { /// Helper function to set the window title in accordance with the ready state. - fn update_window_title(&self, state_change: bool) { - let throbber = THROBBER[self.throbber_frame]; + fn update_window_title(&mut self) { + let now = time::get_time(); + if now.sec == self.last_title_set_time.sec { + return + } + self.last_title_set_time = now; + match self.ready_state { Blank => { - if state_change { self.glfw_window.set_title(fmt!("blank — Servo")) } + self.glfw_window.set_title(fmt!("blank — Servo")) } Loading => { - self.glfw_window.set_title(fmt!("%c Loading — Servo", throbber)) + self.glfw_window.set_title(fmt!("Loading — Servo")) } PerformingLayout => { - self.glfw_window.set_title(fmt!("%c Performing Layout — Servo", throbber)) + self.glfw_window.set_title(fmt!("Performing Layout — Servo")) } FinishedLoading => { match self.render_state { RenderingRenderState => { - self.glfw_window.set_title(fmt!("%c Rendering — Servo", throbber)) + self.glfw_window.set_title(fmt!("Rendering — Servo")) } IdleRenderState => { - if state_change { self.glfw_window.set_title("Servo") } + self.glfw_window.set_title("Servo") } } } diff --git a/src/components/net/local_image_cache.rs b/src/components/net/local_image_cache.rs index c7c922e6910..2970c16bb5f 100644 --- a/src/components/net/local_image_cache.rs +++ b/src/components/net/local_image_cache.rs @@ -17,6 +17,10 @@ use std::task; use servo_util::url::{UrlMap, url_map}; use extra::url::Url; +pub trait ImageResponder { + fn respond(&self) -> ~fn(ImageResponseMsg); +} + pub fn LocalImageCache(image_cache_task: ImageCacheTask) -> LocalImageCache { LocalImageCache { image_cache_task: image_cache_task, @@ -29,7 +33,7 @@ pub fn LocalImageCache(image_cache_task: ImageCacheTask) -> LocalImageCache { pub struct LocalImageCache { priv image_cache_task: ImageCacheTask, priv round_number: uint, - priv on_image_available: Option<~fn() -> ~fn(ImageResponseMsg)>, + priv on_image_available: Option<@ImageResponder>, priv state_map: UrlMap<@mut ImageState> } @@ -43,7 +47,7 @@ struct ImageState { impl LocalImageCache { /// The local cache will only do a single remote request for a given /// URL in each 'round'. Layout should call this each time it begins - pub fn next_round(&mut self, on_image_available: ~fn() -> ~fn(ImageResponseMsg)) { + pub fn next_round(&mut self, on_image_available: @ImageResponder) { self.round_number += 1; self.on_image_available = Some(on_image_available); } @@ -109,7 +113,7 @@ impl LocalImageCache { // on the image to load and triggering layout let image_cache_task = self.image_cache_task.clone(); assert!(self.on_image_available.is_some()); - let on_image_available = (*self.on_image_available.get_ref())(); + let on_image_available = self.on_image_available.unwrap().respond(); let url = (*url).clone(); do task::spawn { let (response_port, response_chan) = comm::stream(); diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index 9b8f7ea1b07..8b5478fefe2 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -882,7 +882,7 @@ pub struct DisplayBoxes { /// Data that layout associates with a node. pub struct LayoutData { /// The results of CSS matching for this node. - applicable_declarations: ~[@[PropertyDeclaration]], + applicable_declarations: ~[Arc<~[PropertyDeclaration]>], /// The results of CSS styling for this node. style: Option<ComputedValues>, @@ -902,11 +902,21 @@ impl LayoutData { applicable_declarations: ~[], style: None, restyle_damage: None, - boxes: DisplayBoxes { display_list: None, range: None }, + boxes: DisplayBoxes { + display_list: None, + range: None, + }, } } } +// This serves as a static assertion that layout data remains sendable. If this is not done, then +// we can have memory unsafety, which usually manifests as shutdown crashes. +fn assert_is_sendable<T:Send>(_: T) {} +fn assert_layout_data_is_sendable() { + assert_is_sendable(LayoutData::new()) +} + impl AbstractNode<LayoutView> { // These accessors take a continuation rather than returning a reference, because // an AbstractNode doesn't have a lifetime parameter relating to the underlying diff --git a/src/components/script/html/hubbub_html_parser.rs b/src/components/script/html/hubbub_html_parser.rs index c6dca22ee21..460053d9686 100644 --- a/src/components/script/html/hubbub_html_parser.rs +++ b/src/components/script/html/hubbub_html_parser.rs @@ -28,8 +28,8 @@ use servo_net::image_cache_task::ImageCacheTask; use servo_net::resource_task::{Load, Payload, Done, ResourceTask, load_whole_resource}; use servo_util::tree::{TreeNodeRef, ElementLike}; use servo_util::url::make_url; -use extra::url::Url; use extra::future::Future; +use extra::url::Url; use geom::size::Size2D; macro_rules! handle_element( diff --git a/src/components/style/properties.rs.mako b/src/components/style/properties.rs.mako index 5585e0ba764..e2f968d432f 100644 --- a/src/components/style/properties.rs.mako +++ b/src/components/style/properties.rs.mako @@ -5,6 +5,7 @@ // This file is a Mako template: http://www.makotemplates.org/ use std::ascii::StrAsciiExt; +pub use extra::arc::Arc; pub use std::iter; pub use cssparser::*; pub use errors::{ErrorLoggerIterator, log_css_error}; @@ -979,7 +980,7 @@ fn get_initial_values() -> ComputedValues { // Most specific/important declarations last -pub fn cascade(applicable_declarations: &[@[PropertyDeclaration]], +pub fn cascade(applicable_declarations: &[Arc<~[PropertyDeclaration]>], parent_style: Option< &ComputedValues>) -> ComputedValues { let initial_keep_alive; @@ -1002,7 +1003,7 @@ pub fn cascade(applicable_declarations: &[@[PropertyDeclaration]], % endfor }; for sub_list in applicable_declarations.iter() { - for declaration in sub_list.iter() { + for declaration in sub_list.get().iter() { match declaration { % for property in LONGHANDS: &${property.ident}_declaration(ref value) => { diff --git a/src/components/style/selector_matching.rs b/src/components/style/selector_matching.rs index a203df620e1..d6c7d75fdd0 100644 --- a/src/components/style/selector_matching.rs +++ b/src/components/style/selector_matching.rs @@ -2,8 +2,8 @@ * 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 std::at_vec; use std::ascii::StrAsciiExt; +use extra::arc::Arc; use extra::sort::tim_sort; use selectors::*; @@ -54,7 +54,7 @@ impl Stylist { // TODO: avoid copying? rules.$priority.push(Rule { selector: @(*selector).clone(), - declarations:at_vec::to_managed(style_rule.declarations.$priority), + declarations: Arc::new(style_rule.declarations.$priority.clone()), }) } } @@ -79,7 +79,7 @@ impl Stylist { pub fn get_applicable_declarations<N: TreeNode<T>, T: TreeNodeRefAsElement<N, E>, E: ElementLike>( &self, element: &T, style_attribute: Option<&PropertyDeclarationBlock>, - pseudo_element: Option<PseudoElement>) -> ~[@[PropertyDeclaration]] { + pseudo_element: Option<PseudoElement>) -> ~[Arc<~[PropertyDeclaration]>] { assert!(element.is_element()) assert!(style_attribute.is_none() || pseudo_element.is_none(), "Style attributes do not apply to pseudo-elements") @@ -89,7 +89,7 @@ impl Stylist { ($rules: expr) => { for rule in $rules.iter() { if matches_selector::<N, T, E>(rule.selector, element, pseudo_element) { - applicable_declarations.push(rule.declarations) + applicable_declarations.push(rule.declarations.clone()) } } }; @@ -102,11 +102,11 @@ impl Stylist { // Style attributes have author origin but higher specificity than style rules. append!(self.author_rules.normal); // TODO: avoid copying? - style_attribute.map(|sa| applicable_declarations.push(at_vec::to_managed(sa.normal))); + style_attribute.map(|sa| applicable_declarations.push(Arc::new(sa.normal.clone()))); append!(self.author_rules.important); // TODO: avoid copying? - style_attribute.map(|sa| applicable_declarations.push(at_vec::to_managed(sa.important))); + style_attribute.map(|sa| applicable_declarations.push(Arc::new(sa.important.clone()))); append!(self.user_rules.important); append!(self.ua_rules.important); @@ -131,7 +131,7 @@ impl PerOriginRules { #[deriving(Clone)] struct Rule { selector: @Selector, - declarations: @[PropertyDeclaration], + declarations: Arc<~[PropertyDeclaration]>, } diff --git a/src/components/util/geometry.rs b/src/components/util/geometry.rs index 4acc8d8dd72..d67fae0c003 100644 --- a/src/components/util/geometry.rs +++ b/src/components/util/geometry.rs @@ -8,52 +8,78 @@ use geom::size::Size2D; use std::num::{NumCast, One, Zero}; -#[deriving(Clone,Eq)] +#[deriving(Clone)] pub struct Au(i32); +impl Eq for Au { + #[inline] + fn eq(&self, other: &Au) -> bool { + **self == **other + } + #[inline] + fn ne(&self, other: &Au) -> bool { + **self != **other + } +} + impl Add<Au,Au> for Au { + #[inline] fn add(&self, other: &Au) -> Au { Au(**self + **other) } } impl Sub<Au,Au> for Au { + #[inline] fn sub(&self, other: &Au) -> Au { Au(**self - **other) } } impl Mul<Au,Au> for Au { + #[inline] fn mul(&self, other: &Au) -> Au { Au(**self * **other) } } impl Div<Au,Au> for Au { + #[inline] fn div(&self, other: &Au) -> Au { Au(**self / **other) } } impl Rem<Au,Au> for Au { + #[inline] fn rem(&self, other: &Au) -> Au { Au(**self % **other) } } impl Neg<Au> for Au { + #[inline] fn neg(&self) -> Au { Au(-**self) } } impl Ord for Au { + #[inline] fn lt(&self, other: &Au) -> bool { **self < **other } + #[inline] fn le(&self, other: &Au) -> bool { **self <= **other } + #[inline] fn ge(&self, other: &Au) -> bool { **self >= **other } + #[inline] fn gt(&self, other: &Au) -> bool { **self > **other } } impl One for Au { + #[inline] fn one() -> Au { Au(1) } } impl Zero for Au { + #[inline] fn zero() -> Au { Au(0) } + #[inline] fn is_zero(&self) -> bool { **self == 0 } } impl Num for Au {} +#[inline] pub fn min(x: Au, y: Au) -> Au { if x < y { x } else { y } } +#[inline] pub fn max(x: Au, y: Au) -> Au { if x > y { x } else { y } } impl NumCast for Au { @@ -70,6 +96,14 @@ impl ToPrimitive for Au { fn to_u64(&self) -> Option<u64> { Some(**self as u64) } + + fn to_f32(&self) -> Option<f32> { + (**self).to_f32() + } + + fn to_f64(&self) -> Option<f64> { + (**self).to_f64() + } } pub fn box<T:Clone + Ord + Add<T,T> + Sub<T,T>>(x: T, y: T, w: T, h: T) -> Rect<T> { @@ -77,42 +111,58 @@ pub fn box<T:Clone + Ord + Add<T,T> + Sub<T,T>>(x: T, y: T, w: T, h: T) -> Rect< } impl Au { + /// FIXME(pcwalton): Workaround for lack of cross crate inlining of newtype structs! + #[inline] + pub fn new(value: i32) -> Au { + Au(value) + } + + #[inline] pub fn scale_by(self, factor: f64) -> Au { - Au(((*self as f64) * factor).round() as i32) + Au(((*self as f64) * factor) as i32) } + #[inline] pub fn from_px(px: int) -> Au { NumCast::from(px * 60).unwrap() } + #[inline] pub fn to_nearest_px(&self) -> int { ((**self as f64) / 60f64).round() as int } + #[inline] pub fn to_snapped(&self) -> Au { let res = **self % 60i32; return if res >= 30i32 { return Au(**self - res + 60i32) } else { return Au(**self - res) }; } + #[inline] pub fn zero_point() -> Point2D<Au> { Point2D(Au(0), Au(0)) } + #[inline] pub fn zero_rect() -> Rect<Au> { let z = Au(0); Rect(Point2D(z, z), Size2D(z, z)) } + #[inline] pub fn from_pt(pt: f64) -> Au { from_px(pt_to_px(pt) as int) } + #[inline] pub fn from_frac_px(px: f64) -> Au { Au((px * 60f64) as i32) } + #[inline] pub fn min(x: Au, y: Au) -> Au { if *x < *y { x } else { y } } + #[inline] pub fn max(x: Au, y: Au) -> Au { if *x > *y { x } else { y } } } @@ -159,3 +209,4 @@ pub fn to_frac_px(au: Au) -> f64 { pub fn from_pt(pt: f64) -> Au { from_px((pt / 72f64 * 96f64) as int) } + diff --git a/src/components/util/range.rs b/src/components/util/range.rs index a0943728a77..e52dbae02ab 100644 --- a/src/components/util/range.rs +++ b/src/components/util/range.rs @@ -22,58 +22,76 @@ pub struct Range { } impl Range { + #[inline] pub fn new(off: uint, len: uint) -> Range { - Range { off: off, len: len } + Range { + off: off, + len: len, + } } + #[inline] pub fn empty() -> Range { Range::new(0, 0) } } impl Range { + #[inline] pub fn begin(&self) -> uint { self.off } + #[inline] pub fn length(&self) -> uint { self.len } + #[inline] pub fn end(&self) -> uint { self.off + self.len } + #[inline] pub fn eachi(&self) -> iter::Range<uint> { range(self.off, self.off + self.len) } + #[inline] pub fn contains(&self, i: uint) -> bool { i >= self.begin() && i < self.end() } + #[inline] pub fn is_valid_for_string(&self, s: &str) -> bool { self.begin() < s.len() && self.end() <= s.len() && self.length() <= s.len() } + #[inline] pub fn is_empty(&self) -> bool { self.len == 0 } + #[inline] pub fn shift_by(&mut self, i: int) { self.off = ((self.off as int) + i) as uint; } + #[inline] pub fn extend_by(&mut self, i: int) { self.len = ((self.len as int) + i) as uint; } + #[inline] pub fn extend_to(&mut self, i: uint) { self.len = i - self.off; } + #[inline] pub fn adjust_by(&mut self, off_i: int, len_i: int) { self.off = ((self.off as int) + off_i) as uint; self.len = ((self.len as int) + len_i) as uint; } + #[inline] pub fn reset(&mut self, off_i: uint, len_i: uint) { self.off = off_i; self.len = len_i; } + #[inline] pub fn intersect(&self, other: &Range) -> Range { let begin = max(self.begin(), other.begin()); let end = min(self.end(), other.end()); @@ -88,6 +106,7 @@ impl Range { /// Computes the relationship between two ranges (`self` and `other`), /// from the point of view of `self`. So, 'EntirelyBefore' means /// that the `self` range is entirely before `other` range. + #[inline] pub fn relation_to_range(&self, other: &Range) -> RangeRelation { if other.begin() > self.end() { return EntirelyBefore; @@ -116,6 +135,7 @@ impl Range { self, other)); } + #[inline] pub fn repair_after_coalesced_range(&mut self, other: &Range) { let relation = self.relation_to_range(other); debug!("repair_after_coalesced_range: possibly repairing range %?", self); diff --git a/src/components/util/slot.rs b/src/components/util/slot.rs new file mode 100644 index 00000000000..6c8eca9068f --- /dev/null +++ b/src/components/util/slot.rs @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! An in-place, dynamically borrowable slot. Useful for "mutable fields". Assuming this works out +//! well, this type should be upstreamed to the Rust standard library. + +use std::cast; + +#[unsafe_no_drop_flag] +#[no_freeze] +pub struct Slot<T> { + // NB: Must be priv, or else someone could borrow it. + priv value: T, + priv immutable_borrow_count: u8, + priv mutably_borrowed: bool, +} + +impl<T:Clone> Clone for Slot<T> { + #[inline] + fn clone(&self) -> Slot<T> { + Slot { + value: self.value.clone(), + immutable_borrow_count: 0, + mutably_borrowed: false, + } + } +} + +#[unsafe_destructor] +impl<T> Drop for Slot<T> { + fn drop(&mut self) { + // Noncopyable. + } +} + +pub struct SlotRef<'self,T> { + ptr: &'self T, + priv immutable_borrow_count: *mut u8, +} + +#[unsafe_destructor] +impl<'self,T> Drop for SlotRef<'self,T> { + #[inline] + fn drop(&mut self) { + unsafe { + *self.immutable_borrow_count -= 1 + } + } +} + +pub struct MutSlotRef<'self,T> { + ptr: &'self mut T, + priv mutably_borrowed: *mut bool, +} + +#[unsafe_destructor] +impl<'self,T> Drop for MutSlotRef<'self,T> { + #[inline] + fn drop(&mut self) { + unsafe { + *self.mutably_borrowed = false + } + } +} + +impl<T> Slot<T> { + #[inline] + pub fn init(value: T) -> Slot<T> { + Slot { + value: value, + immutable_borrow_count: 0, + mutably_borrowed: false, + } + } + + #[inline] + pub fn borrow<'a>(&'a self) -> SlotRef<'a,T> { + unsafe { + if self.immutable_borrow_count == 255 || self.mutably_borrowed { + self.fail() + } + let immutable_borrow_count = cast::transmute_mut(&self.immutable_borrow_count); + *immutable_borrow_count += 1; + SlotRef { + ptr: &self.value, + immutable_borrow_count: immutable_borrow_count, + } + } + } + + #[inline] + pub fn mutate<'a>(&'a self) -> MutSlotRef<'a,T> { + unsafe { + if self.immutable_borrow_count > 0 || self.mutably_borrowed { + self.fail() + } + let mutably_borrowed = cast::transmute_mut(&self.mutably_borrowed); + *mutably_borrowed = true; + MutSlotRef { + ptr: cast::transmute_mut(&self.value), + mutably_borrowed: mutably_borrowed, + } + } + } + + #[inline] + pub fn set(&self, value: T) { + *self.mutate().ptr = value + } + + #[inline(never)] + pub fn fail(&self) { + fail!("slot is borrowed") + } +} + +impl<T:Clone> Slot<T> { + #[inline] + pub fn get(&self) -> T { + self.value.clone() + } +} + + + + diff --git a/src/components/util/time.rs b/src/components/util/time.rs index 35186ef3fa5..9937dbd40e0 100644 --- a/src/components/util/time.rs +++ b/src/components/util/time.rs @@ -2,23 +2,28 @@ * 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/. */ -// Timing functions. -use std::comm::{Port, SharedChan}; -use std::iter::AdditiveIterator; -use std::rt::io::timer::Timer; -use std::task::spawn_with; +//! Timing functions. use extra::sort::tim_sort; use extra::time::precise_time_ns; use extra::treemap::TreeMap; +use std::comm::{Port, SendDeferred, SharedChan}; +use std::iter::AdditiveIterator; +use std::rt::io::timer::Timer; +use std::task::spawn_with; // front-end representation of the profiler used to communicate with the profiler #[deriving(Clone)] pub struct ProfilerChan(SharedChan<ProfilerMsg>); + impl ProfilerChan { pub fn new(chan: Chan<ProfilerMsg>) -> ProfilerChan { ProfilerChan(SharedChan::new(chan)) } + + pub fn send_deferred(&self, msg: ProfilerMsg) { + (**self).send_deferred(msg); + } } pub enum ProfilerMsg { @@ -185,7 +190,7 @@ pub fn profile<T>(category: ProfilerCategory, let val = callback(); let end_time = precise_time_ns(); let ms = ((end_time - start_time) as f64 / 1000000f64); - profiler_chan.send(TimeMsg(category, ms)); + profiler_chan.send_deferred(TimeMsg(category, ms)); return val; } diff --git a/src/components/util/util.rc b/src/components/util/util.rc index bbcf7203740..c84a87b13e5 100644 --- a/src/components/util/util.rc +++ b/src/components/util/util.rc @@ -16,6 +16,7 @@ extern mod geom; pub mod cache; pub mod geometry; pub mod range; +pub mod slot; pub mod time; pub mod tree; pub mod url; diff --git a/src/support/spidermonkey/mozjs b/src/support/spidermonkey/mozjs -Subproject 81aeed0b6159acdedf7dd530fb6c3bde5ddbb70 +Subproject d57edb998865f9616d9164d561f26d54ade4964 diff --git a/src/test/html/longcatbot.png b/src/test/html/longcatbot.png Binary files differindex 10b897e88a8..e1a4bc00fcf 100644 --- a/src/test/html/longcatbot.png +++ b/src/test/html/longcatbot.png diff --git a/src/test/html/longcatmid.png b/src/test/html/longcatmid.png Binary files differindex bd9a921ac64..e1b6f83eea5 100644 --- a/src/test/html/longcatmid.png +++ b/src/test/html/longcatmid.png diff --git a/src/test/html/longcattop.png b/src/test/html/longcattop.png Binary files differindex beb85663d3a..1d786dc1950 100644 --- a/src/test/html/longcattop.png +++ b/src/test/html/longcattop.png diff --git a/src/test/html/rust-0.png b/src/test/html/rust-0.png Binary files differindex dd0f5c102fc..20d93badf5e 100644 --- a/src/test/html/rust-0.png +++ b/src/test/html/rust-0.png diff --git a/src/test/html/rust-135.png b/src/test/html/rust-135.png Binary files differindex 1f4f8500432..d5a26f62139 100644 --- a/src/test/html/rust-135.png +++ b/src/test/html/rust-135.png diff --git a/src/test/html/rust-180.png b/src/test/html/rust-180.png Binary files differindex d3230266ce1..af2a0631b13 100644 --- a/src/test/html/rust-180.png +++ b/src/test/html/rust-180.png diff --git a/src/test/html/rust-225.png b/src/test/html/rust-225.png Binary files differindex 03252755fc6..b0a671ea3b1 100644 --- a/src/test/html/rust-225.png +++ b/src/test/html/rust-225.png diff --git a/src/test/html/rust-270.png b/src/test/html/rust-270.png Binary files differindex 2fc953bb363..0c7d1d7f002 100644 --- a/src/test/html/rust-270.png +++ b/src/test/html/rust-270.png diff --git a/src/test/html/rust-315.png b/src/test/html/rust-315.png Binary files differindex 4c41fad4c0c..5d15acd0cfb 100644 --- a/src/test/html/rust-315.png +++ b/src/test/html/rust-315.png diff --git a/src/test/html/rust-45.png b/src/test/html/rust-45.png Binary files differindex d1bc4bbc05d..b7dd1fd9c5a 100644 --- a/src/test/html/rust-45.png +++ b/src/test/html/rust-45.png diff --git a/src/test/html/rust-90.png b/src/test/html/rust-90.png Binary files differindex 0c956fce847..25fd8a66251 100644 --- a/src/test/html/rust-90.png +++ b/src/test/html/rust-90.png diff --git a/src/test/html/test_sandboxed_iframe.html b/src/test/html/test_sandboxed_iframe.html index d4dbb045ea7..061ea8c09a5 100644 --- a/src/test/html/test_sandboxed_iframe.html +++ b/src/test/html/test_sandboxed_iframe.html @@ -37,11 +37,11 @@ function run() { var now = new Date(); var div = document.getElementsByTagName('div')[0]; var elems = []; - for (var i = 0; i < 900; i++) { + for (var i = 0; i < 600; i++) { elems.push(i); } var outer = []; - for (var i = 0; i < 900; i++) { + for (var i = 0; i < 600; i++) { outer.push(elems); } var a = new Matrix(outer); @@ -56,10 +56,10 @@ function run() { setTimeout(function forever() { run(); - setTimeout(forever, 3000); + setTimeout(forever, 1000); }, 0); </script> -<p>Time required to multiply two 900x900 matrices:</p> +<p>Time required to multiply two 600x600 matrices:</p> <div></div> </body> </html> |