From 42092921c17a779eb8c0db3b056204dfaaa9dab7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 24 Oct 2013 16:57:42 -0700 Subject: [PATCH] Optimize reflow by changing enums to traits and inlining more --- Makefile.in | 2 +- src/components/gfx/font.rs | 44 +- src/components/gfx/platform/linux/font.rs | 4 +- src/components/gfx/text/glyph.rs | 56 +- src/components/gfx/text/text_run.rs | 25 +- .../main/compositing/compositor_layer.rs | 5 +- src/components/main/css/node_style.rs | 5 +- src/components/main/layout/block.rs | 613 ++++--- src/components/main/layout/box.rs | 1495 +++++++++-------- src/components/main/layout/box_builder.rs | 376 +++-- .../main/layout/display_list_builder.rs | 16 +- src/components/main/layout/float.rs | 331 ++-- src/components/main/layout/float_context.rs | 161 +- src/components/main/layout/flow.rs | 740 ++++---- src/components/main/layout/inline.rs | 513 +++--- src/components/main/layout/layout_task.rs | 240 ++- src/components/main/layout/model.rs | 25 +- src/components/main/layout/text.rs | 101 +- src/components/main/layout/util.rs | 18 +- src/components/main/pipeline.rs | 2 - .../main/platform/common/glfw_windowing.rs | 36 +- src/components/net/local_image_cache.rs | 10 +- src/components/script/dom/node.rs | 14 +- .../script/html/hubbub_html_parser.rs | 2 +- src/components/style/properties.rs.mako | 5 +- src/components/style/selector_matching.rs | 14 +- src/components/util/geometry.rs | 57 +- src/components/util/range.rs | 22 +- src/components/util/slot.rs | 127 ++ src/components/util/time.rs | 17 +- src/components/util/util.rc | 1 + src/support/spidermonkey/mozjs | 2 +- src/test/html/longcatbot.png | Bin 17588 -> 18610 bytes src/test/html/longcatmid.png | Bin 329 -> 463 bytes src/test/html/longcattop.png | Bin 17457 -> 18222 bytes src/test/html/rust-0.png | Bin 4399 -> 4399 bytes src/test/html/rust-135.png | Bin 10767 -> 10547 bytes src/test/html/rust-180.png | Bin 4395 -> 4395 bytes src/test/html/rust-225.png | Bin 10889 -> 10808 bytes src/test/html/rust-270.png | Bin 4497 -> 4497 bytes src/test/html/rust-315.png | Bin 10753 -> 10574 bytes src/test/html/rust-45.png | Bin 10873 -> 10807 bytes src/test/html/rust-90.png | Bin 4466 -> 4466 bytes src/test/html/test_sandboxed_iframe.html | 8 +- 44 files changed, 2725 insertions(+), 2362 deletions(-) create mode 100644 src/components/util/slot.rs 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 { - 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 { 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>, } +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], + glyphs: Arc<~[Arc]>, } -/// This is a hack until TextRuns are normally sendable, or we instead use Arc 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], + priv glyphs: Arc<~[Arc]>, } 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] { &self.glyphs } + pub fn glyphs(&'self self) -> &'self ~[Arc] { + 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) -> (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 { 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, + 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; } + 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; } - 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? - } + 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); - 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; - }; + 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); - base.position.origin.y = clearance + base.model.margin.top; + // The associated box is the border box of this flow. + model.margin.top = margin_top; + model.margin.bottom = margin_bottom; - 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; + position.origin.y = clearance + model.margin.top; - noncontent_height = noncontent_height + clearance + - base.model.margin.top + base.model.margin.bottom; - } + 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 + 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(&mut self, - builder: &DisplayListBuilder, - dirty: &Rect, - list: &Cell>) - -> 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( + &mut self, + builder: &DisplayListBuilder, + dirty: &Rect, + list: &Cell>) + -> 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 RenderBox { - pub fn teardown(&self) { - match *self { - TextRenderBoxClass(box) => box.teardown(), - _ => () +impl Clone for @RenderBox { + fn clone(&self) -> @RenderBox { + *self + } +} + +// 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( + &self, + list: &Cell>, + absolute_bounds: &Rect); + + /// Adds the display items necessary to paint the borders of this render box to a display list + /// if necessary. + fn paint_borders_if_applicable( + &self, + list: &Cell>, + abs_bounds: &Rect); + + /// 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( + &self, + _: &DisplayListBuilder, + dirty: &Rect, + offset: &Point2D, + list: &Cell>); +} + +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, } 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 = 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 = 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 = 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, Option), - SplitDidNotFit(Option, Option) + 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, /// The position of this box relative to its owning flow. - position: Rect, + position: Slot>, /// The core parameters (border, padding, margin) used by the box model. - model: BoxModel, + model: Slot, /// A debug ID. /// @@ -180,394 +624,63 @@ 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(&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) - } - } - } - - /// Borrows this render box mutably in order to work with its common data. - #[inline(always)] - pub fn with_mut_base(&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) - } - } - } - - /// A convenience function to return the position of this box. - pub fn position(&self) -> Rect { - do self.with_base |base| { - base.position - } - } - - /// A convenience function to return the debugging ID of this box. pub fn id(&self) -> int { - do self.with_mut_base |base| { - base.id + 0 + } + + 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 + } + + 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 } } - /// 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 - } - } - - /// 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 - } - } - - /// 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 - } - } - - /// 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) - } - (_, _) => 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. - 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.") - } - - 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 = 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); - } - } - - pieces_processed_count += 1; - - if !should_continue { - break - } - } - - 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 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)) - }; - - if pieces_processed_count == 1 || left_box.is_none() { - SplitDidNotFit(left_box, right_box) - } else { - SplitDidFit(left_box, right_box) - } - } - } - } - - /// 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 - } - } - } - - /// 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. - - 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), - - ImageRenderBoxClass(image_box) => { - self.image_width(image_box) - } - - TextRenderBoxClass(text_box) => { - text_box.run.min_width_for_range(&text_box.range) - } - - UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here.") - } - } - - /// 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), - - ImageRenderBoxClass(image_box) => { - self.image_width(image_box) - } - - 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); - } - - max_line_width - } - - UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here."), - } - } - - // 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 = 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) - } - - // 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 = 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 - }; - - Au::from_px(px_height) - } - - /// 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) { - // TODO: This should actually do some computation! See CSS 2.1, Sections 10.3 and 10.4. - (Au(0), Au(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) { - // TODO: This should actually do some computation! See CSS 2.1, Sections 10.5 and 10.6. - (Au(0), Au(0)) - } - - pub fn compute_padding(&self, cb_width: Au) { - do self.with_mut_base |base| { - base.model.compute_padding(base.node.style(), cb_width); - } + pub fn compute_padding(&self, containing_block_width: Au) { + self.model.mutate().ptr.compute_padding(self.node.style(), containing_block_width); } 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 - } - } - - pub fn with_model(&self, callback: &fn(&mut BoxModel) -> R) -> R { - do self.with_mut_base |base| { - callback(&mut base.model) - } + 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 } /// 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 { - 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) - } + 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) } /// The box formed by the border edge as defined in CSS 2.1 ยง 8.1. Coordinates are relative to @@ -584,37 +697,233 @@ impl RenderBox { 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()) - } - - /// A convenience function to access the DOM node that this render box represents. - pub fn node(&self) -> AbstractNode { - self.with_base(|base| base.node) - } - /// 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 { - 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, - } + let mut node = self.node; + while !node.is_element() { + match node.parent_node() { + None => fail!("no nearest element?!"), + Some(parent) => node = parent, } - node + } + node + } + + // Always inline for SCCP. + #[inline(always)] + pub fn clear(&self) -> Option { + let style = self.node.style(); + match style.Box.clear { + clear::none => None, + clear::left => Some(ClearLeft), + clear::right => Some(ClearRight), + clear::both => Some(ClearBoth), } } - // - // Painting - // + /// 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(); + + debug!("(font style) start: %?", self.nearest_ancestor_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, + } + } + + // FIXME(pcwalton): Why &'static??? Isn't that wildly unsafe? + #[inline(always)] + pub fn style(&self) -> &'static ComputedValues { + self.node.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) + -> 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 + } + } + get_propagated_text_decoration(self.nearest_ancestor_element()) + } + +} + +impl RenderBoxUtils for @RenderBox { + #[inline(always)] + fn base<'a>(&'a self) -> &'a RenderBoxBase { + unsafe { + let (_, box_ptr): (uint, *Box) = cast::transmute(*self); + cast::transmute(&(*box_ptr).data) + } + } + + fn is_replaced(&self) -> bool { + self.class() == ImageRenderBoxClass + } + + 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. + 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::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. + 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::new(0), Au::new(0)) + } + + /// Adds the display items necessary to paint the background of this render box to the display + /// list if necessary. + fn paint_background_if_applicable( + &self, + list: &Cell>, + absolute_bounds: &Rect) { + // 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(); + + 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)) + } + } + } + + /// Adds the display items necessary to paint the borders of this render box to a display list + /// if necessary. + fn paint_borders_if_applicable( + &self, + list: &Cell>, + abs_bounds: &Rect) { + // Fast path. + let base = self.base(); + let border = base.model.get().border; + if border.is_zero() { + return + } + + 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; + + // 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)) + } + } /// Adds the display items for this render box to the given display list. /// @@ -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(&self, - _: &DisplayListBuilder, - dirty: &Rect, - offset: &Point2D, - list: &Cell>) { - let box_bounds = self.position(); + fn build_display_list( + &self, + _: &DisplayListBuilder, + dirty: &Rect, + offset: &Point2D, + list: &Cell>) { + 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(&self, - list: &Cell>, - absolute_bounds: &Rect) { - // 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 { - 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) -> 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) -> 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(&self, - list: &Cell>, - abs_bounds: &Rect) { - // 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 (&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(&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, _: InlineSpacerSide) - -> Option { + -> Option<@RenderBox> { None } @@ -89,17 +105,18 @@ impl<'self> BoxGenerator<'self> { ctx: &LayoutContext, node: AbstractNode, 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) { - debug!("BoxGenerator[f%d]: popping node: %s", self.flow.id(), node.debug_str()); + pub fn pop_node(&mut self, ctx: &LayoutContext, node: AbstractNode) { + 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, 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, 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) -> RenderBoxType { + fn decide_box_type(&self, node: AbstractNode) -> 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, - mut grandparent_generator: Option>, - mut parent_generator: BoxGenerator<'a>, - mut prev_sibling_generator: Option>) - -> BoxConstructResult<'a> { + pub fn construct_recursively<'a>( + &mut self, + layout_ctx: &LayoutContext, + cur_node: AbstractNode, + mut grandparent_generator: Option>, + mut parent_generator: BoxGenerator<'a>, + mut prev_sibling_generator: Option>) + -> 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, - parent_generator: &mut BoxGenerator<'a>, - ty: FlowContextType) - -> BoxGenerator<'a> { - + pub fn create_child_generator<'a>( + &mut self, + node: AbstractNode, + 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) - -> Result { + -> 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) -> FlowContext { + pub fn make_flow(&mut self, flow_type: FlowType, node: AbstractNode) + -> ~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, + 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(&mut self, + builder: &DisplayListBuilder, + dirty: &Rect, + list: &Cell>) + -> 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; - } + + position.size.height = height; } } - pub fn build_display_list_float(&mut self, - builder: &DisplayListBuilder, - dirty: &Rect, - list: &Cell>) - -> 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; - } - } - - 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], +struct FloatContextBase { + /// This is an option of a vector to avoid allocation in the fast path (no floats). + float_data: Option<~[Option]>, floats_used: uint, - max_y : Au, - offset: Point2D + max_y: Au, + offset: Point2D, } #[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(&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(&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 { 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); + 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); + 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; + 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); + 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) -> 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 !self.is_inorder() { - callback(self) - } else { - true - } + /// 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") } - 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, borrowed immutably. Fails + /// otherwise. + fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow { + fail!("called as_immutable_inline() on a non-inline flow") + } - if !callback(self) { - return false; - } + /// 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") + } - 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; - } - } + /// 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") + } + // Main methods + + /// Pass 1 of reflow: computes minimum and preferred widths. + fn bubble_widths(&mut self, _ctx: &mut LayoutContext) { + fail!("bubble_widths not yet implemented") + } + + /// Pass 2 of reflow: computes width. + fn assign_widths(&mut self, _ctx: &mut LayoutContext) { + fail!("assign_widths not yet implemented") + } + + /// Pass 3 of reflow: computes height. + fn assign_height(&mut self, _ctx: &mut LayoutContext) { + fail!("assign_height not yet implemented") + } + + /// 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") + } + + /// 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") + } + + /// Returns a debugging string describing this flow. + fn debug_str(&self) -> ~str { + ~"???" + } +} + +// Base access + +#[inline(always)] +pub fn base<'a>(this: &'a FlowContext) -> &'a FlowData { + unsafe { + let (_, ptr): (uint, &FlowData) = cast::transmute(this); + ptr + } +} + +#[inline(always)] +pub fn mut_base<'a>(this: &'a mut FlowContext) -> &'a mut FlowData { + unsafe { + let (_, ptr): (uint, &mut FlowData) = cast::transmute(this); + ptr + } +} + +/// 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(self, traversal: &mut T) -> bool; + + /// Traverses the tree in postorder. + fn traverse_postorder(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(self, f: &fn(Option<&mut ~FlowContext:>) -> R) -> R; + + /// Invokes a closure with the last child of this flow. + fn with_last_child(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( + self, + builder: &DisplayListBuilder, + dirty: &Rect, + list: &Cell>) + -> 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, + } + } +} + +impl FlowContext for AbsoluteFlow { + fn class(&self) -> FlowClass { + AbsoluteFlowClass + } +} + +pub struct InlineBlockFlow { + base: FlowData, +} + +impl InlineBlockFlow { + pub fn new(base: FlowData) -> InlineBlockFlow { + InlineBlockFlow { + base: base, + } + } +} + +impl FlowContext for InlineBlockFlow { + fn class(&self) -> FlowClass { + InlineBlockFlowClass + } +} + +pub struct TableFlow { + base: FlowData, +} + +impl TableFlow { + pub fn new(base: FlowData) -> TableFlow { + TableFlow { + base: base, + } + } +} + +impl FlowContext for TableFlow { + fn class(&self) -> FlowClass { + TableFlowClass + } +} + +/// 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 + } +} + +/// 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 each_postorder_prune(&mut self, prune: &fn(&mut FlowContext) -> bool, - callback: &fn(&mut FlowContext) -> bool) - -> bool { - if prune(self) { - return true; - } - - 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; - } - } - - callback(self) - } - - pub fn each_preorder(&mut self, callback: &fn(&mut FlowContext) -> bool) -> bool { - self.each_preorder_prune(|_| false, callback) - } - - pub fn each_postorder(&mut self, callback: &fn(&mut FlowContext) -> bool) -> bool { - self.each_postorder_prune(|_| false, callback) - } -} - -impl<'self> FlowContext { - pub fn is_block_like(&self) -> bool { - match *self { - BlockFlow(*) | FloatFlow(*) => true, - _ => false, - } - } - - pub fn is_leaf(&self) -> bool { - do self.with_base |base| { - base.children.len() == 0 - } - } - - 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()); - } - } - - pub fn with_first_child(&mut self, cb: &fn(Option<&mut FlowContext>) -> R) -> R { - do self.with_mut_base |base| { - cb(base.children.front_mut()) - } - } - - pub fn with_last_child(&mut self, cb: &fn(Option<&mut FlowContext>) -> R) -> R { - do self.with_mut_base |base| { - cb(base.children.back_mut()) - } - } - - pub fn last_child(&'self mut self) -> Option<&'self mut FlowContext> { - self.mut_base().children.back_mut() - } - - pub fn remove_first(&mut self) { - do self.with_mut_base |base| { - base.children.pop_front(); - } - } - - pub fn remove_last(&mut self) { - do self.with_mut_base |base| { - base.children.pop_back(); - } - } - - pub fn child_iter<'a>(&'a mut self) -> MutDListIterator<'a, FlowContext> { - self.mut_base().children.mut_iter() - } - -} - -impl<'self> FlowContext { - pub fn with_base(&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) - } - } - pub fn with_mut_base(&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), - } - } - 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, restyle_damage: RestyleDamage, - children: DList, + 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 for BoxIterator { - fn next(&mut self) -> Option { + +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 for BoxIterator { } } } + impl FlowData { pub fn new(id: int, node: AbstractNode) -> 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 { - do self.with_base |common_info| { - common_info.position +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, } } - #[inline(always)] - pub fn is_inorder(&self) -> bool { - do self.with_base |common_info| { - common_info.is_inorder + /// Returns true if this flow has no children. + fn is_leaf(self) -> bool { + base(self).children.len() == 0 + } + + /// 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, } } - /// 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 - } - } - - 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())) - } - } - - 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())) - } - } - - 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())) - } - } - - 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())) - } - } - - 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())) - } - } - - 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())) - } - } - - 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())) - } - } - - 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())) - } - } - - pub fn build_display_list(&mut self, - builder: &DisplayListBuilder, - dirty: &Rect, - list: &Cell>) - -> 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) - } - } - - } - /// 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 - } - } - - - // Actual methods that do not require much flow-specific logic - pub fn foldl_all_boxes(&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)), - } - } - - pub fn foldl_boxes_for_node(&mut self, - node: AbstractNode, - 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 - } - } - } - - 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, + /// 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, } } /// Dumps the flow tree for debugging. - pub fn dump(&mut self) { - self.dump_indent(0); + fn dump(self) { + // TODO(pcwalton): Fill this in. } +} - /// 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("---- "); +impl<'self> MutableFlowUtils for &'self mut FlowContext { + /// Traverses the tree in preorder. + fn traverse_preorder(self, traversal: &mut T) -> bool { + if traversal.should_prune(self) { + return true } - s.push_str(self.debug_str()); - stderr().write_line(s); - - // FIXME: this should have a pure/const version? - for child in self.child_iter() { - child.dump_indent(indent + 1) + if !traversal.process(self) { + return false } - } - - 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) + for kid in child_iter(self) { + if !kid.traverse_preorder(traversal) { + return false + } + } + + true + } + + /// Traverses the tree in postorder. + fn traverse_postorder(self, traversal: &mut T) -> bool { + if traversal.should_prune(self) { + return true + } + + for kid in child_iter(self) { + if !kid.traverse_postorder(traversal) { + return false + } + } + + if !traversal.should_process(self) { + return true + } + + traversal.process(self) + } + + /// 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) + } + + /// Invokes a closure with the first child of this flow. + fn with_first_child(self, f: &fn(Option<&mut ~FlowContext:>) -> R) -> R { + f(mut_base(self).children.front_mut()) + } + + /// Invokes a closure with the last child of this flow. + fn with_last_child(self, f: &fn(Option<&mut ~FlowContext:>) -> R) -> R { + f(mut_base(self).children.back_mut()) + } + + /// Removes the first child of this flow and destroys it. + fn remove_first(self) { + let _ = mut_base(self).children.pop_front(); + } + + /// Removes the last child of this flow and destroys it. + fn remove_last(self) { + let _ = mut_base(self).children.pop_back(); + } + + fn build_display_list( + self, + builder: &DisplayListBuilder, + dirty: &Rect, + list: &Cell>) + -> 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 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::range::Range; -use servo_util::tree::TreeNodeRef; use extra::container::Deque; use extra::ringbuf::RingBuf; +use geom::{Point2D, Rect, Size2D}; +use gfx::display_list::DisplayList; +use style::computed_values::text_align; +use style::computed_values::vertical_align; +use servo_util::geometry::Au; +use servo_util::range::Range; +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, + 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) { + fn initial_line_placement(&self, first_box: @RenderBox, ceiling: Au, flow: &mut InlineFlow) + -> (Rect, 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(&self, + builder: &DisplayListBuilder, + dirty: &Rect, + list: &Cell>) + -> 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 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.style().Font.font_size; - parent_text_top = font_size; - } + 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(&self, - builder: &DisplayListBuilder, - dirty: &Rect, - list: &Cell>) - -> bool { - - //TODO: implement inline iframe size messaging - if self.common.node.is_iframe_element() { - error!("inline iframe size messaging not implemented yet"); + 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); } - - let abs_rect = Rect(self.common.abs_position, self.common.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.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, + display_list: ~Cell>>, +} + +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, @@ -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::>::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::>::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 = 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, padding: SideOffsets2D, @@ -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>) -> 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 for Window { @@ -81,7 +82,8 @@ impl WindowMethods 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 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 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 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 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, @@ -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) {} +fn assert_layout_data_is_sendable() { + assert_is_sendable(LayoutData::new()) +} + impl AbstractNode { // 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, T: TreeNodeRefAsElement, E: ElementLike>( &self, element: &T, style_attribute: Option<&PropertyDeclarationBlock>, - pseudo_element: Option) -> ~[@[PropertyDeclaration]] { + pseudo_element: Option) -> ~[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::(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 for Au { + #[inline] fn add(&self, other: &Au) -> Au { Au(**self + **other) } } impl Sub for Au { + #[inline] fn sub(&self, other: &Au) -> Au { Au(**self - **other) } } impl Mul for Au { + #[inline] fn mul(&self, other: &Au) -> Au { Au(**self * **other) } } impl Div for Au { + #[inline] fn div(&self, other: &Au) -> Au { Au(**self / **other) } } impl Rem for Au { + #[inline] fn rem(&self, other: &Au) -> Au { Au(**self % **other) } } impl Neg 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 { Some(**self as u64) } + + fn to_f32(&self) -> Option { + (**self).to_f32() + } + + fn to_f64(&self) -> Option { + (**self).to_f64() + } } pub fn box + Sub>(x: T, y: T, w: T, h: T) -> Rect { @@ -77,42 +111,58 @@ pub fn box + Sub>(x: T, y: T, w: T, h: T) -> Rect< } impl Au { - pub fn scale_by(self, factor: f64) -> Au { - Au(((*self as f64) * factor).round() as i32) + /// 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) 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 { Point2D(Au(0), Au(0)) } + #[inline] pub fn zero_rect() -> Rect { 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 { 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 { + // NB: Must be priv, or else someone could borrow it. + priv value: T, + priv immutable_borrow_count: u8, + priv mutably_borrowed: bool, +} + +impl Clone for Slot { + #[inline] + fn clone(&self) -> Slot { + Slot { + value: self.value.clone(), + immutable_borrow_count: 0, + mutably_borrowed: false, + } + } +} + +#[unsafe_destructor] +impl Drop for Slot { + 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 Slot { + #[inline] + pub fn init(value: T) -> Slot { + 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 Slot { + #[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); + impl ProfilerChan { pub fn new(chan: Chan) -> 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(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 index 81aeed0b615..d57edb99886 160000 --- a/src/support/spidermonkey/mozjs +++ b/src/support/spidermonkey/mozjs @@ -1 +1 @@ -Subproject commit 81aeed0b6159acdedf7dd530fb6c3bde5ddbb70a +Subproject commit d57edb998865f9616d9164d561f26d54ade49642 diff --git a/src/test/html/longcatbot.png b/src/test/html/longcatbot.png index 10b897e88a8b4dcc0e9fb5408c40e04db6964345..e1a4bc00fcf6d5de2b7cc3ebdb7fedfec33da71d 100644 GIT binary patch literal 18610 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 wnV;YE%5whR~{ism-x;hl=@Lqty}`=1T# zqrHNpbr@>8OdgcgbjVZFAI`3Rj%KOxLHCV zdk}TM$SIYm=eRcQ8HGtg!v6XxP5Ysk8_j{{Zmn1W4sf<}8OWBc zf#H-Y^8=7ZG4Dr!&6O*C$FFa|+i*I&XV4zHVk?*o0lc@yw(F&prDh{SX9TSuT`-wg z1IFm+=vOXJL@+o)*FymGya<}7q5R{;oq^btc-vdN;UQptboud&`+Uy+Vd`}Z|3g?J zF(Ob`w*z3n@U$p7Q0#AB#xtP9aC?bbH|{tWX#SuAfU-Uv&EQd85Pv?33^jBzvH1gR!WL+OlPB zQ7dW5+t40MQIbXk%f0afUFkNwl zK{QE{3^oUq(iB(+$oI`kWt|L~Y zczb~?eBQP!=}8U8dn`F-6xOxZIAB&xeP~4}%`02})n)%W+e6l7-*Zgcor-Y-AS0Db zTICk!cLKwe=r7A83EC~aUHD}5)NBB3I1vfSVhD38S*+2oms7PaoYrrDVkJ{kOGfmg z@^U@ksHBV(y&wt=Pjj`x3YBNKd?oQhDpd$DzJElb4nA8WmvkikKQv}TE(=r#^&LSK?NkSr74yav@8FS2vd{;8^l zMj$wN_*KwpGk1DI8IIZ{fKMdy+jN9+HUy846-mF5@2M{NqFDnGsQr_b=beoR*^Xdm ze_e%l`r$84!P{yAEu7A;l=r1o0V4s<@lzS@pWcUoS-Er|;1;-FI~C~EQ%qOxJ(G9f zCg|Tsf+$110?A3#KAI{vXx;Z5cl*H1p;a4 zWSkBNObi?X_&E@K{K5TYEq^KS1tXgN-|sN=Bn9sU{`>xadehy<{!efHKfUSyuj~2W a;$l($@m`7+;Re4<1Ok;-k}8tW^Zg&+VEP#V diff --git a/src/test/html/longcatmid.png b/src/test/html/longcatmid.png index bd9a921ac6478a00c1ddf0bbf1fd78d82f2a8acc..e1b6f83eea5b22da54e4374bf341ed179a8dc787 100644 GIT binary patch delta 411 zcmX@fbe?&FWIZzj14C-|MsFa+lJ4m1$iT3%pZiZDE0E7v9OUlAua@a2CEqi4B`cIb_Lo1CF}!yLR|m<|G#15Mj(%c zjeYWzsRs`p0-DIo%GT7}ykNn?B}#EDzBY~8tQS5I$WeM94+!-uzQ z-8yOV6ffYh7ML)4nSmP`k8zz~}EXDP8XNX=JUO_ zQmvAUQh^kMk%6JHuAzahfpLhTsg;4b6_5)g4GeD2``Q3>7eYgBeoAIqC2kF?4*P1illp`4(s zyrTI8hlM~y)`KL~<>_3xi|*L&|D^lhgg@|{r8>ihg~73>H)*)cK3efQKm4YA-=@g* z5*ibhS$fZVZq%<7=+%2aF+6mWt~VP~UCuEUhJ6>e&iy_wRr4{62~Ts!p8xW*mw#|) zbx2^G;gt{@$Nc&u$2>KbFpq89@9n-etB`?-`@oeg_r!Hn3x$mRP8`_u?|{)U1HKRJ Z@8S>Yus!;?ei_h944$rjF6*2UngA*aYqkIY diff --git a/src/test/html/longcattop.png b/src/test/html/longcattop.png index beb85663d3a06d68d361a9ed90ab2e070700ffe7..1d786dc195039354ac2d0a85e4e9cbb6be715806 100644 GIT binary patch literal 18222 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 literal 17457 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∾8@L zEk_`E>FSYF8FkqevAQR3)Dg0PnkjS@(;Bb=q*l zC))TrA&)jj3xBAwfS&_bCb7@L1JDf`sUC{6t1}1=<&SWypgb>+0a7uDAIPE|3{npo ziz*b|G3jN{umO%&F)=alBW-bvkN1@+q4Wdb?JC-jtU}6v zox+1))7E5kQqSOBG{wf~9{A|QyALjx{-jrUn9fg_tuui>Nya2|GlRWX!Sb4 zv|aXf`NcCfHpq(?FTmZ{J3GIy^Kf>?h!BTv#>7NW1Yz8lI2hIPCkHHFYxRK!=pU=q zM5$(Kiums>s(_ZElI6yo7Z;ytzw@?ratcx|29cPb|KVQ^P|sE9)>ud~Ay#A<&3ZLO z)kHbJFt2X|eS%3@Sy_YZadS(;6>JG5u|FOhn2(2oyF!BIUSl07xrO^cO#62!X+Rg( z*{{F!rOrj7#RdkFkmO`SR7XnS^MW$mvBhv|%Jwh68)im4Mj8p=4H0p06EYaM+nena zV+X&xFj7`)S(B>a%K%cAnx8)f;W6)aHozRjMvSh=SiGeCl?&C>?%>%{LklQ4IDq?p z{YK8@Z$D^w1t`^f%3DKqEJP#+|C_t<3{kjU9@`uoH$*sqnKb|2%tylr7u#eSfGIdJ zP>jK~9)j=HYER&bfgz3e-vOkc626#*XLh;J%F}BVig3__Qxk{O3`()SZy5NS=1zv3 zBzF7PTzIYiWl#>~^wikzARE9cQL{~b+_D$C4L$$f7$5sN^!sza89$ka;Fb@8PqtAx=+h^>AM`Y3fBTd*?4;!GmGIR`03I^( zGR`YZu{FbX)qEAu2B;2X$o@qLoj!hw{$m{`U+HE-rVOQv0_W7;vl0}Mx+VV@XFq{O zyZB>&7P0~`5O{c0@?wgJ=tJ;iat0FvH;29%{aZc|V5G!61@yTdWTf8f=>aEVxd^7O z-rY4Ya|JMjtIe_6!~aDbAwVAfo|7w@S@m&L(en*qgHKaa)AHIH-0Ml0-JOX-=yS4v zd!y44cm({jWn)ietB*LrCd{Zg7=1U@_rB=_{rFDv-G0>UF+Q)M4_3r$apKv~id?8? z8>*W#sxlgzn6CaDO!*g_PIh+c#UBaZHf=obKw@GTX$UJ+&h({0G5iY-mS8 z)p~uFsccF0Cf=zneC=!CV-?H^%L)x#?XT>?vG8#3SXlUm;O^_`=?Ux_7Pw_4@0-CG z25mj6Nr{Qza{&N+uBPLIu zaU}fh#|4Z}g0c_Z$9>&Kj+tEN`4uS73tJQ9!j9XQ zs;-`&q76MVe_4!7&)Itc8Tbb1WCoZ;D62a=U-d4u`*HDstK9H!P%(mu1>7A&Qki0* z8p~t5$8T#Ng;AVs3EDk#|F;-w;EsumM9Y}Q53Cjx1j>&_28w>}m}rjjT%yFp z78Lgb%OxlAI!PeDboQu-W(RW8jMe{qmW+ZzNV|U7w%__7NCfvb#`~{-qYxXEOCV6B z2yX~+_t%aDDBq4#JvLhWHr0#TpA#Two{r)BIJCNxNW>H6W%A)>t(q?GDC{ jMOK=DtYXWR^33>(Z>A`)bHKmk0eK@SFHs_<@Be=QVu{Q5 diff --git a/src/test/html/rust-0.png b/src/test/html/rust-0.png index dd0f5c102fca3a65667c6015abde05d6db1ca7b7..20d93badf5e2290baba400611b888d31dc03b5af 100644 GIT binary patch delta 93 zcmZ3lv|ee$ErC!&16>2-5JOWd19K}wV_hI=U~qfh*9Ha#2GtVRh?11Vl2ohQ{FKbJ cN(LhXpbBgnRvq@W0cv3IboFyt=akR{02#3wl>h($ delta 93 zcmZ3lv|ee$ErC!2OI<^w5JPh-10yR#3ta;s!{98NvJC?RgKCLuL`h0wNvc(DeoAIq dC4-THp|LJD4SS+zHvly-c)I$ztaD0e0sylf87Tk& diff --git a/src/test/html/rust-135.png b/src/test/html/rust-135.png index 1f4f8500432a1b44cb423fc78cd9b8b4964e53c6..d5a26f62139fb27b75fd30f2e4dad528a775657d 100644 GIT binary patch literal 10547 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 z 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 Z)5&L#ZgsOL6Sg;EJW$kBsF1S={T~M*e8vC( literal 10767 zcmZ{KWmHsO)c4HLE!{OV3_3_lBPk3>3=AO>(k)1L$WQ`ez)%VZI7kc942^V1*9bBw zjl>WFFaKw~U!D)oy6fD1_sMe5HqfRbXCnsy093j<8pgMD^uI((e0vYz zR_eJWgwAR(H2|O{jpCmj(Jc@3GuBoIRE~1|xn+19bc|sDK!_j!5ETaiT-;_w{RRMD zKmmX)TL9oe768EPliO^hcw0bhucxg6xcTq;&{mpxn?vTUrR8^XbMy1R1Ypi3(**!T z2z52oOoJA7^MZ4^;BWCArSJWn`FQQ7vZ_gisYOAeUBXE*pvshI)3ZcG#M&E-{ed!p zPfe}0)mRzlYr$obgJqCGaN%zrP|ScEX|f$zdb+>RayTx{PweV=3Vvcb?dKlcyp2C! zGJLRHzl3hy%=e4LLkiK4QrV=9KbG%M|3LN-RZf@jgbePfrarbNZ0){UqJcnK9!mEW z&d5!7Ke9FIzF}@!oi4jmY_o1k?jF@k8)-Fyh(;TM-BL|BzX)2o_(WPg2@rB;aqIj{ zYzES!OJmtr0@+K_zYXSoF5?T~uOJaT!_cYa{pEzv-G&QbjY00v~7O zHf$LqSZX!*ETze{#N~P&q^p0dpS&}!^NcTI%C)a~`E19+65=~sX>`Y}Q-oS9akbQy zIr%eLY}eyVf?f;>Z92p4006htOqE*WQ41rJX`&A^a_8VtJjDA)yQlo^JkCGrf9zat z5u|*sE-rV@;|=B!aqHp{`cZ<4WY8t#*MzYIX;t|CD|Gpv0*s_v>N<^MmFw#df|QE4 z0!ECBl2(Y8#r{|_A+UM0Mq#Aqe&>V z7eRgB_m>7UQX^&3hQvSEgp|xAeB+4mkQ0=UfLo_7$*P(QT#b|RJzYJPU)$#YGoV}M zp{qs|QYZRbIazF6+4txf;Ss#Ph7-sn$_A?oUp5}Ea9?d;x*bWe^&&q}rvS+xvI(xG zC;>*0TT^g|f=x`8+(X!(RD>^D-YQ|@KubP(fC0-L9gJziSfHEbac9a{9&8K7Z>egQ zAOXF#Oty!&uw|=eI!i*odbSe+gKMj~$JxC_uayH!fm;g?u#gWbxU;)h)JwmZ05a&e z-ZnESj!2S&2ARta^iGJ}I~sAXoU){3yyH+AuELX7tVF4dx^7`%q<{ zuT!>m_y-9!{^#wOzHfzWr}QR4mCMEQ9>y2rItp>` z5sQ9Lu*^HVKoZ10zB=5HI3ODxn2=k?5M-0Q`G={SgO;p(@>>cD%@u3uqm_Yt&Xmy> zbwdClF<{Q%EMofiiZPV=PQ&9>5{JqaZkIc>*;K7ua*z^=Dk5bxnvFCK8Ac{2zjl_5 zi7-;%(}`P-mW)1%Ue|nPIq*>$zKpE(>`k&w6|^cp26_YXyJqezMy(QD5=f$6G1^l5 zD6P+mrtT7B8Eg%)#Z3LgyM|dGcF4oX=H}MU+@rF2$Li=yV5nJ>g#Ok9X~)Pfq92$z zrr)B%GFB6^AtMBS;y$2i!>L2=N5Z6OfIwh3SsFKpI4YEmD^kb_6d--@Vc6g%$^niYU!f=z#xQppbU z$3*4dt`9fg|}8!W2F=wGw~~vI1}cBl7=xmt>2Iw(AnNu%7=+nzkFSO1QAy7>ej`w*0*}j2Z9ch8!nd^1t)T z8Rg`7_rwu%!T|mO{s04Rl-wt)$jV-CP{k61}grHcfLGpwimv0(F~vju;=MA+lrj-2|)yFvHVo( zi>BC67Qpq~TGop!rX2?T&IW0e#Q+&6rK}N=Gv&q-=plnPF9}Tp75e|GetgS5N+5fw7nun9%C0*REMU9l%DFU%8T`WMn>L=ihTL@){~S6+2Zx z)+zDE9(`Z0P6KP4MtTx;vV_~+M!0|qpqoTZV=dkfk3$byW+=isujzAAnV~CsAuLwr zPiB4ot=^BQcp-4$jqyfxWFo`J&s;|LrKw&n*Y#-jRJG#SU$E8`=V_V#sy~nJQs_Jqd*{K5tj-le@k?NFu(f==;u(7J6<7Udu z{p08gzqr*#ed0Kelm||kaY|G(OH|elw;GM9goTqa{VnL%@BJ7z`_KD>?5}xc__*Y` z*J6}|e+*&wi(2fLnKPFWhiGwm>Nr}g8g>zr9Bm;HPK)`34NRj3|#NgU%UtWtD5df$+OLCp`ZZ^7o=Y zM0jr#MsN~=6-ya=lK#GAevA0HW=~ncwrMV6PbzLK^ooSHq|{w(kjvC{!ZjvSm_2oY z=Xd8{fXC7CPwuX;Vd__`09PWHKONHbyzh{ob zRPdwW%iqG4&6$MGDJ6ETSNEt?0-GXL*8g;`zVy`J`^ZID@_AM5X^YGK^XE(`ZfOeG zR9mUNuWq^G(!LRvKs9hmsriK|q6K~uQ%hoD>`%_ABuFZ*Qz!(EO!gF}CNNCWsSa*5 z(#WUb*u7qTb#LLumTbz~Xj(g5CF_AKBYr>N{aX%^O-~FXW*!~ls^8Io9DpNM8c*gh zV;6Tf?f-n%3i3f@jFl7gV&Ygez$iTgw(zj4vo`lPx{Q zj03OS%wCFFJ?FclC+|Qu6!}OQg$pB}xsU27(e=!)|Gj;G*z~j__%3PFQ-RN0qT~ic zBe43kz?Yk8GDt?p*Zg?={6HY=XcaRde|)|>hw6EMpooM=|3<zd); z{EC@=qZ<8SP(Do>7L^yx4Ct2@m^uK#l}O6VCC71}@_S^;WqUbHDxf>H0<*js!6oa? zt<1Iz&MlJ5k^`=0ul|H)*o>-lFP-^Wv;a4Q&WOkKf;t{$dBVr#5=B31VyU|xrf06D zWocs^7Qla+4d2``^kZ-`Yn%Rt^UaxXUu+!~eJ%OvZ5gGx3}>Do`$&Kk|I0bDA=Y#v z8r$gtZ#bMAXEWKpsVV=hE2ELsM&s_pj=)mRA*66fSg_Z|?VLU&n)ihpgm|dB#)=L8 zqy(En7WgVg?=f%XNca8DVY-Zm)oG_Ql=uj?BfxbATG{3Y0dsYUYkZMUgdO;~QA^kQ zYd;S4%WUoU&Cg3Zh`jHbDp#@Ur8N~CmD#3Ue-RR*4TK?wJ4u)jv#)!E|IqE zY=52-Dq$ur#)aAHSVH_DFo!Ii--i_a#xF`3<-Keq4f2~R;OhA+VjX+_c?7tU(4^OU z-mH{LHl#IN6HtDM7sU=DYnoB_yQ=jN<^qM`5_-s*(By?bkd#ZgZ}0SD+gP6JxJml7p}ky`5wMVm#80h{D0=GZu!qADkUzcSNi8R35P@Z~s1+ospQ?%gGq1qHf} zvga{=$gNuw9`>-x!gwrFEt0$R0TGQa=0a()a<&mk%1WRxjz-yyK&zD0wtwkGGDkS6 z#OdsReDko3ex1EZ35(&n;W*$ym`A~<^Ch07qu(zbLZMT8Iw{Qt3u!QfQ*QXenP5|m zXE2$8;A&kKjQ~RMhKiNA>&^jeP02g__^^zeGUVy5^IT<^c~6}(fnOlGLLkLE+TS;J zZ$Hy?s;friuU&T|Dihv^oXIquHEi(8Wh0{q3F=7nKv@BIC_%}88Q5k!J|hgi){=zp z#Q_`MS?F);f3||sE9|y?vr~?1f36fHL>X4)d;dINpvM~DIk{&11cjan*D$?mq$YUv;8LZv?(&nk5-9bvJ0yo9aH zVxl>YxK^SdyA?KVZP=je*Q((BK059_iltW_#Yq+;4ZU$$o-9qr9K(1%L9)HwT^Q&Le+vjDGXa;)<+cm+-~Ui z1kRCm-d?kya7|K!?jwD}kHIqWWU%g&Q$LhYRj!lnf+Ne~OzvQSsM*%9zL*PN=7V;b z4kIfkD}|U$IbcXvbc2{#FJvg0qh|&v3yk!+;U<;eD`(hd6gLR6bj>e~HXrYz>>O?o zb;)069SL4etLRUnLwyKOIkosUoVZd)Zea7qZ*smVxTJj`d{}<2@{|@6pK=fG%ZK}G zQ+(i0YslgOj^CwLWM6zJViFVso6JTuz0M(Tzqw+0vdfpCE;y2@>C;LF!up+^J}LX7J203Z+a9*OYDG*iksc)ciAr-l+vmnijD+Dm>-1CHhIg9 ztjj)X!|962HQ~7YVy38a->dsD^;++%cEy$&h!}56ks9*ZRkM~$Yia zz2X!S>Cs7y@tHi*cy{+)?@c#Li_H9i!f>3537o{Q)$p&zK3SX?Gnl7WKlH&w-MHl! zjeL#VOWBl)qN%xd-Q)b8ekgZ&BhlHtnyZq{^dbV0BHX;^8!8P>hWz3gE!^LiQJ>}?woRg2bmUJcsXAs+jBMCy@eot}GrV+_r8B=GY zu>1cI=m!QqkEW%lVsCyC-osC^&_X$r4Z?UES~y<(D}c^al>dpasGIQUI!$9*7kTRy zThi19iv2d+!1Zn3BIZEDEnm1(M8Gey`vWcHWc*K7)0>`5>?~p=SFd^wU*@;xLlEXQ zrL;(;PI2q7z>Zr^1-W;#Zc;K8+2AaET_V=7G-yfe>kx_G)R*h zxM~0z-pO|!LD(s??m2ww3cmxiSlE{^bMXeBdgiL`|BO4MHpD+k@Z@{5yErpf2~u8* zD}53j5l0y6o4**)#d9b3Xo!wY*5~xEoGrr?qN9I?Op?sP)Y!v5wO>NviUD%97Y5PX zk*rA_7eJ(3@o~lDtoG*d{#_L5yZN4RzQTM+`FGCG~mM{KmBh##=6+-j4VlQNLX>29Fxu^p3> zy<;1$ZLXo)@9;fQsrYjq)olN-wt_><)fb``HaljJa^0s>_2me+?r$aY;++b2MOp6N zLE&IH&hK1FJ_7GTKcxSorK%9%kSf;6uX);ZovRDGm(jW10sl-G8{w{vl!bLqXDm~)Z&b=IhZ(==?9nR#?;dqdz zJs8%Rn4?>UA6kFo}+clOUfixbj^gWdaPTPyUtn;Hukx`-0w1Ov1L8zh?$?NaD!dA z<dPB6>e;(?g?l~L# z;ePxl&@ZDwA2!^}i2LtUmB!A_N3xzSaYT+wg15^jT;71tuRAq3}-uV;y z?m*$x;nVEY5}q~%@__iL?DXezlP2Srz~@I$pStUKd8$!Qgm5<(*~K9xmJ{)E{Dg>X z>?lGn<@zJz>$uwus3 z+7eeSo0y`l2_=%F`a!Ys4MD7U<6lnCQxmqT_3KFJMmqT}%DLV6Jb&P9%|^FlXda{N zxuo)z9me)6`|z1Wb?tI8a$po;HheCB;)gWa>~FxXxYoJW5NJL32gP_uZI*BOv@p|W zh-7_&Ds}&vie8WE*5DsH7dxzm(Z#o#5Q%rn9qHf6V8yUUncERSX@V;V>vkv3Nw z^Ih%QlmVc!=ok)r$o5#jq`uc3Vgu(qY;M!0YMT)xmf;>NN zX5_#Rj!H2^;em)ezT*jDP7d=0N>$^TcM887KIEw8Fw=j{I$GPjTA1N&u9wKX)L4ft z#a`>78l-qu_Tux)Ap;K3YMq}FIgGpSjPs>L)UJIb_95BVQ8#il2jZDozh*Jw2sz;4 z;ZP-f8>XQ?4jUPn!##o1lESRs_>q>_<-GS(dQ!z)w0dd(%0d^25>%b|^Z31OyOwS> zQ6r0k`CqMjxP}`w+CvKm6A_^e4?03z_U+jt%4#4I%L_^;O6rD1I< zVs>bVgsgu=$AbTcLymr_d*D`Bi%Lpuvkk-SWe!4YRW;fx(ceNEai$*Y4Q`~!D2hxM zHd-=8M$o-v?B<7w1MljB>DjGj-nk@L76F2+8wg>&8H*a?yI5%Nb|QZB)<2xK!6oGc z`gpoU^OrLA{_N}%2_TB16|mTJlinXO(#fI7&75(s%&jFhA}$L(C&2q5*NP741)E5K zTV%z<2*b0qdzd_^WRZ+))V0-IFQ?@n#r8{7_|ltA+p)ic9*Wpn>8nuig48o+iH+2s z{q^kj9JOn=T-?m}yB2+=gZ=eZuy06%P>@Wa1bwfXBk+Lu32fznH;SsnIkhl%2E*sxG5?a?nm; zY#YVaD&6aZ5oRs<{*UA_1Wu)bj*C&dJ0t3~W6wtrmFQroB#nb);No2+)(mNFjE4uo z1ImY4-Me{n#p4E~l5@m(KaJvPR4Aikg)8du+awQL$Ra73I-LeT0?DN&qf+Li7)MkV zoPhRX%vfAj%FpgsvLF8)`c6Pwb*?8wvcc>Dv(+pFGu)KH zhe!`v+iLO{>42C7cl>_-dq@{2+<#n}gNzq%V3M2ii_E+O+~;?20pOe)KK?;{b1M)d5U#=o`cj#SZq^9kNZXB*w)-UjXraR7rZU~wa6yoR(3L| z;A;l<^aN-FV4LS1g(r*ORM4B696yT%$HuK`mTf;uoI;>gl6$LW|JYzS0$zKx@Y~9P zqPCMGwWACMG_e2Ix9N-=kXa|bVon^>_&QNfi_c?|eW?wV&B;bLx9+ITlEY`@fPH5T zp9Su=GA2(2v*Pl3=Tcf&>ED&s5UvW@U(#BhHOlFS6kb8cM;p5wr|LbVpT{K%g~0wVVf~9tr66*E#!UI3kPZ$&s%eyEnGQC zt4Z9H2wYZ9F=ctad2F9k=WG%kj(R6-_1wf(e??EtqPsl>}?(maXr(WAY&84WNGZ99W1K)ZpvLhapBco4mfvgae3g{m~+I_ z`g1ilr8tgm_gU~?*3`Kg^XW^?XPkXKRCXYV{*yLl_8#`-@a8D0TXO`D>|^+vpR9Jz zh?UxY-1(L?U^(1^ozHSg>yjD{_Dj*CT1i|8f%t)r?~m)o5d8Vu@WKIS$u)LHl47@j zwY=v;{SJ4Mnj)xBFgc!}W6mrDP#bLwjS7u%(UvW9VS3XV*Eg|EV!K^kmcD;wt(O#m zw~1go#7~BgO%_fOlDrlQSxpEF*xK(rwde8}2U|toel-0(h`qkh`B2KZw7vTdBAA3%DHvUbVT;_<_(?U)ZN?o@Vi@w)-Fr4P_;KT{#xL`H0#eNw0q5u zid2!p62Lv3>`$K_BL@^centwsQ(62r4&Xp)&dU_?#S=d;_u&yti1q_4OO|f7FgoNG zR%g^GtS-;BkM3=>mbf4XC~4)H#2#G|c=(LD?xQoZQfpj*W;mkYUG95FGf(($rqOjyq{g&^%**&iC1OdPws(NyJhI`80(oTjJ2;9 z#;C)s=01%>CD`>}xd8zVIWUbWsO~(I8P4QQDtQ9n*hnJ0SvuJ@Dy3?U&iFcD#rKTe?m#wvN_MJM^mrRuqhdS{&%D5X=8N}%O>Ji zQ=%D@*-vYkToc^X9^R@u3rZ``btW_K^s~PjC?Chz{wPMANFG$dWTu6A354~ePy%~QcM|HGqkt)VZ!nx zf#@nPwxXzcc}#Mt$+5av0pQoALj&jR$+ZxAGmhzD;LzE#8`qQ;CDV753RItY)8yzW zPWXwj`*DSj^B9eiQ4PaUq+5L6_p$2&N*2(CI-Td2kMym%lJ;N<>MUHM-Z2+xN?OVV zJ!8cT=1R5Tm3UeGYw9BR-Z&@!~7@`W3@gO-{QR2zyVPB9OGq zLUzv{W?8U%lo9_%$fBgOC}RkUxN9wf8t4ot70DHmy+=zWXxYR84iD9~Z#xMhfEc4= z?tZ~M3TWLnoMmGL3A7!ES1^~X(W1_R#1Kr(`$eSm-f*Xo_4TsATG{4`(Vh8Y>vyl8 z-N%w)@pCJacMh~^&mlO>g<8r*gi_zHWr}~+@G-~Nq*VID;kjmw$d@z_u%yITG_ySc zE&)OKhb2;SGvMYCWYSk_o+d+*i@>H7-9d3#gjA#``hoA6BredcWw{01d@uF|rF=xg zUlbviiqSVyKKfaFxI2P~hvJo0rkS58F5W)taORWX!lIk05kg=k^u-gw>|6aiPZvT3 zCt3t`H1?#9GUx2&$#W#0Hdfh**q0ZN$l7ty6?E&Q$t}eQfLu=u-Z^}(7335w>LaTr zWuJ4UtElSMi6v0KJ|)Fr&Y!^B3^W3I1`;v{BOWJI5fu23wG!*K$`u?81{h8}$Ov(y z`|v*cJK7fn5!6(Bnqo~_-d{(3#&Qr0Z=+U)%i2YLCkw7GWUs`goe{ET+If5<(nIx}Qb<$l)tBe_76smm=`qpXEf@$8u^sM;Z zI$3+OT0Pms6~}LT*RT*L(P59@t)Uskg_qLhJ6*Hn$}vMbvfU;bK2n%SRMu3yOi>g#q` z_!_wZbyWntE@a9*L_8X-P4ay_-C;XX3gqf)WmK+2TdBX|241sTu}iNrVJGj{QTTuz zj=tR*q#9iI+ac*90fBpp{rwPX{$4gk3GKTII9J41+!f~i8gU&(b*Oa1UQom3NeI>v z9bz~ftcF?KtT~ys#MYsd>PCxw=7wV|eb#E|3|rO=SW4`j%!Z;N;ISU3i*F9@C_I6A zoa<-gk+Yl0#@9CpwOf~;5ru(!%Auzi9r!*weZ$=^Quapj^AALUA*^LYLaeQ>`CkW` zMAcZ_V3~khAXdURX~TG}+lta{RdTFM`c)*P z*b?PTR6ic1UE%xpA!FGV>iHn4ZA+rOfeuW*BO#qV}5S&e2ux)%;5nir8&vRw^0kGVl>L6 zu_#jE+pa}ucIk+vX&?WG^;52O$+w((lVZ94ChaS#bViC%wZa!2JN=a!YNZJyGzHc7 zeWtb~>Yi3}VZ4ReO{>50w%QFflem>cdP0ZghlVPV#4Y^DoRuOv`elK;DdlZ_BR+y_ z=2tX@P5B)z_*;;qfWIcf|Cyb?;{%81j<*DGA1Wa!4wV;|xNj;U`v3}k0F@Dk{`UZP iLU#SH2A2-5JOWd19K}wV_hI=U~qfh*M`aO1SD0kNv=BVYr_Bpp00i_ I>zopr04I|b9RL6T delta 73 zcmZ3jv|4GyH310&OI<^w5JPh-10yR#3ta;s!{98Nvd!do0+K4&B=b%7 diff --git a/src/test/html/rust-225.png b/src/test/html/rust-225.png index 03252755fc69fbb74bdb639538222853c70f05d7..b0a671ea3b1891cedf30c62201bf2be682d961d2 100644 GIT binary patch literal 10808 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 lJT^{{vMCzsvvt literal 10889 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 E0CEtbqyPW_ diff --git a/src/test/html/rust-270.png b/src/test/html/rust-270.png index 2fc953bb363702824b4f8ee219128df86481ec8f..0c7d1d7f002d093301eb5ceece20a3cba25ece60 100644 GIT binary patch delta 93 zcmbQJJW+XrjbNyufv$mZh@q*Kfw`5Tu`ZA_Ft|PMYXbuVgKCLuL`h0wNvc(DeoAIq cC4-RxPz5#(s}B3x05vdpy85}Sb4q9e0M5@Dz5oCK delta 93 zcmbQJJW+XrjbNyOrLLh-h@rWafsvJ=rLF;xVUS&@(ZRsLpjzS@QIe8al4_NkpOTqY c$zWt)XsnA(gOtt$6QBkLPgg&ebxsLQ0BqD3h5!Hn diff --git a/src/test/html/rust-315.png b/src/test/html/rust-315.png index 4c41fad4c0c4f3c3bf11b004deab649e7d1604f4..5d15acd0cfbccd037934d3b8627994de3fcd99ee 100644 GIT binary patch literal 10574 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 literal 10753 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 R?Y#m(LsdtmM#(DTe*lWSPSF4W diff --git a/src/test/html/rust-45.png b/src/test/html/rust-45.png index d1bc4bbc05dd3328c57728fc77883d91b60e10fe..b7dd1fd9c5a3edbc8e96dbbbada6dca9fd379ef7 100644 GIT binary patch literal 10807 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 XSmFfxT*nb`Pb`&3nhK?I7NP$Sv2GX^ literal 10873 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 V8Mx)Z5%&xLEp>ghDivtV{{cYkhcy5I diff --git a/src/test/html/rust-90.png b/src/test/html/rust-90.png index 0c956fce8473e6d23d7b639a3bc8f795c6630014..25fd8a662513d77865d3be12120431277dc3e19f 100644 GIT binary patch delta 93 zcmeyQ^hs%htYE02fv$mZh@q*Kfw`5Tu`ZA_Ft|PMYXbuVgKCLuL`h0wNvc(DeoAIq cC4-RxPz5#(s}B3x05vdpy85}Sb4q9e00}r6UjP6A delta 93 zcmeyQ^hs%htYD~trLLh-h@rWafsvJ=rLF;xVUS&@(ZRsLpjzS@QIe8al4_NkpOTqY c$zWt)XsnA(gOtt$6QBkLPgg&ebxsLQ0H-b(CjbBd 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); -

Time required to multiply two 900x900 matrices:

+

Time required to multiply two 600x600 matrices: