mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
auto merge of #512 : metajack/servo/pcwalton-master, r=jdm
This is try number 2 for #501 rollup.
This commit is contained in:
commit
0ae2081e63
38 changed files with 952 additions and 366 deletions
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -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
|
||||
|
|
15
configure
vendored
15
configure
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
13
mk/sub.mk
13
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)
|
||||
|
|
|
@ -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<uint>);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<E> {
|
||||
list: ~[DisplayItem<E>]
|
||||
}
|
||||
|
||||
impl DisplayList {
|
||||
impl<E> DisplayList<E> {
|
||||
/// Creates a new display list.
|
||||
pub fn new() -> DisplayList {
|
||||
pub fn new() -> DisplayList<E> {
|
||||
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<E>) {
|
||||
// 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<E> {
|
||||
SolidColorDisplayItemClass(~SolidColorDisplayItem<E>),
|
||||
TextDisplayItemClass(~TextDisplayItem<E>),
|
||||
ImageDisplayItemClass(~ImageDisplayItem<E>),
|
||||
BorderDisplayItemClass(~BorderDisplayItem<E>),
|
||||
}
|
||||
|
||||
/// Information common to all display items.
|
||||
pub struct BaseDisplayItem {
|
||||
pub struct BaseDisplayItem<E> {
|
||||
/// The boundaries of the display item.
|
||||
///
|
||||
/// TODO: Which coordinate system should this use?
|
||||
bounds: Rect<Au>,
|
||||
|
||||
/// Extra data: either the originating flow (for hit testing) or nothing (for rendering).
|
||||
extra: E,
|
||||
}
|
||||
|
||||
/// Renders a solid color.
|
||||
pub struct SolidColorDisplayItem {
|
||||
base: BaseDisplayItem,
|
||||
pub struct SolidColorDisplayItem<E> {
|
||||
base: BaseDisplayItem<E>,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
/// Renders text.
|
||||
pub struct TextDisplayItem {
|
||||
base: BaseDisplayItem,
|
||||
pub struct TextDisplayItem<E> {
|
||||
base: BaseDisplayItem<E>,
|
||||
text_run: ~SendableTextRun,
|
||||
range: Range,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
/// Renders an image.
|
||||
pub struct ImageDisplayItem {
|
||||
base: BaseDisplayItem,
|
||||
pub struct ImageDisplayItem<E> {
|
||||
base: BaseDisplayItem<E>,
|
||||
image: ARC<~Image>,
|
||||
}
|
||||
|
||||
/// Renders a border.
|
||||
pub struct BorderDisplayItem {
|
||||
base: BaseDisplayItem,
|
||||
pub struct BorderDisplayItem<E> {
|
||||
base: BaseDisplayItem<E>,
|
||||
/// The width of the border.
|
||||
width: Au,
|
||||
/// The color of the border.
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl DisplayItem {
|
||||
impl<E> DisplayItem<E> {
|
||||
/// 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<E> {
|
||||
// FIXME(tkuehn): Workaround for Rust region bug.
|
||||
unsafe {
|
||||
match *self {
|
||||
SolidColorDisplayItemClass(ref solid_color) => transmute_region(&solid_color.base),
|
||||
TextDisplayItemClass(ref text) => transmute_region(&text.base),
|
||||
ImageDisplayItemClass(ref image_item) => transmute_region(&image_item.base),
|
||||
BorderDisplayItemClass(ref border) => transmute_region(&border.base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Rect<Au> {
|
||||
self.base().bounds
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 } }
|
||||
|
||||
|
|
|
@ -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<Nothing>,
|
||||
size: Size2D<uint>
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -162,7 +162,7 @@ impl<C: Compositor + Owned> Renderer<C> {
|
|||
};
|
||||
|
||||
debug!("renderer: returning surface");
|
||||
self.compositor.paint(layer_buffer_set);
|
||||
self.compositor.paint(layer_buffer_set, render_layer.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<uint>),
|
||||
/// 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<Msg>,
|
|||
// 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<Msg>,
|
|||
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<Msg>,
|
|||
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<Msg>,
|
|||
|
||||
// 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<Msg>,
|
|||
// 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<Msg>,
|
|||
|
||||
/// 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<uint>) {
|
||||
self.chan.send(Paint(layer_buffer_set, new_size))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
"
|
||||
}
|
||||
|
||||
|
|
|
@ -76,6 +76,16 @@ impl SelectHandler<AbstractNode<LayoutView>> for NodeSelectHandler {
|
|||
self.parent_node(node).is_none()
|
||||
}
|
||||
|
||||
fn node_is_link(&self, node: &AbstractNode<LayoutView>) -> bool {
|
||||
if node.is_element() {
|
||||
do node.with_imm_element |element| {
|
||||
"a" == element.tag_name
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn with_node_id<R>(&self, node: &AbstractNode<LayoutView>, f: &fn(Option<&str>) -> R) -> R {
|
||||
if !node.is_element() {
|
||||
fail!(~"attempting to style non-element node");
|
||||
|
|
|
@ -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<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList>) {
|
||||
pub fn build_display_list_block<E:ExtraDisplayListData>(@mut self,
|
||||
builder: &DisplayListBuilder,
|
||||
dirty: &Rect<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList<E>>) {
|
||||
// add box that starts block context
|
||||
self.box.map(|&box| {
|
||||
box.build_display_list(builder, dirty, offset, list)
|
||||
|
@ -194,3 +298,4 @@ impl BlockFlowData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Au>,
|
||||
|
||||
/// 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<R>(&self, callback: &fn(&RenderBoxBase) -> R) -> R {
|
||||
fn with_base<R>(&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<Au> {
|
||||
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<R>(&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<Au> {
|
||||
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<LayoutView> {
|
||||
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<LayoutView> {
|
||||
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<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList>) {
|
||||
fn build_display_list<E:ExtraDisplayListData>(&self,
|
||||
_: &DisplayListBuilder,
|
||||
dirty: &Rect<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList<E>>) {
|
||||
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<DisplayList>,
|
||||
absolute_bounds: &Rect<Au>) {
|
||||
fn paint_background_if_applicable<E:ExtraDisplayListData>(&self,
|
||||
list: &Cell<DisplayList<E>>,
|
||||
absolute_bounds: &Rect<Au>) {
|
||||
// FIXME: This causes a lot of background colors to be displayed when they are clearly not
|
||||
// needed. We could use display list optimization to clean this up, but it still seems
|
||||
// inefficient. What we really want is something like "nearest ancestor element that
|
||||
|
@ -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<DisplayList>, abs_bounds: &Rect<Au>) {
|
||||
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 {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Au>
|
||||
}
|
||||
|
|
|
@ -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<Au>, c: &Cell<DisplayList>);
|
||||
fn build_display_list_for_child(&self,
|
||||
a: &DisplayListBuilder,
|
||||
b: FlowContext,
|
||||
c: &Rect<Au>,
|
||||
d: &Point2D<Au>,
|
||||
e: &Cell<DisplayList>);
|
||||
fn build_display_list<E:ExtraDisplayListData>(&self,
|
||||
a: &DisplayListBuilder,
|
||||
b: &Rect<Au>,
|
||||
c: &Cell<DisplayList<E>>);
|
||||
fn build_display_list_for_child<E:ExtraDisplayListData>(&self,
|
||||
a: &DisplayListBuilder,
|
||||
b: FlowContext,
|
||||
c: &Rect<Au>,
|
||||
d: &Point2D<Au>,
|
||||
e: &Cell<DisplayList<E>>);
|
||||
}
|
||||
|
||||
impl FlowDisplayListBuilderMethods for FlowContext {
|
||||
fn build_display_list(&self,
|
||||
builder: &DisplayListBuilder,
|
||||
dirty: &Rect<Au>,
|
||||
list: &Cell<DisplayList>) {
|
||||
fn build_display_list<E:ExtraDisplayListData>(&self,
|
||||
builder: &DisplayListBuilder,
|
||||
dirty: &Rect<Au>,
|
||||
list: &Cell<DisplayList<E>>) {
|
||||
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<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList>) {
|
||||
fn build_display_list_for_child<E:ExtraDisplayListData>(&self,
|
||||
builder: &DisplayListBuilder,
|
||||
child_flow: FlowContext,
|
||||
dirty: &Rect<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList<E>>) {
|
||||
// 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);
|
||||
|
|
|
@ -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<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList>) {
|
||||
pub fn build_display_list_recurse<E:ExtraDisplayListData>(&self,
|
||||
builder: &DisplayListBuilder,
|
||||
dirty: &Rect<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList<E>>) {
|
||||
do self.with_base |info| {
|
||||
debug!("FlowContext::build_display_list at %?: %s", info.position, self.debug_str());
|
||||
}
|
||||
|
|
|
@ -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<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList>) {
|
||||
pub fn build_display_list_inline<E:ExtraDisplayListData>(&self,
|
||||
builder: &DisplayListBuilder,
|
||||
dirty: &Rect<Au>,
|
||||
offset: &Point2D<Au>,
|
||||
list: &Cell<DisplayList<E>>) {
|
||||
// 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",
|
||||
|
|
|
@ -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<Msg>,
|
||||
font_ctx: @mut FontContext,
|
||||
doc_url: Option<Url>,
|
||||
screen_size: Option<Size2D<Au>>,
|
||||
|
||||
/// 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<LayoutView> = 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<LayoutView> = unsafe {
|
||||
transmute(node)
|
||||
};
|
||||
let mut flow_node: AbstractNode<LayoutView> = 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<DisplayList<RenderBox>> =
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
|
199
src/components/main/layout/model.rs
Normal file
199
src/components/main/layout/model.rs
Normal file
|
@ -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<Au>,
|
||||
padding: SideOffsets2D<Au>,
|
||||
margin: SideOffsets2D<Au>,
|
||||
/// 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<E:ExtraDisplayListData>(&self,
|
||||
list: &Cell<DisplayList<E>>,
|
||||
abs_bounds: &Rect<Au>) {
|
||||
// Fast path.
|
||||
let border = do self.with_base |base| {
|
||||
base.model.border
|
||||
};
|
||||
if border.is_zero() {
|
||||
return
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<CompositeCallback>,
|
||||
resize_callback: Option<ResizeCallback>,
|
||||
load_url_callback: Option<LoadUrlCallback>,
|
||||
click_callback: Option<ClickCallback>,
|
||||
scroll_callback: Option<ScrollCallback>,
|
||||
|
||||
drag_origin: Point2D<c_int>,
|
||||
|
@ -54,6 +56,7 @@ impl WindowMethods<Application> 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<Application> 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<Application> 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<Application> 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)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<f32>);
|
||||
|
||||
/// Type of the function that is called when the user scrolls.
|
||||
pub type ScrollCallback = @fn(Point2D<f32>);
|
||||
|
||||
|
@ -38,6 +42,8 @@ pub trait WindowMethods<A> {
|
|||
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);
|
||||
|
||||
|
|
|
@ -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<f32>),
|
||||
}
|
||||
|
||||
pub struct Event_ {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ScriptView>),
|
||||
/// Requests the dimensions of all the content boxes, as in the `getClientRects()` call.
|
||||
ContentBoxesQuery(AbstractNode<ScriptView>),
|
||||
/// Requests the node containing the point of interest
|
||||
HitTestQuery(AbstractNode<ScriptView>, Point2D<f32>),
|
||||
}
|
||||
|
||||
/// The reply of a synchronous message from script to layout.
|
||||
|
@ -54,44 +55,65 @@ pub enum LayoutResponse {
|
|||
ContentBoxResponse(Rect<Au>),
|
||||
/// A response to the `ContentBoxesQuery` message.
|
||||
ContentBoxesResponse(~[Rect<Au>]),
|
||||
/// A response to the `HitTestQuery` message.
|
||||
HitTestResponse(AbstractNode<LayoutView>),
|
||||
}
|
||||
|
||||
/// 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<ScriptView>,
|
||||
/// 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<ScriptView>,
|
||||
/// What reflow needs to be done.
|
||||
damage: Damage,
|
||||
pub struct Reflow {
|
||||
/// The document node.
|
||||
document_root: AbstractNode<ScriptView>,
|
||||
/// 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<ScriptMsg>,
|
||||
/// The current window size.
|
||||
window_size: Size2D<uint>,
|
||||
/// The channel that we send a notification to.
|
||||
script_join_chan: Chan<()>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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<uint>,
|
||||
/// What parts of layout are dirty.
|
||||
damage: Damage,
|
||||
/// What parts of the document are dirty, if any.
|
||||
damage: Option<DocumentDamage>,
|
||||
}
|
||||
|
||||
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<LayoutResponse,()> {
|
||||
self.join_layout();
|
||||
|
@ -433,6 +445,26 @@ impl ScriptContext {
|
|||
response_port.recv()
|
||||
}
|
||||
|
||||
/// Adds the given damage.
|
||||
fn damage(damage: &mut Option<DocumentDamage>,
|
||||
root: AbstractNode<ScriptView>,
|
||||
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"));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 61c99b4b1aa8653b8ee8511b6c15099de441ac44
|
||||
Subproject commit d916b9796668d4bb46a3b1b4976c95ab7da79837
|
|
@ -1 +1 @@
|
|||
Subproject commit e3057f02d48bf43856a0c13ad17372647f3b934f
|
||||
Subproject commit 57407e5209ec95e695e17447fa7c5dfea96629b3
|
|
@ -1 +1 @@
|
|||
Subproject commit 61f46e5035685e8e3d8b554db3b1e90c21d63fa2
|
||||
Subproject commit ebf1cc8f2e8a39f31da2575981fad966aa7da904
|
|
@ -1 +1 @@
|
|||
Subproject commit eb35e3957834264e99f42274f9c5c442e747ed3b
|
||||
Subproject commit 453bf81e021008f5eba29b135f07f4529e6c8b2e
|
|
@ -1 +1 @@
|
|||
Subproject commit 36a651dd83089c01da888ee9b5fff14437d4bcb8
|
||||
Subproject commit 3565b32ba3d15d31b02cc76bdf76d6b13fc88451
|
1
src/support/nss/nspr
Submodule
1
src/support/nss/nspr
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 8219c5c5137b94ae93cfb4afc1dc10a94384e1c6
|
1
src/support/nss/nss
Submodule
1
src/support/nss/nss
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 8ba903a1af20ce461d4f4033ec1092a229bc7482
|
10
src/test/html/test_slam_layout.css
Normal file
10
src/test/html/test_slam_layout.css
Normal file
|
@ -0,0 +1,10 @@
|
|||
#ohhi {
|
||||
background-color: red;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
#mark {
|
||||
background-color: blue;
|
||||
padding: 12px;
|
||||
}
|
||||
|
7
src/test/html/test_slam_layout.html
Normal file
7
src/test/html/test_slam_layout.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="test_slam_layout.css">
|
||||
</head>
|
||||
<body><div id="ohhi"></div></body><script src="test_slam_layout.js"></script>
|
||||
</html>
|
14
src/test/html/test_slam_layout.js
Normal file
14
src/test/html/test_slam_layout.js
Normal file
|
@ -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");
|
Loading…
Add table
Add a link
Reference in a new issue