diff --git a/.gitmodules b/.gitmodules index 38368bc25bf..c08e1f13a16 100644 --- a/.gitmodules +++ b/.gitmodules @@ -88,3 +88,9 @@ [submodule "src/support/alert/rust-alert"] path = src/support/alert/rust-alert url = git://github.com/mozilla-servo/rust-alert.git +[submodule "src/support/nss/nss"] + path = src/support/nss/nss + url = git://github.com/mozilla-servo/nss.git +[submodule "src/support/nss/nspr"] + path = src/support/nss/nspr + url = git://github.com/mozilla-servo/nspr.git diff --git a/configure b/configure index e6dddb7ea3a..8d39cb8788a 100755 --- a/configure +++ b/configure @@ -411,6 +411,8 @@ CFG_SUBMODULES="\ support/libparserutils/libparserutils \ support/netsurfcss/libcss \ support/netsurfcss/rust-netsurfcss \ + support/nss/nspr \ + support/nss/nss \ support/opengles/rust-opengles \ support/sharegl/sharegl \ support/skia/skia \ @@ -501,14 +503,17 @@ do # needed because Azure's configure wants "--enable-skia" CONFIGURE_ARGS="" ENV_VARS="" - if [ $i = "rust-azure" ]; then + if [ $i = "support/azure/rust-azure" ]; then CONFIGURE_ARGS="--enable-skia" fi - if [ $i = mozjs ]; then - if [ ! -z $CFG_ENABLE_DEBUG ]; then - CONFIGURE_ARGS="--enable-debug" - fi + if [ $i = "support/nss/nspr" ]; then + CONFIGURE_ARGS="--enable-64bit" + fi + if [ $i = "support/spidermonkey/mozjs" ]; then + if [ ! -z $CFG_ENABLE_DEBUG ]; then + CONFIGURE_ARGS="--enable-debug" fi + fi if [ -f ${CONFIGURE_SCRIPT} ] then diff --git a/mk/check.mk b/mk/check.mk index 42adc18516d..af8481aaa84 100644 --- a/mk/check.mk +++ b/mk/check.mk @@ -26,8 +26,10 @@ reftest: $(S)src/test/harness/reftest/reftest.rs servo contenttest: $(S)src/test/harness/contenttest/contenttest.rs servo $(RUSTC) $(RFLAGS_servo) -o $@ $< -L . -DEPS_CHECK_TARGETS_ALL = $(addprefix check-,$(DEPS_CHECK_ALL)) -DEPS_CHECK_TARGETS_FAST = $(addprefix check-,$(filter-out $(SLOW_TESTS),$(DEPS_CHECK_ALL))) + +DEPS_CHECK_TESTABLE = $(filter-out $(NO_TESTS),$(DEPS_CHECK_ALL)) +DEPS_CHECK_TARGETS_ALL = $(addprefix check-,$(DEPS_CHECK_TESTABLE)) +DEPS_CHECK_TARGETS_FAST = $(addprefix check-,$(filter-out $(SLOW_TESTS),$(DEPS_CHECK_TESTABLE))) .PHONY: check $(DEPS_CHECK_TARGETS_ALL) diff --git a/mk/sub.mk b/mk/sub.mk index e8bf4d7c67a..745b5b71179 100644 --- a/mk/sub.mk +++ b/mk/sub.mk @@ -3,6 +3,12 @@ SLOW_TESTS += \ mozjs \ $(NULL) +# Tests for these submodules do not exist. +NO_TESTS += \ + nspr \ + nss \ + $(NULL) + # These submodules will not be cleaned by the `make clean-fast` target. SLOW_BUILDS += \ libcss \ @@ -21,6 +27,8 @@ NATIVE_BUILDS += \ libwapcaplet \ mozjs \ skia \ + nss \ + nspr \ $(NULL) # NOTE: the make magic can only compute transitive build dependencies, @@ -40,6 +48,7 @@ DEPS_rust-glut += \ DEPS_rust-layers += \ rust-azure \ + rust-cocoa \ rust-geom \ rust-glut \ rust-opengles \ @@ -128,6 +137,10 @@ DEPS_rust-layers += \ rust-core-text \ $(NULL) +DEPS_rust-glut += \ + rust-cocoa \ + $(NULL) + endif ifeq ($(CFG_OSTYPE),unknown-linux-gnu) diff --git a/src/components/gfx/compositor.rs b/src/components/gfx/compositor.rs index 83f8bb597a0..742894f739a 100644 --- a/src/components/gfx/compositor.rs +++ b/src/components/gfx/compositor.rs @@ -4,6 +4,7 @@ use azure::azure_hl::{DrawTarget}; use geom::rect::Rect; +use geom::size::Size2D; pub struct LayerBuffer { draw_target: DrawTarget, @@ -24,6 +25,6 @@ pub struct LayerBufferSet { /// The interface used to by the renderer to acquire draw targets for each rendered frame and /// submit them to be drawn to the display. pub trait Compositor { - fn paint(&self, layer_buffer_set: LayerBufferSet); + fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D); } diff --git a/src/components/gfx/display_list.rs b/src/components/gfx/display_list.rs index 6ceeef76a9c..8a7d5135242 100644 --- a/src/components/gfx/display_list.rs +++ b/src/components/gfx/display_list.rs @@ -19,6 +19,7 @@ use geometry::Au; use render_context::RenderContext; use text::SendableTextRun; +use core::cast::transmute_region; use geom::{Point2D, Rect, Size2D}; use servo_net::image::base::Image; use servo_util::range::Range; @@ -26,20 +27,20 @@ use std::arc::ARC; use std::arc; /// A list of rendering operations to be performed. -pub struct DisplayList { - priv list: ~[DisplayItem] +pub struct DisplayList { + list: ~[DisplayItem] } -impl DisplayList { +impl DisplayList { /// Creates a new display list. - pub fn new() -> DisplayList { + pub fn new() -> DisplayList { DisplayList { list: ~[] } } /// Appends the given item to the display list. - pub fn append_item(&mut self, item: DisplayItem) { + pub fn append_item(&mut self, item: DisplayItem) { // FIXME(Issue #150): crashes //debug!("Adding display item %u: %?", self.len(), item); self.list.push(item) @@ -58,51 +59,54 @@ impl DisplayList { } /// One drawing command in the list. -pub enum DisplayItem { - SolidColorDisplayItemClass(~SolidColorDisplayItem), - TextDisplayItemClass(~TextDisplayItem), - ImageDisplayItemClass(~ImageDisplayItem), - BorderDisplayItemClass(~BorderDisplayItem), +pub enum DisplayItem { + SolidColorDisplayItemClass(~SolidColorDisplayItem), + TextDisplayItemClass(~TextDisplayItem), + ImageDisplayItemClass(~ImageDisplayItem), + BorderDisplayItemClass(~BorderDisplayItem), } /// Information common to all display items. -pub struct BaseDisplayItem { +pub struct BaseDisplayItem { /// The boundaries of the display item. /// /// TODO: Which coordinate system should this use? bounds: Rect, + + /// Extra data: either the originating flow (for hit testing) or nothing (for rendering). + extra: E, } /// Renders a solid color. -pub struct SolidColorDisplayItem { - base: BaseDisplayItem, +pub struct SolidColorDisplayItem { + base: BaseDisplayItem, color: Color, } /// Renders text. -pub struct TextDisplayItem { - base: BaseDisplayItem, +pub struct TextDisplayItem { + base: BaseDisplayItem, text_run: ~SendableTextRun, range: Range, color: Color, } /// Renders an image. -pub struct ImageDisplayItem { - base: BaseDisplayItem, +pub struct ImageDisplayItem { + base: BaseDisplayItem, image: ARC<~Image>, } /// Renders a border. -pub struct BorderDisplayItem { - base: BaseDisplayItem, +pub struct BorderDisplayItem { + base: BaseDisplayItem, /// The width of the border. width: Au, /// The color of the border. color: Color, } -impl DisplayItem { +impl DisplayItem { /// Renders this display item into the given render context. fn draw_into_context(&self, render_context: &RenderContext) { match *self { @@ -148,5 +152,21 @@ impl DisplayItem { } } } + + fn base<'a>(&'a self) -> &'a BaseDisplayItem { + // FIXME(tkuehn): Workaround for Rust region bug. + unsafe { + match *self { + SolidColorDisplayItemClass(ref solid_color) => transmute_region(&solid_color.base), + TextDisplayItemClass(ref text) => transmute_region(&text.base), + ImageDisplayItemClass(ref image_item) => transmute_region(&image_item.base), + BorderDisplayItemClass(ref border) => transmute_region(&border.base) + } + } + } + + fn bounds(&self) -> Rect { + self.base().bounds + } } diff --git a/src/components/gfx/geometry.rs b/src/components/gfx/geometry.rs index d3b1f019df9..01cee75193a 100644 --- a/src/components/gfx/geometry.rs +++ b/src/components/gfx/geometry.rs @@ -6,7 +6,7 @@ use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; -use core::num::NumCast; +use core::num::{NumCast, One, Zero}; pub struct Au(i32); @@ -46,6 +46,17 @@ impl cmp::Eq for Au { fn ne(&self, other: &Au) -> bool { **self != **other } } +impl One for Au { + fn one() -> Au { Au(1) } +} + +impl Zero for Au { + fn zero() -> Au { Au(0) } + fn is_zero(&self) -> bool { **self == 0 } +} + +impl Num for Au {} + pub fn min(x: Au, y: Au) -> Au { if x < y { x } else { y } } pub fn max(x: Au, y: Au) -> Au { if x > y { x } else { y } } diff --git a/src/components/gfx/render_layers.rs b/src/components/gfx/render_layers.rs index 49ee76130a5..a2cab09e2c1 100644 --- a/src/components/gfx/render_layers.rs +++ b/src/components/gfx/render_layers.rs @@ -14,8 +14,12 @@ use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; +/// The type representing the lack of extra display list data. This is used when sending display +/// list data off to be rendered. +pub type Nothing = (); + pub struct RenderLayer { - display_list: DisplayList, + display_list: DisplayList, size: Size2D } @@ -50,8 +54,6 @@ pub fn render_layers(layer_ref: *RenderLayer, let width = right - x; let height = bottom - y; - let tile_rect = Rect(Point2D(x, y), Size2D(width, height)); - // Round the width up the nearest 32 pixels for DMA on the Mac. let aligned_width = if width % 32 == 0 { width @@ -63,6 +65,8 @@ pub fn render_layers(layer_ref: *RenderLayer, debug!("tile aligned_width %u", aligned_width); + let tile_rect = Rect(Point2D(x, y), Size2D(aligned_width, height)); + let buffer; // FIXME: Try harder to search for a matching tile. // FIXME: Don't use shift; it's bad for perf. Maybe reverse and pop. @@ -75,7 +79,7 @@ pub fn render_layers(layer_ref: *RenderLayer, let size = Size2D(aligned_width as i32, height as i32); // FIXME: This may not be always true. - let stride = size.width * 4; + let stride = (aligned_width as i32) * 4; let mut data: ~[u8] = ~[0]; let offset; diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 4b55382305c..2beabee6641 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -162,7 +162,7 @@ impl Renderer { }; debug!("renderer: returning surface"); - self.compositor.paint(layer_buffer_set); + self.compositor.paint(layer_buffer_set, render_layer.size); } } } diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 1469acbb57c..c0b08be2f15 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -4,12 +4,14 @@ use compositing::resize_rate_limiter::ResizeRateLimiter; use platform::{Application, Window}; -use script::script_task::{LoadMsg, ScriptMsg}; +use script::script_task::{LoadMsg, ScriptMsg, SendEventMsg}; use windowing::{ApplicationMethods, WindowMethods}; +use script::dom::event::ClickEvent; use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods}; use core::cell::Cell; use core::comm::{Chan, SharedChan, Port}; +use core::num::Orderable; use core::util; use geom::matrix::identity; use geom::point::Point2D; @@ -59,8 +61,10 @@ impl CompositorTask { /// Messages to the compositor. pub enum Msg { - Paint(LayerBufferSet), - Exit + /// Requests that the compositor paint the given layer buffer set for the given page size. + Paint(LayerBufferSet, Size2D), + /// Requests that the compositor shut down. + Exit, } /// Azure surface wrapping to work with the layers infrastructure. @@ -119,6 +123,8 @@ fn run_main_loop(port: Port, // applied to the layers themselves on a per-layer basis. However, this won't work until scroll // positions are sent to content. let world_offset = @mut Point2D(0f32, 0f32); + let page_size = @mut Size2D(0f32, 0f32); + let window_size = @mut Size2D(800, 600); let check_for_messages: @fn() = || { // Periodically check if the script task responded to our last resize event @@ -129,15 +135,20 @@ fn run_main_loop(port: Port, match port.recv() { Exit => *done = true, - Paint(new_layer_buffer_set) => { + Paint(new_layer_buffer_set, new_size) => { debug!("osmain: received new frame"); + + *page_size = Size2D(new_size.width as f32, new_size.height as f32); + let mut new_layer_buffer_set = new_layer_buffer_set; // Iterate over the children of the container layer. let mut current_layer_child = root_layer.first_child; - // Replace the image layer data with the buffer data. + // Replace the image layer data with the buffer data. Also compute the page + // size here. let buffers = util::replace(&mut new_layer_buffer_set.buffers, ~[]); + for buffers.each |buffer| { let width = buffer.rect.size.width as uint; let height = buffer.rect.size.height as uint; @@ -172,9 +183,10 @@ fn run_main_loop(port: Port, Some(_) => fail!(~"found unexpected layer kind"), }; + let origin = buffer.rect.origin; + let origin = Point2D(origin.x as f32, origin.y as f32); + // Set the layer's transform. - let origin = Point2D(buffer.rect.origin.x as f32, - buffer.rect.origin.y as f32); let transform = original_layer_transform.translate(origin.x, origin.y, 0.0); @@ -184,6 +196,8 @@ fn run_main_loop(port: Port, // TODO: Recycle the old buffers; send them back to the renderer to reuse if // it wishes. + + window.set_needs_display() } } } @@ -205,24 +219,45 @@ fn run_main_loop(port: Port, // Hook the windowing system's resize callback up to the resize rate limiter. do window.set_resize_callback |width, height| { debug!("osmain: window resized to %ux%u", width, height); - resize_rate_limiter.window_resized(width, height); + *window_size = Size2D(width, height); + resize_rate_limiter.window_resized(width, height) } + let script_chan_clone = script_chan.clone(); + // When the user enters a new URL, load it. do window.set_load_url_callback |url_string| { debug!("osmain: loading URL `%s`", url_string); - script_chan.send(LoadMsg(url::make_url(url_string.to_str(), None))) + script_chan_clone.send(LoadMsg(url::make_url(url_string.to_str(), None))) + } + + let script_chan_clone = script_chan.clone(); + + // When the user clicks, perform hit testing + do window.set_click_callback |layer_click_point| { + let world_click_point = layer_click_point + *world_offset; + debug!("osmain: clicked at %?", world_click_point); + + script_chan_clone.send(SendEventMsg(ClickEvent(world_click_point))); } // When the user scrolls, move the layer around. do window.set_scroll_callback |delta| { - // FIXME (Rust #2528): Can't use `+=`. + // FIXME (Rust #2528): Can't use `-=`. let world_offset_copy = *world_offset; - *world_offset = world_offset_copy + delta; + *world_offset = world_offset_copy - delta; + + // Clamp the world offset to the screen size. + let max_x = (page_size.width - window_size.width as f32).max(&0.0); + world_offset.x = world_offset.x.clamp(&0.0, &max_x); + let max_y = (page_size.height - window_size.height as f32).max(&0.0); + world_offset.y = world_offset.y.clamp(&0.0, &max_y); debug!("compositor: scrolled to %?", *world_offset); - root_layer.common.set_transform(identity().translate(world_offset.x, world_offset.y, 0.0)); + root_layer.common.set_transform(identity().translate(-world_offset.x, + -world_offset.y, + 0.0)); window.set_needs_display() } @@ -241,8 +276,8 @@ fn run_main_loop(port: Port, /// Implementation of the abstract `Compositor` interface. impl Compositor for CompositorTask { - fn paint(&self, layer_buffer_set: LayerBufferSet) { - self.chan.send(Paint(layer_buffer_set)) + fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D) { + self.chan.send(Paint(layer_buffer_set, new_size)) } } diff --git a/src/components/main/css/select.rs b/src/components/main/css/select.rs index 71f5327cedb..46c62de20ea 100644 --- a/src/components/main/css/select.rs +++ b/src/components/main/css/select.rs @@ -121,6 +121,9 @@ h1 { page-break-before: always } h4, h5, h6 { page-break-after: avoid } ul, ol, dl { page-break-before: avoid } } + +/* Servo additions */ +:link { color: blue } " } diff --git a/src/components/main/css/select_handler.rs b/src/components/main/css/select_handler.rs index fb232460b90..80affef9d34 100644 --- a/src/components/main/css/select_handler.rs +++ b/src/components/main/css/select_handler.rs @@ -76,6 +76,16 @@ impl SelectHandler> for NodeSelectHandler { self.parent_node(node).is_none() } + fn node_is_link(&self, node: &AbstractNode) -> bool { + if node.is_element() { + do node.with_imm_element |element| { + "a" == element.tag_name + } + } else { + false + } + } + fn with_node_id(&self, node: &AbstractNode, f: &fn(Option<&str>) -> R) -> R { if !node.is_element() { fail!(~"attempting to style non-element node"); diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index b5915dd801e..fc90d3273d2 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -6,16 +6,18 @@ use layout::box::{RenderBox}; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, FlowDisplayListBuilderMethods}; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; +use layout::display_list_builder::{FlowDisplayListBuilderMethods}; use layout::flow::{BlockFlow, FlowContext, FlowData, InlineBlockFlow}; use layout::inline::InlineLayout; +use layout::model::{MaybeAuto, Specified, Auto}; -use au = gfx::geometry; use core::cell::Cell; use geom::point::Point2D; use geom::rect::Rect; use gfx::display_list::DisplayList; use gfx::geometry::Au; +use gfx::geometry; use servo_util::tree::{TreeNodeRef, TreeUtils}; pub struct BlockFlowData { @@ -95,14 +97,19 @@ impl BlockFlowData { assert!(child_ctx.starts_block_flow() || child_ctx.starts_inline_flow()); do child_ctx.with_base |child_node| { - min_width = au::max(min_width, child_node.min_width); - pref_width = au::max(pref_width, child_node.pref_width); + min_width = geometry::max(min_width, child_node.min_width); + pref_width = geometry::max(pref_width, child_node.pref_width); } } /* if not an anonymous block context, add in block box's widths. these widths will not include child elements, just padding etc. */ self.box.map(|&box| { + //Can compute border width here since it doesn't depend on anything + let style = box.style(); + do box.with_model |model| { + model.compute_borders(style) + } min_width = min_width.add(&box.get_min_width(ctx)); pref_width = pref_width.add(&box.get_pref_width(ctx)); }); @@ -111,6 +118,57 @@ impl BlockFlowData { self.common.pref_width = pref_width; } + /// Computes left and right margins and width based on CSS 2.1 secion 10.3.3. + /// Requires borders and padding to already be computed + priv 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.spec_or_default(Au(0)); + let right = right_margin.spec_or_default(Au(0)); + + if((left + right + width) > available_width) { + (Specified(left), Specified(right)) + } else { + (left_margin, right_margin) + } + } + }; + + //Invariant: left_margin_Au + width_Au + right_margin_Au == available_width + let (left_margin_Au, width_Au, right_margin_Au) = match (left_margin, width, right_margin) { + //If all have a computed value other than 'auto', the system is over-constrained and we need to discard a margin. + //if direction is ltr, ignore the specified right margin and solve for it. If it is rtl, ignore the specified + //left margin. FIXME(eatkinson): this assumes the direction is ltr + (Specified(margin_l), Specified(width), Specified(_margin_r)) => (margin_l, width, available_width - (margin_l + width )), + + //If exactly one value is 'auto', solve for it + (Auto, Specified(width), Specified(margin_r)) => (available_width - (width + margin_r), width, margin_r), + (Specified(margin_l), Auto, Specified(margin_r)) => (margin_l, available_width - (margin_l + margin_r), margin_r), + (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)), + + //If left and right margins are auto, they become equal + (Auto, Specified(width), Auto) => { + let margin = (available_width - width).scale_by(0.5); + (margin, width, margin) + } + + }; + //return values in same order as params + (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. /// @@ -124,24 +182,60 @@ impl BlockFlowData { self.common.position.size.width = ctx.screen_size.size.width; } + //position was set to the containing block by the flow's parent let mut remaining_width = self.common.position.size.width; - let left_used = Au(0); + let mut x_offset = Au(0); - // Let the box consume some width. It will return the amount remaining for its children. - self.box.map(|&box| { - do box.with_mut_base |base| { - base.position.size.width = remaining_width; + for self.box.each |&box| { + 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 (left_used, right_used) = box.get_used_width(); - remaining_width -= left_used.add(&right_used); + // 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_margin(style.margin_top()); + let margin_top = margin_top.spec_or_default(Au(0)); + let margin_bottom = MaybeAuto::from_margin(style.margin_bottom()); + let margin_bottom = margin_bottom.spec_or_default(Au(0)); + + let (width, margin_left, margin_right) = + (MaybeAuto::from_width(style.width()), + MaybeAuto::from_margin(style.margin_left()), + MaybeAuto::from_margin(style.margin_right())); + + // FIXME(pcwalton): We discard the width here. Is that correct? + let (_, 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 = remaining_width - model.noncontent_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; + } + } for BlockFlow(self).each_child |kid| { assert!(kid.starts_block_flow() || kid.starts_inline_flow()); do kid.with_mut_base |child_node| { - child_node.position.origin.x = left_used; + child_node.position.origin.x = x_offset; child_node.position.size.width = remaining_width; } } @@ -150,6 +244,12 @@ impl BlockFlowData { pub fn assign_height_block(@mut self, ctx: &LayoutContext) { let mut cur_y = Au(0); + for self.box.each |&box| { + do box.with_model |model| { + cur_y += model.margin.top + model.border.top + model.padding.top; + } + } + for BlockFlow(self).each_child |kid| { do kid.with_mut_base |child_node| { child_node.position.origin.y = cur_y; @@ -157,28 +257,32 @@ impl BlockFlowData { } } - let height = if self.is_root { Au::max(ctx.screen_size.size.height, cur_y) } - else { cur_y }; + let height = if self.is_root { + Au::max(ctx.screen_size.size.height, cur_y) + } else { + cur_y + }; + //TODO(eatkinson): compute heights using the 'height' property. self.common.position.size.height = height; - let _used_top = Au(0); - let _used_bot = Au(0); - self.box.map(|&box| { do box.with_mut_base |base| { - base.position.origin.y = Au(0); - base.position.size.height = height; - let (_used_top, _used_bot) = box.get_used_height(); + //The associated box is the border box of this flow + base.position.origin.y = base.model.margin.top; + + let pb = base.model.padding.top + base.model.padding.bottom + + base.model.border.top + base.model.border.bottom; + base.position.size.height = height + pb; } }); } - pub fn build_display_list_block(@mut self, - builder: &DisplayListBuilder, - dirty: &Rect, - offset: &Point2D, - list: &Cell) { + pub fn build_display_list_block(@mut self, + builder: &DisplayListBuilder, + dirty: &Rect, + offset: &Point2D, + list: &Cell>) { // add box that starts block context self.box.map(|&box| { box.build_display_list(builder, dirty, offset, list) @@ -194,3 +298,4 @@ impl BlockFlowData { } } } + diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index 9012fe16438..78934a937b4 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -6,13 +6,15 @@ use css::node_style::StyledNode; use layout::context::LayoutContext; -use layout::display_list_builder::{DisplayListBuilder, ToGfxColor}; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor}; use layout::flow::FlowContext; +use layout::model::BoxModel; use layout::text; use core::cell::Cell; use core::cmp::ApproxEq; use core::managed; +use core::num::Zero; use geom::{Point2D, Rect, Size2D}; use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass}; use gfx::display_list::{DisplayList, ImageDisplayItem, ImageDisplayItemClass}; @@ -24,7 +26,6 @@ use gfx::text::text_run::TextRun; use newcss::color::rgb; use newcss::complete::CompleteStyle; use newcss::units::{Cursive, Em, Fantasy, Monospace, Pt, Px, SansSerif, Serif}; -use newcss::values::{CSSBorderWidthLength, CSSBorderWidthMedium}; use newcss::values::{CSSFontFamilyFamilyName, CSSFontFamilyGenericFamily}; use newcss::values::{CSSFontSizeLength, CSSFontStyleItalic, CSSFontStyleNormal}; use newcss::values::{CSSFontStyleOblique, CSSTextAlign, CSSTextDecoration}; @@ -154,6 +155,9 @@ pub struct RenderBoxBase { /// The position of this box relative to its owning flow. position: Rect, + /// The core parameters (border, padding, margin) used by the box model. + model: BoxModel, + /// A debug ID. /// /// TODO(#87) Make this only present in debug builds. @@ -168,6 +172,7 @@ impl RenderBoxBase { node: node, ctx: flow_context, position: Au::zero_rect(), + model: Zero::zero(), id: id, } } @@ -176,7 +181,7 @@ impl RenderBoxBase { pub impl RenderBox { /// Borrows this render box immutably in order to work with its common data. #[inline(always)] - fn with_imm_base(&self, callback: &fn(&RenderBoxBase) -> R) -> R { + fn with_base(&self, callback: &fn(&RenderBoxBase) -> R) -> R { match *self { GenericRenderBoxClass(generic_box) => callback(generic_box), ImageRenderBoxClass(image_box) => { @@ -210,7 +215,7 @@ pub impl RenderBox { /// A convenience function to return the position of this box. fn position(&self) -> Rect { - do self.with_imm_base |base| { + do self.with_base |base| { base.position } } @@ -367,6 +372,8 @@ pub impl RenderBox { /// Returns the *minimum width* of this render box as defined by the CSS specification. 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. 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 @@ -441,37 +448,36 @@ pub impl RenderBox { (Au(0), Au(0)) } + fn compute_padding(&self, cb_width: Au) { + do self.with_mut_base |base| { + base.model.compute_padding(base.node.style(), cb_width); + } + } + + 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 + } + } + + fn with_model(&self, callback: &fn(&mut BoxModel) -> R) -> R { + do self.with_mut_base |base| { + callback(&mut base.model) + } + } + /// The box formed by the content edge as defined in CSS 2.1 ยง 8.1. Coordinates are relative to /// the owning flow. fn content_box(&self) -> Rect { - let origin = self.position().origin; - match *self { - ImageRenderBoxClass(image_box) => { - Rect { - origin: origin, - size: image_box.base.position.size, - } - }, - GenericRenderBoxClass(*) => { - self.position() - - // FIXME: The following hits an ICE for whatever reason. - - /* - let origin = self.d().position.origin; - let size = self.d().position.size; - let (offset_left, offset_right) = self.get_used_width(); - let (offset_top, offset_bottom) = self.get_used_height(); - - Rect { - origin: Point2D(origin.x + offset_left, origin.y + offset_top), - size: Size2D(size.width - (offset_left + offset_right), - size.height - (offset_top + offset_bottom)) - } - */ - }, - TextRenderBoxClass(*) => self.position(), - UnscannedTextRenderBoxClass(*) => fail!(~"Shouldn't see unscanned boxes here.") + 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) } } @@ -489,22 +495,15 @@ pub impl RenderBox { self.content_box() } - /// A convenience function to determine whether this render box represents a DOM element. - fn is_element(&self) -> bool { - do self.with_imm_base |base| { - base.node.is_element() - } - } - /// A convenience function to access the computed style of the DOM node that this render box /// represents. fn style(&self) -> CompleteStyle { - self.with_imm_base(|base| base.node.style()) + self.with_base(|base| base.node.style()) } /// A convenience function to access the DOM node that this render box represents. fn node(&self) -> AbstractNode { - self.with_imm_base(|base| base.node) + self.with_base(|base| base.node) } /// Returns the nearest ancestor-or-self `Element` to the DOM node that this render box @@ -512,7 +511,7 @@ pub impl RenderBox { /// /// If there is no ancestor-or-self `Element` node, fails. fn nearest_ancestor_element(&self) -> AbstractNode { - do self.with_imm_base |base| { + do self.with_base |base| { let mut node = base.node; while !node.is_element() { match node.parent_node() { @@ -542,11 +541,11 @@ pub 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. - fn build_display_list(&self, - _: &DisplayListBuilder, - dirty: &Rect, - offset: &Point2D, - list: &Cell) { + fn build_display_list(&self, + _: &DisplayListBuilder, + dirty: &Rect, + offset: &Point2D, + list: &Cell>) { let box_bounds = self.position(); let absolute_box_bounds = box_bounds.translate(offset); debug!("RenderBox::build_display_list at rel=%?, abs=%?: %s", @@ -574,6 +573,7 @@ pub impl RenderBox { let text_display_item = ~TextDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, + extra: ExtraDisplayListData::new(*self), }, // FIXME(pcwalton): Allocation? Why?! text_run: ~text_box.run.serialize(), @@ -594,6 +594,7 @@ pub impl RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, + extra: ExtraDisplayListData::new(*self), }, width: Au::from_px(1), color: rgb(0, 0, 200).to_gfx_color(), @@ -613,6 +614,7 @@ pub impl RenderBox { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { bounds: baseline, + extra: ExtraDisplayListData::new(*self), }, width: Au::from_px(1), color: rgb(0, 200, 0).to_gfx_color(), @@ -630,7 +632,8 @@ pub impl RenderBox { do list.with_mut_ref |list| { let border_display_item = ~BorderDisplayItem { base: BaseDisplayItem { - bounds: absolute_box_bounds, + bounds: absolute_box_bounds, + extra: ExtraDisplayListData::new(*self), }, width: Au::from_px(1), color: rgb(0, 0, 0).to_gfx_color(), @@ -650,6 +653,7 @@ pub impl RenderBox { let image_display_item = ~ImageDisplayItem { base: BaseDisplayItem { bounds: absolute_box_bounds, + extra: ExtraDisplayListData::new(*self), }, image: image.clone(), }; @@ -674,9 +678,9 @@ pub impl RenderBox { /// 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) { + 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 @@ -689,6 +693,7 @@ pub impl RenderBox { let solid_color_display_item = ~SolidColorDisplayItem { base: BaseDisplayItem { bounds: *absolute_bounds, + extra: ExtraDisplayListData::new(*self), }, color: background_color.to_gfx_color(), }; @@ -698,70 +703,6 @@ pub impl RenderBox { } } - /// Adds the display items necessary to paint the borders of this render box to the display - /// list if necessary. - fn paint_borders_if_applicable(&self, list: &Cell, abs_bounds: &Rect) { - if !self.is_element() { - return - } - - let style = self.style(); - let (top_width, right_width) = (style.border_top_width(), style.border_right_width()); - let (bottom_width, left_width) = (style.border_bottom_width(), style.border_left_width()); - match (top_width, right_width, bottom_width, left_width) { - (CSSBorderWidthLength(Px(top)), - CSSBorderWidthLength(Px(right)), - CSSBorderWidthLength(Px(bottom)), - CSSBorderWidthLength(Px(left))) => { - let top_au = Au::from_frac_px(top); - let right_au = Au::from_frac_px(right); - let bottom_au = Au::from_frac_px(bottom); - let left_au = Au::from_frac_px(left); - - // Are all the widths equal? - if [ top_au, right_au, bottom_au ].all(|a| *a == left_au) { - let border_width = top_au; - let bounds = Rect { - origin: Point2D { - x: abs_bounds.origin.x - border_width / Au(2), - y: abs_bounds.origin.y - border_width / Au(2), - }, - size: Size2D { - width: abs_bounds.size.width + border_width, - height: abs_bounds.size.height + border_width - } - }; - - let top_color = self.style().border_top_color(); - let color = top_color.to_gfx_color(); // FIXME - - // Append the border to the display list. - do list.with_mut_ref |list| { - let border_display_item = ~BorderDisplayItem { - base: BaseDisplayItem { - bounds: bounds, - }, - width: border_width, - color: color, - }; - - list.append_item(BorderDisplayItemClass(border_display_item)) - } - } else { - warn!("ignoring unimplemented border widths"); - } - } - (CSSBorderWidthMedium, - CSSBorderWidthMedium, - CSSBorderWidthMedium, - CSSBorderWidthMedium) => { - // FIXME: This seems to be the default for non-root nodes. For now we'll ignore it. - warn!("ignoring medium border widths"); - } - _ => warn!("ignoring unimplemented border widths") - } - } - /// Converts this node's computed style to a font style used for rendering. fn font_style(&self) -> FontStyle { let my_style = self.nearest_ancestor_element().style(); @@ -884,3 +825,4 @@ pub impl RenderBox { } } + diff --git a/src/components/main/layout/box_builder.rs b/src/components/main/layout/box_builder.rs index de0d74d368f..99153d51ee4 100644 --- a/src/components/main/layout/box_builder.rs +++ b/src/components/main/layout/box_builder.rs @@ -183,7 +183,10 @@ impl BoxGenerator { } let mut node_range: Range = Range::new(self.range_stack.pop(), 0); node_range.extend_to(inline.boxes.len()); - assert!(node_range.length() > 0); + + if node_range.length() == 0 { + warn!("node range length is zero?!") + } debug!("BoxGenerator: adding element range=%?", node_range); inline.elems.add_mapping(node, &node_range); @@ -444,47 +447,49 @@ pub impl LayoutTreeBuilder { // FIXME: this will create refcounted cycles between the removed flow and any // of its RenderBox or FlowContext children, and possibly keep alive other junk - let parent_flow = *parent_flow; - let (first_child, last_child) = do parent_flow.with_base |parent_node| { - (parent_node.first_child, parent_node.last_child) - }; - // check first/last child for whitespace-ness + let first_child = do parent_flow.with_base |parent_node| { + parent_node.first_child + }; for first_child.each |first_flow| { if first_flow.starts_inline_flow() { // FIXME: workaround for rust#6393 let mut do_remove = false; { - let boxes = &first_flow.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(), - parent_flow.id()); - do_remove = true; - } + let boxes = &first_flow.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(), + parent_flow.id()); + do_remove = true; + } } if (do_remove) { - parent_flow.remove_child(*first_flow); + (*parent_flow).remove_child(*first_flow); } } } + + let last_child = do parent_flow.with_base |parent_node| { + parent_node.last_child + }; for last_child.each |last_flow| { if last_flow.starts_inline_flow() { // FIXME: workaround for rust#6393 let mut do_remove = false; { - let boxes = &last_flow.inline().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(), - parent_flow.id()); - do_remove = true; - } + let boxes = &last_flow.inline().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(), + parent_flow.id()); + do_remove = true; + } } if (do_remove) { - parent_flow.remove_child(*last_flow); + (*parent_flow).remove_child(*last_flow); } } } diff --git a/src/components/main/layout/context.rs b/src/components/main/layout/context.rs index 8646b9faabc..13b6f4e8a83 100644 --- a/src/components/main/layout/context.rs +++ b/src/components/main/layout/context.rs @@ -8,12 +8,10 @@ use geom::rect::Rect; use gfx::font_context::FontContext; use gfx::geometry::Au; use servo_net::local_image_cache::LocalImageCache; -use std::net::url::Url; /// Data needed by the layout task. pub struct LayoutContext { font_ctx: @mut FontContext, image_cache: @mut LocalImageCache, - doc_url: Url, screen_size: Rect } diff --git a/src/components/main/layout/display_list_builder.rs b/src/components/main/layout/display_list_builder.rs index 1d0ea27b7e5..02173ef7517 100644 --- a/src/components/main/layout/display_list_builder.rs +++ b/src/components/main/layout/display_list_builder.rs @@ -2,15 +2,13 @@ * 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/. */ -/// -/// Constructs display lists from render boxes. -/// - -use core::cell::Cell; +//! Constructs display lists from render boxes. +use layout::box::RenderBox; use layout::context::LayoutContext; use layout::flow::FlowContext; +use core::cell::Cell; use geom::point::Point2D; use geom::rect::Rect; use gfx::display_list::DisplayList; @@ -19,6 +17,28 @@ use gfx; use newcss; use servo_util::tree::TreeNodeRef; +/// Extra display list data is either nothing (if the display list is to be rendered) or the +/// originating render box (if the display list is generated for hit testing). +pub trait ExtraDisplayListData { + fn new(box: RenderBox) -> Self; +} + +/// The type representing the lack of extra display list data. This is used when sending display +/// list data off to be rendered. +pub type Nothing = (); + +impl ExtraDisplayListData for Nothing { + fn new(_: RenderBox) -> Nothing { + () + } +} + +impl ExtraDisplayListData for RenderBox { + fn new(box: RenderBox) -> RenderBox { + box + } +} + /// A builder object that manages display list builder should mainly hold information about the /// initial request and desired result--for example, whether the `DisplayList` is to be used for /// painting or hit testing. This can affect which boxes are created. @@ -30,30 +50,33 @@ pub struct DisplayListBuilder<'self> { } pub trait FlowDisplayListBuilderMethods { - fn build_display_list(&self, a: &DisplayListBuilder, b: &Rect, c: &Cell); - fn build_display_list_for_child(&self, - a: &DisplayListBuilder, - b: FlowContext, - c: &Rect, - d: &Point2D, - e: &Cell); + fn build_display_list(&self, + a: &DisplayListBuilder, + b: &Rect, + c: &Cell>); + fn build_display_list_for_child(&self, + a: &DisplayListBuilder, + b: FlowContext, + c: &Rect, + d: &Point2D, + e: &Cell>); } impl FlowDisplayListBuilderMethods for FlowContext { - fn build_display_list(&self, - builder: &DisplayListBuilder, - dirty: &Rect, - list: &Cell) { + fn build_display_list(&self, + builder: &DisplayListBuilder, + dirty: &Rect, + list: &Cell>) { let zero = gfx::geometry::zero_point(); self.build_display_list_recurse(builder, dirty, &zero, list); } - fn build_display_list_for_child(&self, - builder: &DisplayListBuilder, - child_flow: FlowContext, - dirty: &Rect, - offset: &Point2D, - list: &Cell) { + fn build_display_list_for_child(&self, + builder: &DisplayListBuilder, + child_flow: FlowContext, + dirty: &Rect, + offset: &Point2D, + list: &Cell>) { // Adjust the dirty rect to child flow context coordinates. do child_flow.with_base |child_node| { let abs_flow_bounds = child_node.position.translate(offset); diff --git a/src/components/main/layout/flow.rs b/src/components/main/layout/flow.rs index b01341608e7..900a7b0a130 100644 --- a/src/components/main/layout/flow.rs +++ b/src/components/main/layout/flow.rs @@ -28,7 +28,7 @@ use layout::block::BlockFlowData; use layout::box::RenderBox; use layout::context::LayoutContext; -use layout::display_list_builder::DisplayListBuilder; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::inline::{InlineFlowData}; use core::cell::Cell; @@ -284,11 +284,11 @@ impl<'self> FlowContext { } } - pub fn build_display_list_recurse(&self, - builder: &DisplayListBuilder, - dirty: &Rect, - offset: &Point2D, - list: &Cell) { + pub fn build_display_list_recurse(&self, + builder: &DisplayListBuilder, + dirty: &Rect, + offset: &Point2D, + list: &Cell>) { do self.with_base |info| { debug!("FlowContext::build_display_list at %?: %s", info.position, self.debug_str()); } diff --git a/src/components/main/layout/inline.rs b/src/components/main/layout/inline.rs index 3db7e23e34e..ffdad2f65e0 100644 --- a/src/components/main/layout/inline.rs +++ b/src/components/main/layout/inline.rs @@ -7,7 +7,7 @@ use core; use layout::box::{CannotSplit, GenericRenderBoxClass, ImageRenderBoxClass, RenderBox}; use layout::box::{SplitDidFit, SplitDidNotFit, TextRenderBoxClass, UnscannedTextRenderBoxClass}; use layout::context::LayoutContext; -use layout::display_list_builder::DisplayListBuilder; +use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData}; use layout::flow::{FlowContext, FlowData, InlineFlow}; use layout::text::{UnscannedMethods, adapt_textbox_with_range}; @@ -141,8 +141,8 @@ impl ElementMapping { // 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_imm_base |old_box_base| { - do new_boxes[new_j].with_imm_base |new_box_base| { + 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 } }; @@ -284,7 +284,7 @@ impl TextRunScanner { let run = @fontgroup.create_textrun(transformed_text, underline); debug!("TextRunScanner: pushing single text box in range: %?", self.clump); - let new_box = do old_box.with_imm_base |old_box_base| { + 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) }; @@ -345,7 +345,7 @@ impl TextRunScanner { loop } - do in_boxes[i].with_imm_base |base| { + do in_boxes[i].with_base |base| { let new_box = @mut adapt_textbox_with_range(*base, run.get(), range); out_boxes.push(TextRenderBoxClass(new_box)); } @@ -864,11 +864,11 @@ impl InlineFlowData { self.common.position.size.height = cur_y; } - pub fn build_display_list_inline(&self, - builder: &DisplayListBuilder, - dirty: &Rect, - offset: &Point2D, - list: &Cell) { + pub fn build_display_list_inline(&self, + builder: &DisplayListBuilder, + dirty: &Rect, + offset: &Point2D, + list: &Cell>) { // 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", diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index 12f55da55df..385014a1172 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -8,15 +8,12 @@ use css::matching::MatchMethods; use css::select::new_css_select_ctx; use layout::aux::{LayoutData, LayoutAuxMethods}; +use layout::box::RenderBox; use layout::box_builder::LayoutTreeBuilder; use layout::context::LayoutContext; use layout::display_list_builder::{DisplayListBuilder, FlowDisplayListBuilderMethods}; use layout::flow::FlowContext; use util::task::spawn_listener; -use servo_util::time; -use servo_util::time::time; -use servo_util::time::profile; -use servo_util::time::ProfilerChan; use core::cast::transmute; use core::cell::Cell; @@ -35,14 +32,19 @@ use newcss::stylesheet::Stylesheet; use newcss::types::OriginAuthor; use script::dom::event::ReflowEvent; use script::dom::node::{AbstractNode, LayoutView}; -use script::layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, ContentBoxQuery}; -use script::layout_interface::{ContentBoxResponse, ContentBoxesQuery, ContentBoxesResponse}; -use script::layout_interface::{ExitMsg, LayoutQuery, LayoutResponse, LayoutTask}; -use script::layout_interface::{MatchSelectorsDamage, Msg, NoDamage, QueryMsg, ReflowDamage}; +use script::layout_interface::{AddStylesheetMsg, ContentBoxQuery}; +use script::layout_interface::{HitTestQuery, ContentBoxResponse, HitTestResponse}; +use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitMsg, LayoutQuery}; +use script::layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDocumentDamage, Msg}; +use script::layout_interface::{QueryMsg, Reflow, ReflowDocumentDamage, ReflowForDisplay}; +use script::layout_interface::{ReflowMsg}; use script::script_task::{ScriptMsg, SendEventMsg}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use servo_net::local_image_cache::LocalImageCache; -use servo_util::tree::TreeUtils; +use servo_util::tree::{TreeNodeRef, TreeUtils}; +use servo_util::time::{ProfilerChan, profile, time}; +use servo_util::time; +use std::net::url::Url; pub fn create_layout_task(render_task: RenderTask, img_cache_task: ImageCacheTask, @@ -69,6 +71,8 @@ struct Layout { local_image_cache: @mut LocalImageCache, from_script: Port, font_ctx: @mut FontContext, + doc_url: Option, + screen_size: Option>, /// This is used to root reader data. layout_refs: ~[@mut LayoutData], @@ -92,6 +96,9 @@ impl Layout { local_image_cache: @mut LocalImageCache(image_cache_task), from_script: from_script, font_ctx: fctx, + doc_url: None, + screen_size: None, + layout_refs: ~[], css_select_ctx: @mut new_css_select_ctx(), profiler_chan: profiler_chan, @@ -104,14 +111,27 @@ impl Layout { } } + // Create a layout context for use in building display lists, hit testing, &c. + fn build_layout_context(&self) -> LayoutContext { + let image_cache = self.local_image_cache; + let font_ctx = self.font_ctx; + let screen_size = self.screen_size.unwrap(); + + LayoutContext { + image_cache: image_cache, + font_ctx: font_ctx, + screen_size: Rect(Point2D(Au(0), Au(0)), screen_size), + } + } + fn handle_request(&mut self) -> bool { match self.from_script.recv() { AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet), - BuildMsg(data) => { + ReflowMsg(data) => { let data = Cell(data); do profile(time::LayoutPerformCategory, self.profiler_chan.clone()) { - self.handle_build(data.take()); + self.handle_reflow(data.take()); } } QueryMsg(query, chan) => { @@ -135,10 +155,10 @@ impl Layout { } /// The high-level routine that performs layout tasks. - fn handle_build(&mut self, data: &BuildData) { + fn handle_reflow(&mut self, data: &Reflow) { // FIXME: Isolate this transmutation into a "bridge" module. let node: &AbstractNode = unsafe { - transmute(&data.node) + transmute(&data.document_root) }; // FIXME: Bad copy! @@ -149,20 +169,16 @@ impl Layout { 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.doc_url = Some(doc_url); let screen_size = Size2D(Au::from_px(data.window_size.width as int), Au::from_px(data.window_size.height as int)); + self.screen_size = Some(screen_size); // Create a layout context for use throughout the following passes. - let mut layout_ctx = LayoutContext { - image_cache: self.local_image_cache, - font_ctx: self.font_ctx, - doc_url: doc_url, - screen_size: Rect(Point2D(Au(0), Au(0)), screen_size) - }; + let mut layout_ctx = self.build_layout_context(); // Initialize layout data for each node. // @@ -172,9 +188,9 @@ impl Layout { } // Perform CSS selector matching if necessary. - match data.damage { - NoDamage | ReflowDamage => {} - MatchSelectorsDamage => { + match data.damage.level { + ReflowDocumentDamage => {} + MatchSelectorsDocumentDamage => { do profile(time::LayoutSelectorMatchCategory, self.profiler_chan.clone()) { node.restyle_subtree(self.css_select_ctx); } @@ -210,25 +226,31 @@ impl Layout { }; } - // Build the display list, and send it to the renderer. - do profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone()) { - let builder = DisplayListBuilder { - ctx: &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(DisplayList::new()); - - // TODO: Set options on the builder before building. - // TODO: Be smarter about what needs painting. - layout_root.build_display_list(&builder, &layout_root.position(), display_list); + let display_list = @Cell(DisplayList::new()); - let render_layer = RenderLayer { - display_list: display_list.take(), - size: Size2D(screen_size.width.to_px() as uint, screen_size.height.to_px() as uint) - }; + // TODO: Set options on the builder before building. + // TODO: Be smarter about what needs painting. + layout_root.build_display_list(&builder, &layout_root.position(), display_list); - self.render_task.channel.send(RenderMsg(render_layer)); - } // time(layout: display list building) + let root_size = do layout_root.with_base |base| { + base.position.size + }; + + let render_layer = RenderLayer { + display_list: display_list.take(), + size: Size2D(root_size.width.to_px() as uint, root_size.height.to_px() as uint) + }; + + self.render_task.channel.send(RenderMsg(render_layer)); + } // time(layout: display list building) + } debug!("%?", layout_root.dump()); @@ -290,6 +312,55 @@ impl Layout { } }; + reply_chan.send(response) + } + HitTestQuery(node, point) => { + // FIXME: Isolate this transmutation into a single "bridge" module. + let node: AbstractNode = unsafe { + transmute(node) + }; + let mut flow_node: AbstractNode = node; + for node.traverse_preorder |node| { + if node.layout_data().flow.is_some() { + flow_node = node; + break; + } + }; + + let response = match flow_node.layout_data().flow { + None => { + debug!("HitTestQuery: flow is None"); + Err(()) + } + Some(flow) => { + let layout_ctx = self.build_layout_context(); + let builder = DisplayListBuilder { + ctx: &layout_ctx, + }; + let display_list: @Cell> = + @Cell(DisplayList::new()); + flow.build_display_list(&builder, + &flow.position(), + display_list); + // iterate in reverse to ensure we have the most recently painted render box + let (x, y) = (Au::from_frac_px(point.x as float), + Au::from_frac_px(point.y as float)); + let mut resp = Err(()); + let display_list = &display_list.take().list; + for display_list.each_reverse |display_item| { + let bounds = display_item.bounds(); + if x <= bounds.origin.x + bounds.size.width && + bounds.origin.x <= x && + y < bounds.origin.y + bounds.size.height && + bounds.origin.y < y { + resp = Ok(HitTestResponse(display_item.base().extra.node())); + break; + } + } + resp + } + }; + reply_chan.send(response) } } diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs new file mode 100644 index 00000000000..0260fd346c7 --- /dev/null +++ b/src/components/main/layout/model.rs @@ -0,0 +1,199 @@ +/* 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/. */ + +//! Borders, padding, and margins. + +use layout::display_list_builder::{ExtraDisplayListData, ToGfxColor}; +use layout::box::RenderBox; + +use core::cell::Cell; +use core::num::Zero; +use geom::point::Point2D; +use geom::rect::Rect; +use geom::size::Size2D; +use geom::side_offsets::SideOffsets2D; +use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass, DisplayList}; +use gfx::geometry::Au; +use newcss::complete::CompleteStyle; +use newcss::units::{Em, Pt, Px}; +use newcss::values::{CSSBorderWidth, CSSBorderWidthLength, CSSBorderWidthMedium}; +use newcss::values::{CSSBorderWidthThick, CSSBorderWidthThin}; +use newcss::values::{CSSWidth, CSSWidthLength, CSSWidthPercentage, CSSWidthAuto}; +use newcss::values::{CSSMargin, CSSMarginLength, CSSMarginPercentage, CSSMarginAuto}; +use newcss::values::{CSSPadding, CSSPaddingLength, CSSPaddingPercentage}; +/// Encapsulates the borders, padding, and margins, which we collectively call the "box model". +pub struct BoxModel { + border: SideOffsets2D, + padding: SideOffsets2D, + margin: SideOffsets2D, + /// The width of the content box. + content_box_width: Au, +} + +/// Useful helper data type when computing values for blocks and positioned elements. +pub enum MaybeAuto { + Auto, + Specified(Au), +} + +impl MaybeAuto { + pub fn from_margin(margin: CSSMargin) -> MaybeAuto{ + match margin { + CSSMarginAuto => Auto, + //FIXME(eatkinson): Compute percents properly + CSSMarginPercentage(_) => Specified(Au(0)), + //FIXME(eatkinson): Compute pt and em values properly + CSSMarginLength(Px(v)) | + CSSMarginLength(Pt(v)) | + CSSMarginLength(Em(v)) => Specified(Au::from_frac_px(v)), + } + } + + pub fn from_width(width: CSSWidth) -> MaybeAuto{ + match width{ + CSSWidthAuto => Auto, + //FIXME(eatkinson): Compute percents properly + CSSWidthPercentage(_) => Specified(Au(0)), + //FIXME(eatkinson): Compute pt and em values properly + CSSWidthLength(Px(v)) | + CSSWidthLength(Pt(v)) | + CSSWidthLength(Em(v)) => Specified(Au::from_frac_px(v)), + } + } + + pub fn spec_or_default(&self, default: Au) -> Au{ + match *self{ + Auto => default, + Specified(value) => value + } + } +} + +impl Zero for BoxModel { + fn zero() -> BoxModel { + BoxModel { + border: Zero::zero(), + padding: Zero::zero(), + margin: Zero::zero(), + content_box_width: Zero::zero(), + } + } + + fn is_zero(&self) -> bool { + self.padding.is_zero() && self.border.is_zero() && self.margin.is_zero() + } +} + +impl BoxModel { + /// Populates the box model parameters from the given computed style. + pub fn compute_borders(&mut self, style: CompleteStyle) { + // Compute the borders. + self.border.top = self.compute_border_width(style.border_top_width()); + self.border.right = self.compute_border_width(style.border_right_width()); + self.border.bottom = self.compute_border_width(style.border_bottom_width()); + self.border.left = self.compute_border_width(style.border_left_width()); + } + + pub fn compute_padding(&mut self, style: CompleteStyle, cb_width: Au){ + self.padding.top = self.compute_padding_length(style.padding_top(), cb_width); + self.padding.right = self.compute_padding_length(style.padding_right(), cb_width); + self.padding.bottom = self.compute_padding_length(style.padding_bottom(), cb_width); + self.padding.left = self.compute_padding_length(style.padding_left(), cb_width); + } + + pub fn noncontent_width(&self) -> Au { + let left = self.margin.left + self.border.left + self.padding.left; + let right = self.margin.right + self.border.right + self.padding.right; + left + right + } + + pub fn offset(&self) -> Au { + self.margin.left + self.border.left + self.padding.left + } + + /// Helper function to compute the border width in app units from the CSS border width. + priv fn compute_border_width(&self, width: CSSBorderWidth) -> Au { + match width { + CSSBorderWidthLength(Px(v)) | + CSSBorderWidthLength(Em(v)) | + CSSBorderWidthLength(Pt(v)) => { + // FIXME(pcwalton): Handle `em` and `pt` correctly. + Au::from_frac_px(v) + } + CSSBorderWidthThin => Au::from_px(1), + CSSBorderWidthMedium => Au::from_px(5), + CSSBorderWidthThick => Au::from_px(10), + } + } + + fn compute_padding_length(&self, padding: CSSPadding, content_box_width: Au) -> Au { + match padding { + CSSPaddingLength(Px(v)) | + CSSPaddingLength(Pt(v)) | + CSSPaddingLength(Em(v)) => { + // FIXME(eatkinson): Handle 'em' and 'pt' correctly + Au::from_frac_px(v) + } + CSSPaddingPercentage(p) => content_box_width.scale_by(p) + } + } +} + +// +// Painting +// + +impl RenderBox { + /// 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 + } + + // Are all the widths equal? + // + // FIXME(pcwalton): Obviously this is wrong. + if [ border.top, border.right, border.bottom ].all(|a| *a == border.left) { + let border_width = border.top; + let bounds = Rect { + origin: Point2D { + x: abs_bounds.origin.x, + y: abs_bounds.origin.y, + }, + size: Size2D { + width: abs_bounds.size.width, + height: abs_bounds.size.height + } + }; + + let top_color = self.style().border_top_color(); + let color = top_color.to_gfx_color(); // FIXME + + // Append the border to the display list. + do list.with_mut_ref |list| { + let border_display_item = ~BorderDisplayItem { + base: BaseDisplayItem { + bounds: bounds, + extra: ExtraDisplayListData::new(*self), + }, + width: border_width, + color: color, + }; + + list.append_item(BorderDisplayItemClass(border_display_item)) + } + } else { + warn!("ignoring unimplemented border widths"); + } + } + +} + diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index 1e33c66b277..07a2f3bf9c2 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -7,8 +7,8 @@ /// GLUT is a very old and bare-bones toolkit. However, it has good cross-platform support, at /// least on desktops. It is designed for testing Servo without the need of a UI. -use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, ResizeCallback}; -use windowing::{ScrollCallback, WindowMethods}; +use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, ClickCallback}; +use windowing::{ResizeCallback, ScrollCallback, WindowMethods}; use alert::{Alert, AlertMethods}; use core::libc::c_int; @@ -16,6 +16,7 @@ use geom::point::Point2D; use geom::size::Size2D; use glut::glut::{DOUBLE, WindowHeight, WindowWidth}; use glut::glut; +use glut::machack; /// A structure responsible for setting up and tearing down the entire windowing system. pub struct Application; @@ -35,6 +36,7 @@ pub struct Window { composite_callback: Option, resize_callback: Option, load_url_callback: Option, + click_callback: Option, scroll_callback: Option, drag_origin: Point2D, @@ -54,6 +56,7 @@ impl WindowMethods for Window { composite_callback: None, resize_callback: None, load_url_callback: None, + click_callback: None, scroll_callback: None, drag_origin: Point2D(0, 0), @@ -76,13 +79,16 @@ impl WindowMethods for Window { do glut::keyboard_func |key, _, _| { window.handle_key(key) } - do glut::mouse_func |_, _, x, y| { - window.start_drag(x, y) - } - do glut::motion_func |x, y| { - window.continue_drag(x, y) + do glut::mouse_func |button, _, x, y| { + if button < 3 { + window.handle_click(x, y); + } else { + window.handle_scroll(if button == 4 { -30.0 } else { 30.0 }); + } } + machack::perform_scroll_wheel_hack(); + window } @@ -94,7 +100,6 @@ impl WindowMethods for Window { /// Presents the window to the screen (perhaps by page flipping). pub fn present(&mut self) { glut::swap_buffers(); - glut::post_redisplay(); } /// Registers a callback to run when a composite event occurs. @@ -112,6 +117,11 @@ impl WindowMethods for Window { self.load_url_callback = Some(new_load_url_callback) } + /// Registers a callback to be run when a click event occurs. + pub fn set_click_callback(&mut self, new_click_callback: ClickCallback) { + self.click_callback = Some(new_click_callback) + } + /// Registers a callback to be run when the user scrolls. pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback) { self.scroll_callback = Some(new_scroll_callback) @@ -137,20 +147,19 @@ impl Window { } } - /// Helper function to start a drag. - fn start_drag(&mut self, x: c_int, y: c_int) { - self.drag_origin = Point2D(x, y) + /// Helper function to handle a click + fn handle_click(&self, x: c_int, y: c_int) { + match self.click_callback { + None => {} + Some(callback) => callback(Point2D(x as f32, y as f32)), + } } - /// Helper function to continue a drag. - fn continue_drag(&mut self, x: c_int, y: c_int) { - let new_point = Point2D(x, y); - let delta = new_point - self.drag_origin; - self.drag_origin = new_point; - + /// Helper function to handle a scroll. + fn handle_scroll(&mut self, delta: f32) { match self.scroll_callback { None => {} - Some(callback) => callback(Point2D(delta.x as f32, delta.y as f32)), + Some(callback) => callback(Point2D(0.0, delta)), } } diff --git a/src/components/main/servo.rc b/src/components/main/servo.rc index d2c29354bdd..50a599a0634 100755 --- a/src/components/main/servo.rc +++ b/src/components/main/servo.rc @@ -68,6 +68,7 @@ pub mod layout { pub mod flow; pub mod layout_task; pub mod inline; + pub mod model; pub mod text; mod aux; } diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 708ea4105ec..9a9cefaf03b 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -16,6 +16,10 @@ pub type ResizeCallback = @fn(uint, uint); /// Type of the function that is called when a new URL is to be loaded. pub type LoadUrlCallback = @fn(&str); +/// Type of the function that is called when hit testing is to be performed. +/// FIXME this currently does not discriminate between left and right clicks or any modifiers +pub type ClickCallback = @fn(Point2D); + /// Type of the function that is called when the user scrolls. pub type ScrollCallback = @fn(Point2D); @@ -38,6 +42,8 @@ pub trait WindowMethods { pub fn set_resize_callback(&mut self, new_resize_callback: ResizeCallback); /// Registers a callback to run when a new URL is to be loaded. pub fn set_load_url_callback(&mut self, new_load_url_callback: LoadUrlCallback); + /// Registers a callback to run when the user clicks. + pub fn set_click_callback(&mut self, new_click_callback: ClickCallback); /// Registers a callback to run when the user scrolls. pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback); diff --git a/src/components/script/dom/event.rs b/src/components/script/dom/event.rs index 7b1fba2ac37..a0ef39d8fca 100644 --- a/src/components/script/dom/event.rs +++ b/src/components/script/dom/event.rs @@ -7,9 +7,12 @@ use dom::window::Window; use dom::bindings::codegen::EventBinding; use dom::bindings::utils::{DOMString, ErrorResult, WrapperCache}; +use geom::point::Point2D; + pub enum Event { ResizeEvent(uint, uint, comm::Chan<()>), - ReflowEvent + ReflowEvent, + ClickEvent(Point2D), } pub struct Event_ { diff --git a/src/components/script/dom/window.rs b/src/components/script/dom/window.rs index c94e6488a67..a5904b642c7 100644 --- a/src/components/script/dom/window.rs +++ b/src/components/script/dom/window.rs @@ -4,7 +4,7 @@ use dom::bindings::utils::WrapperCache; use dom::bindings::window; -use layout_interface::MatchSelectorsDamage; +use layout_interface::ReflowForScriptQuery; use script_task::{ExitMsg, FireTimerMsg, ScriptMsg, ScriptContext}; use core::comm::{Chan, SharedChan}; @@ -83,7 +83,7 @@ pub impl Window { fn content_changed(&self) { unsafe { - (*self.script_context).trigger_relayout(MatchSelectorsDamage); + (*self.script_context).reflow_all(ReflowForScriptQuery) } } diff --git a/src/components/script/layout_interface.rs b/src/components/script/layout_interface.rs index b442dc440d3..f9ab0a8eb2e 100644 --- a/src/components/script/layout_interface.rs +++ b/src/components/script/layout_interface.rs @@ -6,12 +6,13 @@ /// coupling between these two components, and enables the DOM to be placed in a separate crate /// from layout. -use dom::node::{AbstractNode, ScriptView}; +use dom::node::{AbstractNode, ScriptView, LayoutView}; use script_task::ScriptMsg; use core::comm::{Chan, SharedChan}; use geom::rect::Rect; use geom::size::Size2D; +use geom::point::Point2D; use gfx::geometry::Au; use newcss::stylesheet::Stylesheet; use std::net::url::Url; @@ -24,9 +25,7 @@ pub enum Msg { AddStylesheetMsg(Stylesheet), /// Requests a reflow. - /// - /// FIXME(pcwalton): Call this `reflow` instead? - BuildMsg(~BuildData), + ReflowMsg(~Reflow), /// Performs a synchronous layout request. /// @@ -43,6 +42,8 @@ pub enum LayoutQuery { ContentBoxQuery(AbstractNode), /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call. ContentBoxesQuery(AbstractNode), + /// Requests the node containing the point of interest + HitTestQuery(AbstractNode, Point2D), } /// The reply of a synchronous message from script to layout. @@ -54,44 +55,65 @@ pub enum LayoutResponse { ContentBoxResponse(Rect), /// A response to the `ContentBoxesQuery` message. ContentBoxesResponse(~[Rect]), + /// A response to the `HitTestQuery` message. + HitTestResponse(AbstractNode), } -/// Dirty bits for layout. -pub enum Damage { - /// The document is clean; nothing needs to be done. - NoDamage, - /// Reflow, but do not perform CSS selector matching. - ReflowDamage, +/// Determines which part of the +pub enum DocumentDamageLevel { /// Perform CSS selector matching and reflow. - MatchSelectorsDamage, + MatchSelectorsDocumentDamage, + /// Reflow, but do not perform CSS selector matching. + ReflowDocumentDamage, } -impl Damage { +impl DocumentDamageLevel { /// Sets this damage to the maximum of this damage and the given damage. /// /// FIXME(pcwalton): This could be refactored to use `max` and the `Ord` trait, and this /// function removed. - fn add(&mut self, new_damage: Damage) { + fn add(&mut self, new_damage: DocumentDamageLevel) { match (*self, new_damage) { - (NoDamage, _) => *self = new_damage, - (ReflowDamage, NoDamage) => *self = ReflowDamage, - (ReflowDamage, new_damage) => *self = new_damage, - (MatchSelectorsDamage, _) => *self = MatchSelectorsDamage + (ReflowDocumentDamage, new_damage) => *self = new_damage, + (MatchSelectorsDocumentDamage, _) => *self = MatchSelectorsDocumentDamage, } } } +/// What parts of the document have changed, as far as the script task can tell. +/// +/// Note that this is fairly coarse-grained and is separate from layout's notion of the document +pub struct DocumentDamage { + /// The topmost node in the tree that has changed. + root: AbstractNode, + /// The amount of damage that occurred. + level: DocumentDamageLevel, +} + +/// Why we're doing reflow. +#[deriving(Eq)] +pub enum ReflowGoal { + /// We're reflowing in order to send a display list to the screen. + ReflowForDisplay, + /// We're reflowing in order to satisfy a script query. No display list will be created. + ReflowForScriptQuery, +} + /// Information needed for a reflow. -pub struct BuildData { - node: AbstractNode, - /// What reflow needs to be done. - damage: Damage, +pub struct Reflow { + /// The document node. + document_root: AbstractNode, + /// The style changes that need to be done. + damage: DocumentDamage, + /// The goal of reflow: either to render to the screen or to flush layout info for script. + goal: ReflowGoal, /// The URL of the page. url: Url, /// The channel through which messages can be sent back to the script task. script_chan: SharedChan, /// The current window size. window_size: Size2D, + /// The channel that we send a notification to. script_join_chan: Chan<()>, } diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index cf99428c677..66a4e9e4727 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -7,12 +7,13 @@ use dom::bindings::utils::GlobalStaticData; use dom::document::Document; -use dom::event::{Event, ResizeEvent, ReflowEvent}; -use dom::node::define_bindings; +use dom::event::{Event, ResizeEvent, ReflowEvent, ClickEvent}; +use dom::node::{AbstractNode, ScriptView, define_bindings}; use dom::window::Window; -use layout_interface::{AddStylesheetMsg, BuildData, BuildMsg, Damage, LayoutQuery}; -use layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDamage, NoDamage}; -use layout_interface::{QueryMsg, ReflowDamage}; +use layout_interface::{AddStylesheetMsg, DocumentDamage, DocumentDamageLevel, HitTestQuery}; +use layout_interface::{HitTestResponse, LayoutQuery, LayoutResponse, LayoutTask}; +use layout_interface::{MatchSelectorsDocumentDamage, QueryMsg, Reflow, ReflowDocumentDamage}; +use layout_interface::{ReflowForDisplay, ReflowForScriptQuery, ReflowGoal, ReflowMsg}; use layout_interface; use core::cast::transmute; @@ -132,8 +133,8 @@ pub struct ScriptContext { /// The current size of the window, in pixels. window_size: Size2D, - /// What parts of layout are dirty. - damage: Damage, + /// What parts of the document are dirty, if any. + damage: Option, } fn global_script_context_key(_: @ScriptContext) {} @@ -199,7 +200,7 @@ impl ScriptContext { root_frame: None, window_size: Size2D(800, 600), - damage: MatchSelectorsDamage, + damage: None, }; // Indirection for Rust Issue #6248, dynamic freeze scope artifically extended let script_context_ptr = { @@ -283,7 +284,7 @@ impl ScriptContext { null(), &rval); - self.relayout() + self.reflow(ReflowForScriptQuery) } /// Handles a request to exit the script task and shut down layout. @@ -348,8 +349,11 @@ impl ScriptContext { }); // Perform the initial reflow. - self.damage.add(MatchSelectorsDamage); - self.relayout(); + self.damage = Some(DocumentDamage { + root: root_node, + level: MatchSelectorsDocumentDamage, + }); + self.reflow(ReflowForDisplay); // Define debug functions. self.js_compartment.define_functions(debug_fns); @@ -383,19 +387,13 @@ impl ScriptContext { } } - /// Initiate an asynchronous relayout operation - pub fn trigger_relayout(&mut self, damage: Damage) { - self.damage.add(damage); - self.relayout(); - } - /// This method will wait until the layout task has completed its current action, join the /// layout task, and then request a new layout run. It won't wait for the new layout /// computation to finish. /// /// This function fails if there is no root frame. - fn relayout(&mut self) { - debug!("script: performing relayout"); + fn reflow(&mut self, goal: ReflowGoal) { + debug!("script: performing reflow"); // Now, join the layout so that they will see the latest changes we have made. self.join_layout(); @@ -408,22 +406,36 @@ impl ScriptContext { None => fail!(~"Tried to relayout with no root frame!"), Some(ref root_frame) => { // Send new document and relevant styles to layout. - let data = ~BuildData { - node: root_frame.document.root, + let reflow = ~Reflow { + document_root: root_frame.document.root, url: copy root_frame.url, + goal: goal, script_chan: self.script_chan.clone(), window_size: self.window_size, script_join_chan: join_chan, - damage: replace(&mut self.damage, NoDamage), + damage: replace(&mut self.damage, None).unwrap(), }; - self.layout_task.chan.send(BuildMsg(data)) + self.layout_task.chan.send(ReflowMsg(reflow)) } } debug!("script: layout forked") } + /// Reflows the entire document. + /// + /// FIXME: This should basically never be used. + pub fn reflow_all(&mut self, goal: ReflowGoal) { + for self.root_frame.each |root_frame| { + ScriptContext::damage(&mut self.damage, + root_frame.document.root, + MatchSelectorsDocumentDamage) + } + + self.reflow(goal) + } + /// Sends the given query to layout. pub fn query_layout(&mut self, query: LayoutQuery) -> Result { self.join_layout(); @@ -433,6 +445,26 @@ impl ScriptContext { response_port.recv() } + /// Adds the given damage. + fn damage(damage: &mut Option, + root: AbstractNode, + level: DocumentDamageLevel) { + match *damage { + None => {} + Some(ref mut damage) => { + // FIXME(pcwalton): This is wrong. We should trace up to the nearest ancestor. + damage.root = root; + damage.level.add(level); + return + } + } + + *damage = Some(DocumentDamage { + root: root, + level: level, + }) + } + /// This is the main entry point for receiving and dispatching DOM events. /// /// TODO: Actually perform DOM event dispatch. @@ -441,25 +473,52 @@ impl ScriptContext { ResizeEvent(new_width, new_height, response_chan) => { debug!("script got resize event: %u, %u", new_width, new_height); - self.damage.add(ReflowDamage); self.window_size = Size2D(new_width, new_height); + for self.root_frame.each |root_frame| { + ScriptContext::damage(&mut self.damage, + root_frame.document.root, + ReflowDocumentDamage); + } + if self.root_frame.is_some() { - self.relayout() + self.reflow(ReflowForDisplay) } response_chan.send(()) } + // FIXME(pcwalton): This reflows the entire document and is not incremental-y. ReflowEvent => { debug!("script got reflow event"); - self.damage.add(MatchSelectorsDamage); + for self.root_frame.each |root_frame| { + ScriptContext::damage(&mut self.damage, + root_frame.document.root, + MatchSelectorsDocumentDamage); + } if self.root_frame.is_some() { - self.relayout() + self.reflow(ReflowForDisplay) } } + + ClickEvent(point) => { + debug!("ClickEvent: clicked at %?", point); + let root = match self.root_frame { + Some(ref frame) => frame.document.root, + None => fail!("root frame is None") + }; + match self.query_layout(HitTestQuery(root, point)) { + Ok(node) => match node { + HitTestResponse(node) => debug!("clicked on %?", node.debug_str()), + _ => fail!(~"unexpected layout reply") + }, + Err(()) => { + println(fmt!("layout query error")); + } + }; + } } } } diff --git a/src/platform/macos/rust-cocoa b/src/platform/macos/rust-cocoa index 61c99b4b1aa..d916b979666 160000 --- a/src/platform/macos/rust-cocoa +++ b/src/platform/macos/rust-cocoa @@ -1 +1 @@ -Subproject commit 61c99b4b1aa8653b8ee8511b6c15099de441ac44 +Subproject commit d916b9796668d4bb46a3b1b4976c95ab7da79837 diff --git a/src/support/css/rust-css b/src/support/css/rust-css index e3057f02d48..57407e5209e 160000 --- a/src/support/css/rust-css +++ b/src/support/css/rust-css @@ -1 +1 @@ -Subproject commit e3057f02d48bf43856a0c13ad17372647f3b934f +Subproject commit 57407e5209ec95e695e17447fa7c5dfea96629b3 diff --git a/src/support/geom/rust-geom b/src/support/geom/rust-geom index 61f46e50356..ebf1cc8f2e8 160000 --- a/src/support/geom/rust-geom +++ b/src/support/geom/rust-geom @@ -1 +1 @@ -Subproject commit 61f46e5035685e8e3d8b554db3b1e90c21d63fa2 +Subproject commit ebf1cc8f2e8a39f31da2575981fad966aa7da904 diff --git a/src/support/glut/rust-glut b/src/support/glut/rust-glut index eb35e395783..453bf81e021 160000 --- a/src/support/glut/rust-glut +++ b/src/support/glut/rust-glut @@ -1 +1 @@ -Subproject commit eb35e3957834264e99f42274f9c5c442e747ed3b +Subproject commit 453bf81e021008f5eba29b135f07f4529e6c8b2e diff --git a/src/support/netsurfcss/rust-netsurfcss b/src/support/netsurfcss/rust-netsurfcss index 36a651dd830..3565b32ba3d 160000 --- a/src/support/netsurfcss/rust-netsurfcss +++ b/src/support/netsurfcss/rust-netsurfcss @@ -1 +1 @@ -Subproject commit 36a651dd83089c01da888ee9b5fff14437d4bcb8 +Subproject commit 3565b32ba3d15d31b02cc76bdf76d6b13fc88451 diff --git a/src/support/nss/nspr b/src/support/nss/nspr new file mode 160000 index 00000000000..8219c5c5137 --- /dev/null +++ b/src/support/nss/nspr @@ -0,0 +1 @@ +Subproject commit 8219c5c5137b94ae93cfb4afc1dc10a94384e1c6 diff --git a/src/support/nss/nss b/src/support/nss/nss new file mode 160000 index 00000000000..8ba903a1af2 --- /dev/null +++ b/src/support/nss/nss @@ -0,0 +1 @@ +Subproject commit 8ba903a1af20ce461d4f4033ec1092a229bc7482 diff --git a/src/test/html/test_slam_layout.css b/src/test/html/test_slam_layout.css new file mode 100644 index 00000000000..fed7aa97a3a --- /dev/null +++ b/src/test/html/test_slam_layout.css @@ -0,0 +1,10 @@ +#ohhi { + background-color: red; + padding: 6px; +} + +#mark { + background-color: blue; + padding: 12px; +} + diff --git a/src/test/html/test_slam_layout.html b/src/test/html/test_slam_layout.html new file mode 100644 index 00000000000..55adc8109b0 --- /dev/null +++ b/src/test/html/test_slam_layout.html @@ -0,0 +1,7 @@ + + + + + +
+ diff --git a/src/test/html/test_slam_layout.js b/src/test/html/test_slam_layout.js new file mode 100644 index 00000000000..5d1726b904e --- /dev/null +++ b/src/test/html/test_slam_layout.js @@ -0,0 +1,14 @@ +var divs = document.getElementsByTagName("div"); +var div = divs[0]; + +var count = 1000; +var start = new Date(); +for (var i = 0; i < count; i++) { + if (i % 2 == 0) + div.setAttribute('id', 'ohhi'); + else + div.setAttribute('id', 'mark'); + div.getBoundingClientRect(); +} +var stop = new Date(); +window.alert((stop - start) / count * 1e6 + " ns/layout");