auto merge of #515 : pcwalton/servo/master, r=metajack

Changes authored by me have not yet been reviewed; the other changes have.

r? @metajack
This commit is contained in:
bors-servo 2013-06-13 17:45:25 -07:00
commit 96731e9714
33 changed files with 766 additions and 212 deletions

View file

@ -12,6 +12,8 @@ pub struct LayerBuffer {
// The rect in the containing RenderLayer that this represents. // The rect in the containing RenderLayer that this represents.
rect: Rect<uint>, rect: Rect<uint>,
screen_pos: Rect<uint>,
// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH. // NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH.
stride: uint stride: uint
} }
@ -22,9 +24,17 @@ pub struct LayerBufferSet {
buffers: ~[LayerBuffer] buffers: ~[LayerBuffer]
} }
/// The status of the renderer.
#[deriving(Eq)]
pub enum RenderState {
IdleRenderState,
RenderingRenderState,
}
/// The interface used to by the renderer to acquire draw targets for each rendered frame and /// The interface used to by the renderer to acquire draw targets for each rendered frame and
/// submit them to be drawn to the display. /// submit them to be drawn to the display.
pub trait Compositor { pub trait Compositor {
fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D<uint>); fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D<uint>);
fn set_render_state(&self, render_state: RenderState);
} }

View file

@ -18,6 +18,8 @@ use azure::scaled_font::ScaledFont;
use azure::azure_hl::{BackendType, ColorPattern}; use azure::azure_hl::{BackendType, ColorPattern};
use geom::{Point2D, Rect, Size2D}; use geom::{Point2D, Rect, Size2D};
use servo_util::time::ProfilerChan;
// FontHandle encapsulates access to the platform's font API, // FontHandle encapsulates access to the platform's font API,
// e.g. quartz, FreeType. It provides access to metrics and tables // e.g. quartz, FreeType. It provides access to metrics and tables
// needed by the text shaper as well as access to the underlying font // needed by the text shaper as well as access to the underlying font
@ -210,13 +212,15 @@ pub struct Font {
style: UsedFontStyle, style: UsedFontStyle,
metrics: FontMetrics, metrics: FontMetrics,
backend: BackendType, backend: BackendType,
profiler_chan: ProfilerChan,
} }
pub impl Font { pub impl Font {
fn new_from_buffer(ctx: &FontContext, fn new_from_buffer(ctx: &FontContext,
buffer: ~[u8], buffer: ~[u8],
style: &SpecifiedFontStyle, style: &SpecifiedFontStyle,
backend: BackendType) backend: BackendType,
profiler_chan: ProfilerChan)
-> Result<@mut Font, ()> { -> Result<@mut Font, ()> {
let handle = FontHandleMethods::new_from_buffer(&ctx.handle, buffer, style); let handle = FontHandleMethods::new_from_buffer(&ctx.handle, buffer, style);
let handle: FontHandle = if handle.is_ok() { let handle: FontHandle = if handle.is_ok() {
@ -235,11 +239,13 @@ pub impl Font {
style: copy *style, style: copy *style,
metrics: metrics, metrics: metrics,
backend: backend, backend: backend,
profiler_chan: profiler_chan,
}); });
} }
fn new_from_adopted_handle(_fctx: &FontContext, handle: FontHandle, fn new_from_adopted_handle(_fctx: &FontContext, handle: FontHandle,
style: &SpecifiedFontStyle, backend: BackendType) -> @mut Font { style: &SpecifiedFontStyle, backend: BackendType,
profiler_chan: ProfilerChan) -> @mut Font {
let metrics = handle.get_metrics(); let metrics = handle.get_metrics();
@mut Font { @mut Font {
@ -249,11 +255,13 @@ pub impl Font {
style: copy *style, style: copy *style,
metrics: metrics, metrics: metrics,
backend: backend, backend: backend,
profiler_chan: profiler_chan,
} }
} }
fn new_from_existing_handle(fctx: &FontContext, handle: &FontHandle, fn new_from_existing_handle(fctx: &FontContext, handle: &FontHandle,
style: &SpecifiedFontStyle, backend: BackendType) -> Result<@mut Font,()> { style: &SpecifiedFontStyle, backend: BackendType,
profiler_chan: ProfilerChan) -> Result<@mut Font,()> {
// TODO(Issue #179): convert between specified and used font style here? // TODO(Issue #179): convert between specified and used font style here?
let styled_handle = match handle.clone_with_style(&fctx.handle, style) { let styled_handle = match handle.clone_with_style(&fctx.handle, style) {
@ -261,7 +269,7 @@ pub impl Font {
Err(()) => return Err(()) Err(()) => return Err(())
}; };
return Ok(Font::new_from_adopted_handle(fctx, styled_handle, style, backend)); return Ok(Font::new_from_adopted_handle(fctx, styled_handle, style, backend, profiler_chan));
} }
priv fn get_shaper(@mut self) -> @Shaper { priv fn get_shaper(@mut self) -> @Shaper {

View file

@ -40,17 +40,18 @@ pub struct FontContext {
handle: FontContextHandle, handle: FontContextHandle,
backend: BackendType, backend: BackendType,
generic_fonts: HashMap<~str,~str>, generic_fonts: HashMap<~str,~str>,
profiler_chan: ProfilerChan,
} }
#[allow(non_implicitly_copyable_typarams)] #[allow(non_implicitly_copyable_typarams)]
pub impl<'self> FontContext { pub impl<'self> FontContext {
fn new(backend: BackendType, fn new(backend: BackendType,
needs_font_list: bool, needs_font_list: bool,
prof_chan: ProfilerChan) profiler_chan: ProfilerChan)
-> FontContext { -> FontContext {
let handle = FontContextHandle::new(); let handle = FontContextHandle::new();
let font_list = if needs_font_list { let font_list = if needs_font_list {
Some(FontList::new(&handle, prof_chan.clone())) } Some(FontList::new(&handle, profiler_chan.clone())) }
else { None }; else { None };
// TODO: Allow users to specify these. // TODO: Allow users to specify these.
@ -69,6 +70,7 @@ pub impl<'self> FontContext {
handle: handle, handle: handle,
backend: backend, backend: backend,
generic_fonts: generic_fonts, generic_fonts: generic_fonts,
profiler_chan: profiler_chan,
} }
} }
@ -125,7 +127,8 @@ pub impl<'self> FontContext {
for result.each |font_entry| { for result.each |font_entry| {
found = true; found = true;
// TODO(Issue #203): route this instantion through FontContext's Font instance cache. // TODO(Issue #203): route this instantion through FontContext's Font instance cache.
let instance = Font::new_from_existing_handle(self, &font_entry.handle, style, self.backend); let instance = Font::new_from_existing_handle(self, &font_entry.handle, style, self.backend,
self.profiler_chan.clone());
do result::iter(&instance) |font: &@mut Font| { fonts.push(*font); } do result::iter(&instance) |font: &@mut Font| { fonts.push(*font); }
}; };
@ -139,8 +142,14 @@ pub impl<'self> FontContext {
for last_resort.each |family| { for last_resort.each |family| {
let result = list.find_font_in_family(*family,style); let result = list.find_font_in_family(*family,style);
for result.each |font_entry| { for result.each |font_entry| {
let instance = Font::new_from_existing_handle(self, &font_entry.handle, style, self.backend); let instance = Font::new_from_existing_handle(self,
do result::iter(&instance) |font: &@mut Font| { fonts.push(*font); } &font_entry.handle,
style,
self.backend,
self.profiler_chan.clone());
do result::iter(&instance) |font: &@mut Font| {
fonts.push(*font);
}
} }
} }
@ -163,7 +172,8 @@ pub impl<'self> FontContext {
Ok(Font::new_from_adopted_handle(self, Ok(Font::new_from_adopted_handle(self,
handle, handle,
&desc.style, &desc.style,
self.backend)) self.backend,
self.profiler_chan.clone()))
}) })
} }
}; };

View file

@ -13,6 +13,11 @@ pub struct Opts {
render_backend: BackendType, render_backend: BackendType,
n_render_threads: uint, n_render_threads: uint,
tile_size: uint, tile_size: uint,
profiler_period: Option<f64>,
/// A scale factor to apply to tiles, to allow rendering tiles at higher resolutions for
/// testing pan and zoom code.
zoom: uint,
} }
#[allow(non_implicitly_copyable_typarams)] #[allow(non_implicitly_copyable_typarams)]
@ -26,13 +31,14 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts {
getopts::optopt(~"r"), // rendering backend getopts::optopt(~"r"), // rendering backend
getopts::optopt(~"s"), // size of tiles getopts::optopt(~"s"), // size of tiles
getopts::optopt(~"t"), // threads to render with getopts::optopt(~"t"), // threads to render with
getopts::optflagopt(~"p"), // profiler flag and output interval
getopts::optopt(~"z"), // zoom level
]; ];
let opt_match = match getopts::getopts(args, opts) { let opt_match = match getopts::getopts(args, opts) {
result::Ok(m) => { copy m } result::Ok(m) => { copy m }
result::Err(f) => { fail!(getopts::fail_str(copy f)) } result::Err(f) => { fail!(getopts::fail_str(copy f)) }
}; };
let urls = if opt_match.free.is_empty() { let urls = if opt_match.free.is_empty() {
fail!(~"servo asks that you provide 1 or more URLs") fail!(~"servo asks that you provide 1 or more URLs")
} else { } else {
@ -68,10 +74,24 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts {
None => 1, // FIXME: Number of cores. None => 1, // FIXME: Number of cores.
}; };
let profiler_period: Option<f64> =
// if only flag is present, default to 5 second period
match getopts::opt_default(&opt_match, ~"p", ~"5") {
Some(period) => Some(f64::from_str(period).get()),
None => None,
};
let zoom: uint = match getopts::opt_maybe_str(&opt_match, ~"z") {
Some(zoom_str) => uint::from_str(zoom_str).get(),
None => 1,
};
Opts { Opts {
urls: urls, urls: urls,
render_backend: render_backend, render_backend: render_backend,
n_render_threads: n_render_threads, n_render_threads: n_render_threads,
tile_size: tile_size, tile_size: tile_size,
profiler_period: profiler_period,
zoom: zoom,
} }
} }

View file

@ -37,6 +37,7 @@ pub fn render_layers(layer_ref: *RenderLayer,
f: RenderFn) f: RenderFn)
-> LayerBufferSet { -> LayerBufferSet {
let tile_size = opts.tile_size; let tile_size = opts.tile_size;
let scale = opts.zoom;
// FIXME: Try not to create a new array here. // FIXME: Try not to create a new array here.
let mut new_buffer_ports = ~[]; let mut new_buffer_ports = ~[];
@ -45,12 +46,12 @@ pub fn render_layers(layer_ref: *RenderLayer,
do time::profile(time::RenderingPrepBuffCategory, prof_chan.clone()) { do time::profile(time::RenderingPrepBuffCategory, prof_chan.clone()) {
let layer: &RenderLayer = unsafe { cast::transmute(layer_ref) }; let layer: &RenderLayer = unsafe { cast::transmute(layer_ref) };
let mut y = 0; let mut y = 0;
while y < layer.size.height { while y < layer.size.height * scale {
let mut x = 0; let mut x = 0;
while x < layer.size.width { while x < layer.size.width * scale {
// Figure out the dimension of this tile. // Figure out the dimension of this tile.
let right = uint::min(x + tile_size, layer.size.width); let right = uint::min(x + tile_size, layer.size.width * scale);
let bottom = uint::min(y + tile_size, layer.size.height); let bottom = uint::min(y + tile_size, layer.size.height * scale);
let width = right - x; let width = right - x;
let height = bottom - y; let height = bottom - y;
@ -65,7 +66,8 @@ pub fn render_layers(layer_ref: *RenderLayer,
debug!("tile aligned_width %u", aligned_width); debug!("tile aligned_width %u", aligned_width);
let tile_rect = Rect(Point2D(x, y), Size2D(aligned_width, height)); let tile_rect = Rect(Point2D(x / scale, y / scale), Size2D(aligned_width, height)); //change this
let screen_rect = Rect(Point2D(x, y), Size2D(aligned_width, height)); //change this
let buffer; let buffer;
// FIXME: Try harder to search for a matching tile. // FIXME: Try harder to search for a matching tile.
@ -112,6 +114,7 @@ pub fn render_layers(layer_ref: *RenderLayer,
stride, stride,
B8G8R8A8), B8G8R8A8),
rect: tile_rect, rect: tile_rect,
screen_pos: screen_rect,
stride: stride as uint stride: stride as uint
}; };
//} //}

View file

@ -5,7 +5,7 @@
// The task that handles all rendering/painting. // The task that handles all rendering/painting.
use azure::AzFloat; use azure::AzFloat;
use compositor::Compositor; use compositor::{Compositor, IdleRenderState, RenderingRenderState};
use font_context::FontContext; use font_context::FontContext;
use geom::matrix2d::Matrix2D; use geom::matrix2d::Matrix2D;
use opts::Opts; use opts::Opts;
@ -18,9 +18,7 @@ use core::task::SingleThreaded;
use std::task_pool::TaskPool; use std::task_pool::TaskPool;
use servo_net::util::spawn_listener; use servo_net::util::spawn_listener;
use servo_util::time::ProfilerChan; use servo_util::time::{ProfilerChan, profile};
use servo_util::time::profile;
use servo_util::time::time;
use servo_util::time; use servo_util::time;
pub enum Msg { pub enum Msg {
@ -124,7 +122,8 @@ impl<C: Compositor + Owned> Renderer<C> {
fn render(&mut self, render_layer: RenderLayer) { fn render(&mut self, render_layer: RenderLayer) {
debug!("renderer: rendering"); debug!("renderer: rendering");
do time("rendering") { self.compositor.set_render_state(RenderingRenderState);
do profile(time::RenderingCategory, self.profiler_chan.clone()) {
let layer_buffer_set = do render_layers(&render_layer, let layer_buffer_set = do render_layers(&render_layer,
&self.opts, &self.opts,
self.profiler_chan.clone()) |render_layer_ref, self.profiler_chan.clone()) |render_layer_ref,
@ -142,17 +141,24 @@ impl<C: Compositor + Owned> Renderer<C> {
// Apply the translation to render the tile we want. // Apply the translation to render the tile we want.
let matrix: Matrix2D<AzFloat> = Matrix2D::identity(); let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
let matrix = matrix.translate(&-(layer_buffer.rect.origin.x as AzFloat), let scale = thread_render_context.opts.zoom as f32;
&-(layer_buffer.rect.origin.y as AzFloat));
let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
let matrix = matrix.translate(-(layer_buffer.rect.origin.x as f32) as AzFloat,
-(layer_buffer.rect.origin.y as f32) as AzFloat);
layer_buffer.draw_target.set_transform(&matrix); layer_buffer.draw_target.set_transform(&matrix);
// Clear the buffer. // Clear the buffer.
ctx.clear(); ctx.clear();
// Draw the display list. // Draw the display list.
let render_layer: &RenderLayer = unsafe { let render_layer: &RenderLayer = unsafe {
cast::transmute(render_layer_ref) cast::transmute(render_layer_ref)
}; };
render_layer.display_list.draw_into_context(&ctx); render_layer.display_list.draw_into_context(&ctx);
} }
@ -163,6 +169,7 @@ impl<C: Compositor + Owned> Renderer<C> {
debug!("renderer: returning surface"); debug!("renderer: returning surface");
self.compositor.paint(layer_buffer_set, render_layer.size); self.compositor.paint(layer_buffer_set, render_layer.size);
self.compositor.set_render_state(IdleRenderState);
} }
} }
} }

View file

@ -6,6 +6,8 @@ use font_context::FontContext;
use geometry::Au; use geometry::Au;
use text::glyph::{BreakTypeNormal, GlyphStore}; use text::glyph::{BreakTypeNormal, GlyphStore};
use font::{Font, FontDescriptor, RunMetrics}; use font::{Font, FontDescriptor, RunMetrics};
use servo_util::time;
use servo_util::time::profile;
use servo_util::range::Range; use servo_util::range::Range;
/// A text run. /// A text run.
@ -44,7 +46,9 @@ pub impl<'self> TextRun {
fn new(font: @mut Font, text: ~str, underline: bool) -> TextRun { fn new(font: @mut Font, text: ~str, underline: bool) -> TextRun {
let mut glyph_store = GlyphStore::new(str::char_len(text)); let mut glyph_store = GlyphStore::new(str::char_len(text));
TextRun::compute_potential_breaks(text, &mut glyph_store); TextRun::compute_potential_breaks(text, &mut glyph_store);
font.shape_text(text, &mut glyph_store); do profile(time::LayoutShapingCategory, font.profiler_chan.clone()) {
font.shape_text(text, &mut glyph_store);
}
let run = TextRun { let run = TextRun {
text: text, text: text,

View file

@ -5,8 +5,13 @@
use compositing::resize_rate_limiter::ResizeRateLimiter; use compositing::resize_rate_limiter::ResizeRateLimiter;
use platform::{Application, Window}; use platform::{Application, Window};
use script::script_task::{LoadMsg, ScriptMsg, SendEventMsg}; use script::script_task::{LoadMsg, ScriptMsg, SendEventMsg};
use windowing::{ApplicationMethods, WindowMethods}; use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClickEvent};
use script::dom::event::ClickEvent; use windowing::{WindowMouseDownEvent, WindowMouseUpEvent};
use gfx::compositor::RenderState;
use script::dom::event::{Event, ClickEvent, MouseDownEvent, MouseUpEvent};
use script::compositor_interface::{ReadyState, CompositorInterface};
use script::compositor_interface;
use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods}; use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods};
use core::cell::Cell; use core::cell::Cell;
@ -16,7 +21,7 @@ use core::util;
use geom::matrix::identity; use geom::matrix::identity;
use geom::point::Point2D; use geom::point::Point2D;
use geom::size::Size2D; use geom::size::Size2D;
use gfx::compositor::{Compositor, LayerBufferSet}; use gfx::compositor::{Compositor, LayerBufferSet, RenderState};
use layers::layers::{ARGB32Format, BasicImageData, ContainerLayer, ContainerLayerKind, Format}; use layers::layers::{ARGB32Format, BasicImageData, ContainerLayer, ContainerLayerKind, Format};
use layers::layers::{Image, ImageData, ImageLayer, ImageLayerKind, RGB24Format, WithDataFn}; use layers::layers::{Image, ImageData, ImageLayer, ImageLayerKind, RGB24Format, WithDataFn};
use layers::rendergl; use layers::rendergl;
@ -34,11 +39,17 @@ pub struct CompositorTask {
chan: SharedChan<Msg>, chan: SharedChan<Msg>,
} }
impl CompositorInterface for CompositorTask {
fn set_ready_state(&self, ready_state: ReadyState) {
let msg = ChangeReadyState(ready_state);
self.chan.send(msg);
}
}
impl CompositorTask { impl CompositorTask {
/// Starts the compositor. Returns an interface that can be used to communicate with the /// Starts the compositor. Returns an interface that can be used to communicate with the
/// compositor and a port which allows notification when the compositor shuts down. /// compositor and a port which allows notification when the compositor shuts down.
pub fn new(script_chan: SharedChan<ScriptMsg>, pub fn new(script_chan: SharedChan<ScriptMsg>, profiler_chan: ProfilerChan)
profiler_chan: ProfilerChan)
-> (CompositorTask, Port<()>) { -> (CompositorTask, Port<()>) {
let script_chan = Cell(script_chan); let script_chan = Cell(script_chan);
let (shutdown_port, shutdown_chan) = stream(); let (shutdown_port, shutdown_chan) = stream();
@ -61,10 +72,14 @@ impl CompositorTask {
/// Messages to the compositor. /// Messages to the compositor.
pub enum Msg { pub enum Msg {
/// 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. /// Requests that the compositor shut down.
Exit, Exit,
/// Requests that the compositor paint the given layer buffer set for the given page size.
Paint(LayerBufferSet, Size2D<uint>),
/// Alerts the compositor to the current status of page loading.
ChangeReadyState(ReadyState),
/// Alerts the compositor to the current status of rendering.
ChangeRenderState(RenderState),
} }
/// Azure surface wrapping to work with the layers infrastructure. /// Azure surface wrapping to work with the layers infrastructure.
@ -126,15 +141,20 @@ fn run_main_loop(port: Port<Msg>,
let page_size = @mut Size2D(0f32, 0f32); let page_size = @mut Size2D(0f32, 0f32);
let window_size = @mut Size2D(800, 600); let window_size = @mut Size2D(800, 600);
// Keeps track of the current zoom factor
let world_zoom = @mut 1f32;
let check_for_messages: @fn() = || { let check_for_messages: @fn() = || {
// Periodically check if the script task responded to our last resize event // Periodically check if the script task responded to our last resize event
resize_rate_limiter.check_resize_response(); resize_rate_limiter.check_resize_response();
// Handle messages // Handle messages
while port.peek() { while port.peek() {
match port.recv() { match port.recv() {
Exit => *done = true, Exit => *done = true,
ChangeReadyState(ready_state) => window.set_ready_state(ready_state),
ChangeRenderState(render_state) => window.set_render_state(render_state),
Paint(new_layer_buffer_set, new_size) => { Paint(new_layer_buffer_set, new_size) => {
debug!("osmain: received new frame"); debug!("osmain: received new frame");
@ -183,7 +203,7 @@ fn run_main_loop(port: Port<Msg>,
Some(_) => fail!(~"found unexpected layer kind"), Some(_) => fail!(~"found unexpected layer kind"),
}; };
let origin = buffer.rect.origin; let origin = buffer.screen_pos.origin;
let origin = Point2D(origin.x as f32, origin.y as f32); let origin = Point2D(origin.x as f32, origin.y as f32);
// Set the layer's transform. // Set the layer's transform.
@ -233,12 +253,24 @@ fn run_main_loop(port: Port<Msg>,
let script_chan_clone = script_chan.clone(); let script_chan_clone = script_chan.clone();
// When the user clicks, perform hit testing // When the user triggers a mouse event, perform appropriate hit testing
do window.set_click_callback |layer_click_point| { do window.set_mouse_callback |window_mouse_event: WindowMouseEvent| {
let world_click_point = layer_click_point + *world_offset; let event: Event;
debug!("osmain: clicked at %?", world_click_point); let world_mouse_point = |layer_mouse_point: Point2D<f32>| {
layer_mouse_point + *world_offset
script_chan_clone.send(SendEventMsg(ClickEvent(world_click_point))); };
match window_mouse_event {
WindowClickEvent(button, layer_mouse_point) => {
event = ClickEvent(button, world_mouse_point(layer_mouse_point));
}
WindowMouseDownEvent(button, layer_mouse_point) => {
event = MouseDownEvent(button, world_mouse_point(layer_mouse_point));
}
WindowMouseUpEvent(button, layer_mouse_point) => {
event = MouseUpEvent(button, world_mouse_point(layer_mouse_point));
}
}
script_chan_clone.send(SendEventMsg(event));
} }
// When the user scrolls, move the layer around. // When the user scrolls, move the layer around.
@ -248,16 +280,64 @@ fn run_main_loop(port: Port<Msg>,
*world_offset = world_offset_copy - delta; *world_offset = world_offset_copy - delta;
// Clamp the world offset to the screen size. // Clamp the world offset to the screen size.
let max_x = (page_size.width - window_size.width as f32).max(&0.0); let max_x = (page_size.width * *world_zoom - window_size.width as f32).max(&0.0);
world_offset.x = world_offset.x.clamp(&0.0, &max_x); 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); let max_y = (page_size.height * *world_zoom - window_size.height as f32).max(&0.0);
world_offset.y = world_offset.y.clamp(&0.0, &max_y); world_offset.y = world_offset.y.clamp(&0.0, &max_y);
debug!("compositor: scrolled to %?", *world_offset); debug!("compositor: scrolled to %?", *world_offset);
root_layer.common.set_transform(identity().translate(-world_offset.x, let mut scroll_transform = identity();
-world_offset.y,
0.0)); scroll_transform = scroll_transform.translate(window_size.width as f32 / 2f32 * *world_zoom - world_offset.x,
window_size.height as f32 / 2f32 * *world_zoom - world_offset.y,
0.0);
scroll_transform = scroll_transform.scale(*world_zoom, *world_zoom, 1f32);
scroll_transform = scroll_transform.translate(window_size.width as f32 / -2f32,
window_size.height as f32 / -2f32,
0.0);
root_layer.common.set_transform(scroll_transform);
window.set_needs_display()
}
// When the user pinch-zooms, scale the layer
do window.set_zoom_callback |magnification| {
let old_world_zoom = *world_zoom;
// Determine zoom amount
*world_zoom = (*world_zoom * magnification).max(&1.0);
// Update world offset
let corner_to_center_x = world_offset.x + window_size.width as f32 / 2f32;
let new_corner_to_center_x = corner_to_center_x * *world_zoom / old_world_zoom;
world_offset.x = world_offset.x + new_corner_to_center_x - corner_to_center_x;
let corner_to_center_y = world_offset.y + window_size.height as f32 / 2f32;
let new_corner_to_center_y = corner_to_center_y * *world_zoom / old_world_zoom;
world_offset.y = world_offset.y + new_corner_to_center_y - corner_to_center_y;
// Clamp to page bounds when zooming out
let max_x = (page_size.width * *world_zoom - 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 * *world_zoom - window_size.height as f32).max(&0.0);
world_offset.y = world_offset.y.clamp(&0.0, &max_y);
// Apply transformations
let mut zoom_transform = identity();
zoom_transform = zoom_transform.translate(window_size.width as f32 / 2f32 * *world_zoom - world_offset.x,
window_size.height as f32 / 2f32 * *world_zoom - world_offset.y,
0.0);
zoom_transform = zoom_transform.scale(*world_zoom, *world_zoom, 1f32);
zoom_transform = zoom_transform.translate(window_size.width as f32 / -2f32,
window_size.height as f32 / -2f32,
0.0);
root_layer.common.set_transform(zoom_transform);
window.set_needs_display() window.set_needs_display()
} }
@ -279,6 +359,9 @@ impl Compositor for CompositorTask {
fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D<uint>) { fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D<uint>) {
self.chan.send(Paint(layer_buffer_set, new_size)) self.chan.send(Paint(layer_buffer_set, new_size))
} }
fn set_render_state(&self, render_state: RenderState) {
self.chan.send(ChangeRenderState(render_state))
}
} }
/// A function for spawning into the platform's main thread. /// A function for spawning into the platform's main thread.

View file

@ -124,6 +124,8 @@ ul, ol, dl { page-break-before: avoid }
/* Servo additions */ /* Servo additions */
:link { color: blue } :link { color: blue }
script { display: none }
style { display: none }
" "
} }

View file

@ -4,13 +4,14 @@
use compositing::CompositorTask; use compositing::CompositorTask;
use layout::layout_task; use layout::layout_task;
use util::task::spawn_listener;
use core::cell::Cell; use core::cell::Cell;
use core::comm::{Chan, Port, SharedChan}; use core::comm::{Port, SharedChan};
use gfx::opts::Opts; use gfx::opts::Opts;
use gfx::render_task::RenderTask; use gfx::render_task::RenderTask;
use gfx::render_task; use gfx::render_task;
use script::compositor_interface::{CompositorInterface, ReadyState};
use script::engine_interface::{EngineTask, ExitMsg, LoadUrlMsg, Msg};
use script::layout_interface::LayoutTask; use script::layout_interface::LayoutTask;
use script::layout_interface; use script::layout_interface;
use script::script_task::{ExecuteMsg, LoadMsg, ScriptMsg, ScriptTask}; use script::script_task::{ExecuteMsg, LoadMsg, ScriptMsg, ScriptTask};
@ -18,15 +19,7 @@ use script::script_task;
use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient}; use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
use servo_net::resource_task::ResourceTask; use servo_net::resource_task::ResourceTask;
use servo_net::resource_task; use servo_net::resource_task;
use servo_util::time::{ProfilerChan, ProfilerPort, ProfilerTask}; use servo_util::time::{ProfilerChan, ProfilerPort, ProfilerTask, ForcePrintMsg};
use std::net::url::Url;
pub type EngineTask = Chan<Msg>;
pub enum Msg {
LoadUrlMsg(Url),
ExitMsg(Chan<()>),
}
pub struct Engine { pub struct Engine {
request_port: Port<Msg>, request_port: Port<Msg>,
@ -39,6 +32,12 @@ pub struct Engine {
profiler_task: ProfilerTask, profiler_task: ProfilerTask,
} }
impl Drop for Engine {
fn finalize(&self) {
self.profiler_task.chan.send(ForcePrintMsg);
}
}
impl Engine { impl Engine {
pub fn start(compositor: CompositorTask, pub fn start(compositor: CompositorTask,
opts: &Opts, opts: &Opts,
@ -50,31 +49,44 @@ impl Engine {
profiler_chan: ProfilerChan) profiler_chan: ProfilerChan)
-> EngineTask { -> EngineTask {
let (script_port, script_chan) = (Cell(script_port), Cell(script_chan)); let (script_port, script_chan) = (Cell(script_port), Cell(script_chan));
let (engine_port, engine_chan) = comm::stream();
let (engine_port, engine_chan) = (Cell(engine_port), SharedChan::new(engine_chan));
let engine_chan_clone = engine_chan.clone();
let compositor = Cell(compositor);
let profiler_port = Cell(profiler_port); let profiler_port = Cell(profiler_port);
let opts = Cell(copy *opts); let opts = Cell(copy *opts);
do spawn_listener::<Msg> |request| { do task::spawn {
let compositor = compositor.take();
let render_task = RenderTask::new(compositor.clone(), let render_task = RenderTask::new(compositor.clone(),
opts.with_ref(|o| copy *o), opts.with_ref(|o| copy *o),
profiler_chan.clone()); profiler_chan.clone());
let profiler_task = ProfilerTask::new(profiler_port.take(), profiler_chan.clone());
let opts = opts.take(); let opts = opts.take();
let profiler_task = ProfilerTask::new(profiler_port.take(),
profiler_chan.clone(),
opts.profiler_period);
let layout_task = layout_task::create_layout_task(render_task.clone(), let layout_task = layout_task::create_layout_task(render_task.clone(),
image_cache_task.clone(), image_cache_task.clone(),
opts, opts,
profiler_task.chan.clone()); profiler_task.chan.clone());
let compositor_clone = compositor.clone();
let script_task = ScriptTask::new(script_port.take(), let script_task = ScriptTask::new(script_port.take(),
script_chan.take(), script_chan.take(),
engine_chan_clone.clone(),
|msg: ReadyState| {
compositor_clone.set_ready_state(msg)
},
layout_task.clone(), layout_task.clone(),
resource_task.clone(), resource_task.clone(),
image_cache_task.clone()); image_cache_task.clone());
Engine { Engine {
request_port: request, request_port: engine_port.take(),
compositor: compositor.clone(), compositor: compositor.clone(),
render_task: render_task, render_task: render_task,
resource_task: resource_task.clone(), resource_task: resource_task.clone(),
@ -82,8 +94,9 @@ impl Engine {
layout_task: layout_task, layout_task: layout_task,
script_task: script_task, script_task: script_task,
profiler_task: profiler_task, profiler_task: profiler_task,
}.run() }.run();
} }
engine_chan.clone()
} }
fn run(&self) { fn run(&self) {

View file

@ -196,21 +196,20 @@ impl BlockFlowData {
let available_width = remaining_width - model.noncontent_width(); let available_width = remaining_width - model.noncontent_width();
// Top and bottom margins for blocks are 0 if auto. // Top and bottom margins for blocks are 0 if auto.
let margin_top = MaybeAuto::from_margin(style.margin_top()); let margin_top = MaybeAuto::from_margin(style.margin_top(),
let margin_top = margin_top.spec_or_default(Au(0)); remaining_width).spec_or_default(Au(0));
let margin_bottom = MaybeAuto::from_margin(style.margin_bottom()); let margin_bottom = MaybeAuto::from_margin(style.margin_bottom(),
let margin_bottom = margin_bottom.spec_or_default(Au(0)); remaining_width).spec_or_default(Au(0));
let (width, margin_left, margin_right) = let (width, margin_left, margin_right) =
(MaybeAuto::from_width(style.width()), (MaybeAuto::from_width(style.width(), remaining_width),
MaybeAuto::from_margin(style.margin_left()), MaybeAuto::from_margin(style.margin_left(), remaining_width),
MaybeAuto::from_margin(style.margin_right())); MaybeAuto::from_margin(style.margin_right(), remaining_width));
// FIXME(pcwalton): We discard the width here. Is that correct? let (width, margin_left, margin_right) = self.compute_horiz(width,
let (_, margin_left, margin_right) = self.compute_horiz(width, margin_left,
margin_left, margin_right,
margin_right, available_width);
available_width);
model.margin.top = margin_top; model.margin.top = margin_top;
model.margin.right = margin_right; model.margin.right = margin_right;
@ -218,7 +217,7 @@ impl BlockFlowData {
model.margin.left = margin_left; model.margin.left = margin_left;
x_offset = model.offset(); x_offset = model.offset();
remaining_width = remaining_width - model.noncontent_width(); remaining_width = width;
} }
do box.with_mut_base |base| { do box.with_mut_base |base| {
@ -243,10 +242,12 @@ impl BlockFlowData {
pub fn assign_height_block(@mut self, ctx: &LayoutContext) { pub fn assign_height_block(@mut self, ctx: &LayoutContext) {
let mut cur_y = Au(0); let mut cur_y = Au(0);
let mut top_offset = Au(0);
for self.box.each |&box| { for self.box.each |&box| {
do box.with_model |model| { do box.with_model |model| {
cur_y += model.margin.top + model.border.top + model.padding.top; top_offset = model.margin.top + model.border.top + model.padding.top;
cur_y += top_offset;
} }
} }
@ -260,22 +261,26 @@ impl BlockFlowData {
let height = if self.is_root { let height = if self.is_root {
Au::max(ctx.screen_size.size.height, cur_y) Au::max(ctx.screen_size.size.height, cur_y)
} else { } else {
cur_y cur_y - top_offset
}; };
//TODO(eatkinson): compute heights using the 'height' property. let mut noncontent_height = Au(0);
self.common.position.size.height = height;
self.box.map(|&box| { self.box.map(|&box| {
do box.with_mut_base |base| { do box.with_mut_base |base| {
//The associated box is the border box of this flow //The associated box is the border box of this flow
base.position.origin.y = base.model.margin.top; base.position.origin.y = base.model.margin.top;
let pb = base.model.padding.top + base.model.padding.bottom + noncontent_height = base.model.padding.top + base.model.padding.bottom +
base.model.border.top + base.model.border.bottom; base.model.border.top + base.model.border.bottom;
base.position.size.height = height + pb; base.position.size.height = height + noncontent_height;
noncontent_height += base.model.margin.top + base.model.margin.bottom;
} }
}); });
//TODO(eatkinson): compute heights using the 'height' property.
self.common.position.size.height = height + noncontent_height;
} }
pub fn build_display_list_block<E:ExtraDisplayListData>(@mut self, pub fn build_display_list_block<E:ExtraDisplayListData>(@mut self,

View file

@ -627,19 +627,20 @@ pub impl RenderBox {
}, },
GenericRenderBoxClass(_) => { GenericRenderBoxClass(_) => {
// FIXME(pcwalton): This is somewhat of an abuse of the logging system.
debug!("%?", { debug!("%?", {
// Compute the text box bounds and draw a border surrounding them. // Compute the text box bounds and draw a border surrounding them.
do list.with_mut_ref |list| { do list.with_mut_ref |list| {
let border_display_item = ~BorderDisplayItem { let border_display_item = ~BorderDisplayItem {
base: BaseDisplayItem { base: BaseDisplayItem {
bounds: absolute_box_bounds, bounds: absolute_box_bounds,
extra: ExtraDisplayListData::new(*self), extra: ExtraDisplayListData::new(*self),
}, },
width: Au::from_px(1), width: Au::from_px(1),
color: rgb(0, 0, 0).to_gfx_color(), color: rgb(0, 0, 0).to_gfx_color(),
}; };
list.append_item(BorderDisplayItemClass(border_display_item)) list.append_item(BorderDisplayItemClass(border_display_item))
} }
}); });
} }

View file

@ -451,7 +451,7 @@ pub impl LayoutTreeBuilder {
let first_child = do parent_flow.with_base |parent_node| { let first_child = do parent_flow.with_base |parent_node| {
parent_node.first_child parent_node.first_child
}; };
for first_child.each |first_flow| { for first_child.each |&first_flow| {
if first_flow.starts_inline_flow() { if first_flow.starts_inline_flow() {
// FIXME: workaround for rust#6393 // FIXME: workaround for rust#6393
let mut do_remove = false; let mut do_remove = false;
@ -466,7 +466,7 @@ pub impl LayoutTreeBuilder {
} }
} }
if (do_remove) { if (do_remove) {
(*parent_flow).remove_child(*first_flow); (*parent_flow).remove_child(first_flow);
} }
} }
} }
@ -474,7 +474,7 @@ pub impl LayoutTreeBuilder {
let last_child = do parent_flow.with_base |parent_node| { let last_child = do parent_flow.with_base |parent_node| {
parent_node.last_child parent_node.last_child
}; };
for last_child.each |last_flow| { for last_child.each |&last_flow| {
if last_flow.starts_inline_flow() { if last_flow.starts_inline_flow() {
// FIXME: workaround for rust#6393 // FIXME: workaround for rust#6393
let mut do_remove = false; let mut do_remove = false;
@ -489,7 +489,7 @@ pub impl LayoutTreeBuilder {
} }
} }
if (do_remove) { if (do_remove) {
(*parent_flow).remove_child(*last_flow); (*parent_flow).remove_child(last_flow);
} }
} }
} }

View file

@ -38,7 +38,7 @@ use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitMsg,
use script::layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDocumentDamage, Msg}; use script::layout_interface::{LayoutResponse, LayoutTask, MatchSelectorsDocumentDamage, Msg};
use script::layout_interface::{QueryMsg, Reflow, ReflowDocumentDamage, ReflowForDisplay}; use script::layout_interface::{QueryMsg, Reflow, ReflowDocumentDamage, ReflowForDisplay};
use script::layout_interface::{ReflowMsg}; use script::layout_interface::{ReflowMsg};
use script::script_task::{ScriptMsg, SendEventMsg}; use script::script_task::{ReflowCompleteMsg, ScriptMsg, SendEventMsg};
use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
use servo_net::local_image_cache::LocalImageCache; use servo_net::local_image_cache::LocalImageCache;
use servo_util::tree::{TreeNodeRef, TreeUtils}; use servo_util::tree::{TreeNodeRef, TreeUtils};
@ -255,7 +255,11 @@ impl Layout {
debug!("%?", layout_root.dump()); debug!("%?", layout_root.dump());
// Tell script that we're done. // Tell script that we're done.
//
// FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
// either select or a filtered recv() that only looks for messages of a given type.
data.script_join_chan.send(()); data.script_join_chan.send(());
data.script_chan.send(ReflowCompleteMsg);
} }
/// Handles a query from the script task. This is the main routine that DOM functions like /// Handles a query from the script task. This is the main routine that DOM functions like
@ -342,13 +346,14 @@ impl Layout {
flow.build_display_list(&builder, flow.build_display_list(&builder,
&flow.position(), &flow.position(),
display_list); 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), let (x, y) = (Au::from_frac_px(point.x as float),
Au::from_frac_px(point.y as float)); Au::from_frac_px(point.y as float));
let mut resp = Err(()); let mut resp = Err(());
let display_list = &display_list.take().list; let display_list = &display_list.take().list;
// iterate in reverse to ensure we have the most recently painted render box
for display_list.each_reverse |display_item| { for display_list.each_reverse |display_item| {
let bounds = display_item.bounds(); let bounds = display_item.bounds();
// TODO this check should really be performed by a method of DisplayItem
if x <= bounds.origin.x + bounds.size.width && if x <= bounds.origin.x + bounds.size.width &&
bounds.origin.x <= x && bounds.origin.x <= x &&
y < bounds.origin.y + bounds.size.height && y < bounds.origin.y + bounds.size.height &&

View file

@ -37,12 +37,12 @@ pub enum MaybeAuto {
Specified(Au), Specified(Au),
} }
impl MaybeAuto { impl MaybeAuto{
pub fn from_margin(margin: CSSMargin) -> MaybeAuto{ pub fn from_margin(margin: CSSMargin, cb_width: Au) -> MaybeAuto{
match margin { match margin {
CSSMarginAuto => Auto, CSSMarginAuto => Auto,
//FIXME(eatkinson): Compute percents properly //FIXME(eatkinson): Compute percents properly
CSSMarginPercentage(_) => Specified(Au(0)), CSSMarginPercentage(percent) => Specified(cb_width.scale_by(percent/100.0)),
//FIXME(eatkinson): Compute pt and em values properly //FIXME(eatkinson): Compute pt and em values properly
CSSMarginLength(Px(v)) | CSSMarginLength(Px(v)) |
CSSMarginLength(Pt(v)) | CSSMarginLength(Pt(v)) |
@ -50,11 +50,10 @@ impl MaybeAuto {
} }
} }
pub fn from_width(width: CSSWidth) -> MaybeAuto{ pub fn from_width(width: CSSWidth, cb_width: Au) -> MaybeAuto{
match width{ match width{
CSSWidthAuto => Auto, CSSWidthAuto => Auto,
//FIXME(eatkinson): Compute percents properly CSSWidthPercentage(percent) => Specified(cb_width.scale_by(percent/100.0)),
CSSWidthPercentage(_) => Specified(Au(0)),
//FIXME(eatkinson): Compute pt and em values properly //FIXME(eatkinson): Compute pt and em values properly
CSSWidthLength(Px(v)) | CSSWidthLength(Px(v)) |
CSSWidthLength(Pt(v)) | CSSWidthLength(Pt(v)) |
@ -135,7 +134,7 @@ impl BoxModel {
// FIXME(eatkinson): Handle 'em' and 'pt' correctly // FIXME(eatkinson): Handle 'em' and 'pt' correctly
Au::from_frac_px(v) Au::from_frac_px(v)
} }
CSSPaddingPercentage(p) => content_box_width.scale_by(p) CSSPaddingPercentage(p) => content_box_width.scale_by(p/100.0)
} }
} }
} }
@ -165,12 +164,12 @@ impl RenderBox {
let border_width = border.top; let border_width = border.top;
let bounds = Rect { let bounds = Rect {
origin: Point2D { origin: Point2D {
x: abs_bounds.origin.x, x: abs_bounds.origin.x + border_width.scale_by(0.5),
y: abs_bounds.origin.y, y: abs_bounds.origin.y + border_width.scale_by(0.5),
}, },
size: Size2D { size: Size2D {
width: abs_bounds.size.width, width: abs_bounds.size.width - border_width,
height: abs_bounds.size.height height: abs_bounds.size.height - border_width
} }
}; };

View file

@ -7,16 +7,22 @@
/// GLUT is a very old and bare-bones toolkit. However, it has good cross-platform support, at /// 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. /// least on desktops. It is designed for testing Servo without the need of a UI.
use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, ClickCallback}; use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, MouseCallback};
use windowing::{ResizeCallback, ScrollCallback, WindowMethods}; use windowing::{ResizeCallback, ScrollCallback, WindowMethods, WindowMouseEvent, WindowClickEvent};
use windowing::{WindowMouseDownEvent, WindowMouseUpEvent, ZoomCallback};
use alert::{Alert, AlertMethods}; use alert::{Alert, AlertMethods};
use core::cell::Cell;
use core::libc::c_int; use core::libc::c_int;
use geom::point::Point2D; use geom::point::Point2D;
use geom::size::Size2D; use geom::size::Size2D;
use glut::glut::{DOUBLE, WindowHeight, WindowWidth}; use gfx::compositor::{IdleRenderState, RenderState, RenderingRenderState};
use glut::glut::{ACTIVE_CTRL, DOUBLE, HAVE_PRECISE_MOUSE_WHEEL, WindowHeight, WindowWidth};
use glut::glut; use glut::glut;
use glut::machack; use glut::machack;
use script::compositor_interface::{FinishedLoading, Loading, PerformingLayout, ReadyState};
static THROBBER: [char, ..8] = [ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' ];
/// A structure responsible for setting up and tearing down the entire windowing system. /// A structure responsible for setting up and tearing down the entire windowing system.
pub struct Application; pub struct Application;
@ -36,10 +42,18 @@ pub struct Window {
composite_callback: Option<CompositeCallback>, composite_callback: Option<CompositeCallback>,
resize_callback: Option<ResizeCallback>, resize_callback: Option<ResizeCallback>,
load_url_callback: Option<LoadUrlCallback>, load_url_callback: Option<LoadUrlCallback>,
click_callback: Option<ClickCallback>, mouse_callback: Option<MouseCallback>,
scroll_callback: Option<ScrollCallback>, scroll_callback: Option<ScrollCallback>,
zoom_callback: Option<ZoomCallback>,
drag_origin: Point2D<c_int>, drag_origin: Point2D<c_int>,
mouse_down_button: @mut c_int,
mouse_down_point: @mut Point2D<c_int>,
ready_state: ReadyState,
render_state: RenderState,
throbber_frame: u8,
} }
impl WindowMethods<Application> for Window { impl WindowMethods<Application> for Window {
@ -56,10 +70,30 @@ impl WindowMethods<Application> for Window {
composite_callback: None, composite_callback: None,
resize_callback: None, resize_callback: None,
load_url_callback: None, load_url_callback: None,
click_callback: None, mouse_callback: None,
scroll_callback: None, scroll_callback: None,
zoom_callback: None,
drag_origin: Point2D(0, 0), drag_origin: Point2D(0, 0),
mouse_down_button: @mut 0,
mouse_down_point: @mut Point2D(0, 0),
ready_state: FinishedLoading,
render_state: IdleRenderState,
throbber_frame: 0,
};
// Spin the event loop every 50 ms to allow the Rust channels to be polled.
//
// This requirement is pretty much the nail in the coffin for GLUT's usefulness.
//
// FIXME(pcwalton): What a mess.
let register_timer_callback: @mut @fn() = @mut ||{};
*register_timer_callback = || {
glut::timer_func(50, *register_timer_callback);
window.throbber_frame = (window.throbber_frame + 1) % (THROBBER.len() as u8);
window.update_window_title()
}; };
// Register event handlers. // Register event handlers.
@ -79,13 +113,25 @@ impl WindowMethods<Application> for Window {
do glut::keyboard_func |key, _, _| { do glut::keyboard_func |key, _, _| {
window.handle_key(key) window.handle_key(key)
} }
do glut::mouse_func |button, _, x, y| { do glut::mouse_func |button, state, x, y| {
if button < 3 { if button < 3 {
window.handle_click(x, y); window.handle_mouse(button, state, x, y);
} else {
window.handle_scroll(if button == 4 { -30.0 } else { 30.0 });
} }
} }
do glut::mouse_wheel_func |wheel, direction, x, y| {
let delta = if HAVE_PRECISE_MOUSE_WHEEL {
(direction as f32) / 10000.0
} else {
(direction as f32) * 30.0
};
match wheel {
1 => window.handle_scroll(Point2D(delta, 0.0)),
2 => window.handle_zoom(delta),
_ => window.handle_scroll(Point2D(0.0, delta)),
}
}
(*register_timer_callback)();
machack::perform_scroll_wheel_hack(); machack::perform_scroll_wheel_hack();
@ -117,9 +163,9 @@ impl WindowMethods<Application> for Window {
self.load_url_callback = Some(new_load_url_callback) self.load_url_callback = Some(new_load_url_callback)
} }
/// Registers a callback to be run when a click event occurs. /// Registers a callback to be run when a mouse event occurs.
pub fn set_click_callback(&mut self, new_click_callback: ClickCallback) { pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback) {
self.click_callback = Some(new_click_callback) self.mouse_callback = Some(new_mouse_callback)
} }
/// Registers a callback to be run when the user scrolls. /// Registers a callback to be run when the user scrolls.
@ -127,6 +173,11 @@ impl WindowMethods<Application> for Window {
self.scroll_callback = Some(new_scroll_callback) self.scroll_callback = Some(new_scroll_callback)
} }
/// Registers a zoom to be run when the user zooms.
pub fn set_zoom_callback(&mut self, new_zoom_callback: ZoomCallback) {
self.zoom_callback = Some(new_zoom_callback)
}
/// Spins the event loop. /// Spins the event loop.
pub fn check_loop(@mut self) { pub fn check_loop(@mut self) {
glut::check_loop() glut::check_loop()
@ -136,30 +187,110 @@ impl WindowMethods<Application> for Window {
pub fn set_needs_display(@mut self) { pub fn set_needs_display(@mut self) {
glut::post_redisplay() glut::post_redisplay()
} }
/// Sets the ready state.
pub fn set_ready_state(@mut self, ready_state: ReadyState) {
self.ready_state = ready_state;
self.update_window_title()
}
/// Sets the render state.
pub fn set_render_state(@mut self, render_state: RenderState) {
self.render_state = render_state;
self.update_window_title()
}
} }
impl Window { impl Window {
/// Helper function to set the window title in accordance with the ready state.
fn update_window_title(&self) {
let throbber = THROBBER[self.throbber_frame];
match self.ready_state {
Loading => {
glut::set_window_title(self.glut_window, fmt!("%c Loading — Servo", throbber))
}
PerformingLayout => {
glut::set_window_title(self.glut_window,
fmt!("%c Performing Layout — Servo", throbber))
}
FinishedLoading => {
match self.render_state {
RenderingRenderState => {
glut::set_window_title(self.glut_window,
fmt!("%c Rendering — Servo", throbber))
}
IdleRenderState => glut::set_window_title(self.glut_window, "Servo"),
}
}
}
}
/// Helper function to handle keyboard events. /// Helper function to handle keyboard events.
fn handle_key(&self, key: u8) { fn handle_key(&self, key: u8) {
debug!("got key: %d", key as int); debug!("got key: %d", key as int);
if key == 12 { // ^L match key {
self.load_url() 12 => self.load_url(), // Ctrl+L
k if k == ('=' as u8) && (glut::get_modifiers() & ACTIVE_CTRL) != 0 => { // Ctrl++
for self.zoom_callback.each |&callback| {
callback(0.1);
}
}
k if k == 31 && (glut::get_modifiers() & ACTIVE_CTRL) != 0 => { // Ctrl+-
for self.zoom_callback.each |&callback| {
callback(-0.1);
}
}
_ => {}
} }
} }
/// Helper function to handle a click /// Helper function to handle a click
fn handle_click(&self, x: c_int, y: c_int) { fn handle_mouse(&self, button: c_int, state: c_int, x: c_int, y: c_int) {
match self.click_callback { // FIXME(tkuehn): max pixel dist should be based on pixel density
let max_pixel_dist = 10f;
match self.mouse_callback {
None => {} None => {}
Some(callback) => callback(Point2D(x as f32, y as f32)), Some(callback) => {
let event: WindowMouseEvent;
match state {
glut::MOUSE_DOWN => {
event = WindowMouseDownEvent(button as uint, Point2D(x as f32, y as f32));
*self.mouse_down_point = Point2D(x, y);
*self.mouse_down_button = button;
}
glut::MOUSE_UP => {
event = WindowMouseUpEvent(button as uint, Point2D(x as f32, y as f32));
if *self.mouse_down_button == button {
let pixel_dist = *self.mouse_down_point - Point2D(x, y);
let pixel_dist = ((pixel_dist.x * pixel_dist.x +
pixel_dist.y * pixel_dist.y) as float).sqrt();
if pixel_dist < max_pixel_dist {
let click_event = WindowClickEvent(button as uint,
Point2D(x as f32, y as f32));
callback(click_event);
}
}
}
_ => fail!("I cannot recognize the type of mouse action that occured. :-(")
};
callback(event);
}
} }
} }
/// Helper function to handle a scroll. /// Helper function to handle a scroll.
fn handle_scroll(&mut self, delta: f32) { fn handle_scroll(&mut self, delta: Point2D<f32>) {
match self.scroll_callback { match self.scroll_callback {
None => {} None => {}
Some(callback) => callback(Point2D(0.0, delta)), Some(callback) => callback(delta),
}
}
/// Helper function to handle a zoom.
fn handle_zoom(&mut self, magnification: f32) {
match self.zoom_callback {
None => {}
Some(callback) => callback(magnification),
} }
} }

View file

@ -34,7 +34,8 @@ extern mod core_graphics;
extern mod core_text; extern mod core_text;
use compositing::CompositorTask; use compositing::CompositorTask;
use engine::{Engine, LoadUrlMsg}; use engine::Engine;
use script::engine_interface::{ExitMsg, LoadUrlMsg};
use core::comm::SharedChan; use core::comm::SharedChan;
use gfx::opts; use gfx::opts;
@ -121,7 +122,7 @@ fn run(opts: &Opts) {
// Shut the engine down. // Shut the engine down.
debug!("master: Shut down"); debug!("master: Shut down");
let (exit_response_from_engine, exit_chan) = comm::stream(); let (exit_response_from_engine, exit_chan) = comm::stream();
engine_task.send(engine::ExitMsg(exit_chan)); engine_task.send(ExitMsg(exit_chan));
exit_response_from_engine.recv(); exit_response_from_engine.recv();
} }

View file

@ -6,6 +6,14 @@
use geom::point::Point2D; use geom::point::Point2D;
use geom::size::Size2D; use geom::size::Size2D;
use gfx::compositor::RenderState;
use script::compositor_interface::ReadyState;
pub enum WindowMouseEvent {
WindowClickEvent(uint, Point2D<f32>),
WindowMouseDownEvent(uint, Point2D<f32>),
WindowMouseUpEvent(uint, Point2D<f32>),
}
/// Type of the function that is called when the screen is to be redisplayed. /// Type of the function that is called when the screen is to be redisplayed.
pub type CompositeCallback = @fn(); pub type CompositeCallback = @fn();
@ -16,13 +24,15 @@ pub type ResizeCallback = @fn(uint, uint);
/// Type of the function that is called when a new URL is to be loaded. /// Type of the function that is called when a new URL is to be loaded.
pub type LoadUrlCallback = @fn(&str); pub type LoadUrlCallback = @fn(&str);
/// Type of the function that is called when hit testing is to be performed. /// Type of the function that is called when a mouse hit test is to be performed.
/// FIXME this currently does not discriminate between left and right clicks or any modifiers pub type MouseCallback = @fn(WindowMouseEvent);
pub type ClickCallback = @fn(Point2D<f32>);
/// Type of the function that is called when the user scrolls. /// Type of the function that is called when the user scrolls.
pub type ScrollCallback = @fn(Point2D<f32>); pub type ScrollCallback = @fn(Point2D<f32>);
///Type of the function that is called when the user zooms.
pub type ZoomCallback = @fn(f32);
/// Methods for an abstract Application. /// Methods for an abstract Application.
pub trait ApplicationMethods { pub trait ApplicationMethods {
fn new() -> Self; fn new() -> Self;
@ -43,13 +53,19 @@ pub trait WindowMethods<A> {
/// Registers a callback to run when a new URL is to be loaded. /// 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); pub fn set_load_url_callback(&mut self, new_load_url_callback: LoadUrlCallback);
/// Registers a callback to run when the user clicks. /// Registers a callback to run when the user clicks.
pub fn set_click_callback(&mut self, new_click_callback: ClickCallback); pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback);
/// Registers a callback to run when the user scrolls. /// Registers a callback to run when the user scrolls.
pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback); pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback);
/// Registers a callback to run when the user zooms.
pub fn set_zoom_callback(&mut self, new_zoom_callback: ZoomCallback);
/// Spins the event loop. /// Spins the event loop.
pub fn check_loop(@mut self); pub fn check_loop(@mut self);
/// Schedules a redisplay at the next turn of the event loop. /// Schedules a redisplay at the next turn of the event loop.
pub fn set_needs_display(@mut self); pub fn set_needs_display(@mut self);
/// Sets the ready state of the current page.
pub fn set_ready_state(@mut self, ready_state: ReadyState);
/// Sets the render state of the current page.
pub fn set_render_state(@mut self, render_state: RenderState);
} }

View file

@ -5,11 +5,11 @@
use core::comm::{Chan, Port}; use core::comm::{Chan, Port};
pub fn spawn_listener<A: Owned>(f: ~fn(Port<A>)) -> Chan<A> { pub fn spawn_listener<A: Owned>(f: ~fn(Port<A>)) -> Chan<A> {
let (setup_po, setup_ch) = comm::stream(); let (setup_port, setup_chan) = comm::stream();
do task::spawn { do task::spawn {
let (po, ch) = comm::stream(); let (port, chan) = comm::stream();
setup_ch.send(ch); setup_chan.send(chan);
f(po); f(port);
} }
setup_po.recv() setup_port.recv()
} }

View file

@ -0,0 +1,19 @@
/* 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/. */
//! The high-level interface from script to compositor. Using this abstract interface helps reduce
/// coupling between these two components
pub enum ReadyState {
/// Informs the compositor that a page is loading. Used for setting status
Loading,
/// Informs the compositor that a page is performing layout. Used for setting status
PerformingLayout,
/// Informs the compositor that a page is finished loading. Used for setting status
FinishedLoading,
}
pub trait CompositorInterface : Clone {
fn set_ready_state(&self, ReadyState);
}

View file

@ -12,7 +12,9 @@ use geom::point::Point2D;
pub enum Event { pub enum Event {
ResizeEvent(uint, uint, comm::Chan<()>), ResizeEvent(uint, uint, comm::Chan<()>),
ReflowEvent, ReflowEvent,
ClickEvent(Point2D<f32>), ClickEvent(uint, Point2D<f32>),
MouseDownEvent(uint, Point2D<f32>),
MouseUpEvent(uint, Point2D<f32>),
} }
pub struct Event_ { pub struct Event_ {

View file

@ -4,6 +4,7 @@
use dom::bindings::utils::WrapperCache; use dom::bindings::utils::WrapperCache;
use dom::bindings::window; use dom::bindings::window;
use layout_interface::ReflowForScriptQuery; use layout_interface::ReflowForScriptQuery;
use script_task::{ExitMsg, FireTimerMsg, ScriptMsg, ScriptContext}; use script_task::{ExitMsg, FireTimerMsg, ScriptMsg, ScriptContext};

View file

@ -0,0 +1,17 @@
/* 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/. */
//! The high-level interface from script to engine. Using this abstract interface helps reduce
/// coupling between these two components
use core::comm::{Chan, SharedChan};
use std::net::url::Url;
pub type EngineTask = SharedChan<Msg>;
pub enum Msg {
LoadUrlMsg(Url),
ExitMsg(Chan<()>),
}

View file

@ -43,6 +43,7 @@ pub fn spawn_css_parser(provenance: StylesheetProvenance,
fn data_stream(provenance: StylesheetProvenance, resource_task: ResourceTask) -> DataStream { fn data_stream(provenance: StylesheetProvenance, resource_task: ResourceTask) -> DataStream {
match provenance { match provenance {
UrlProvenance(url) => { UrlProvenance(url) => {
debug!("cssparse: loading style sheet at %s", url.to_str());
let (input_port, input_chan) = comm::stream(); let (input_port, input_chan) = comm::stream();
resource_task.send(Load(url, input_chan)); resource_task.send(Load(url, input_chan));
resource_port_to_data_stream(input_port) resource_port_to_data_stream(input_port)

View file

@ -129,11 +129,13 @@ fn js_script_listener(to_parent: Chan<~[~[u8]]>,
buf += data; buf += data;
} }
Done(Ok(*)) => { Done(Ok(*)) => {
result_chan.send(buf); result_chan.send(Some(buf));
break; break;
} }
Done(Err(*)) => { Done(Err(*)) => {
error!("error loading script %s", url.to_str()); error!("error loading script %s", url.to_str());
result_chan.send(None);
break;
} }
} }
} }
@ -146,7 +148,7 @@ fn js_script_listener(to_parent: Chan<~[~[u8]]>,
} }
} }
let js_scripts = vec::map(result_vec, |result_port| result_port.recv()); let js_scripts = vec::filter_map(result_vec, |result_port| result_port.recv());
to_parent.send(js_scripts); to_parent.send(js_scripts);
} }

View file

@ -64,6 +64,8 @@ pub mod html {
pub mod hubbub_html_parser; pub mod hubbub_html_parser;
} }
pub mod compositor_interface;
pub mod engine_interface;
pub mod layout_interface; pub mod layout_interface;
pub mod script_task; pub mod script_task;

View file

@ -5,9 +5,11 @@
/// The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing /// The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing
/// and layout tasks. /// and layout tasks.
use compositor_interface::{ReadyState, Loading, PerformingLayout, FinishedLoading};
use dom::bindings::utils::GlobalStaticData; use dom::bindings::utils::GlobalStaticData;
use dom::document::Document; use dom::document::Document;
use dom::event::{Event, ResizeEvent, ReflowEvent, ClickEvent}; use dom::element::Element;
use dom::event::{Event, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent, MouseUpEvent};
use dom::node::{AbstractNode, ScriptView, define_bindings}; use dom::node::{AbstractNode, ScriptView, define_bindings};
use dom::window::Window; use dom::window::Window;
use layout_interface::{AddStylesheetMsg, DocumentDamage, DocumentDamageLevel, HitTestQuery}; use layout_interface::{AddStylesheetMsg, DocumentDamage, DocumentDamageLevel, HitTestQuery};
@ -15,6 +17,7 @@ use layout_interface::{HitTestResponse, LayoutQuery, LayoutResponse, LayoutTask}
use layout_interface::{MatchSelectorsDocumentDamage, QueryMsg, Reflow, ReflowDocumentDamage}; use layout_interface::{MatchSelectorsDocumentDamage, QueryMsg, Reflow, ReflowDocumentDamage};
use layout_interface::{ReflowForDisplay, ReflowForScriptQuery, ReflowGoal, ReflowMsg}; use layout_interface::{ReflowForDisplay, ReflowForScriptQuery, ReflowGoal, ReflowMsg};
use layout_interface; use layout_interface;
use engine_interface::{EngineTask, LoadUrlMsg};
use core::cast::transmute; use core::cast::transmute;
use core::cell::Cell; use core::cell::Cell;
@ -37,6 +40,7 @@ use js;
use servo_net::image_cache_task::ImageCacheTask; use servo_net::image_cache_task::ImageCacheTask;
use servo_net::resource_task::ResourceTask; use servo_net::resource_task::ResourceTask;
use servo_util::tree::TreeNodeRef; use servo_util::tree::TreeNodeRef;
use servo_util::url::make_url;
use std::net::url::Url; use std::net::url::Url;
use std::net::url; use std::net::url;
@ -50,6 +54,8 @@ pub enum ScriptMsg {
SendEventMsg(Event), SendEventMsg(Event),
/// Fires a JavaScript timeout. /// Fires a JavaScript timeout.
FireTimerMsg(~TimerData), FireTimerMsg(~TimerData),
/// Notifies script that reflow is finished.
ReflowCompleteMsg,
/// Exits the engine. /// Exits the engine.
ExitMsg, ExitMsg,
} }
@ -64,12 +70,15 @@ impl ScriptTask {
/// Creates a new script task. /// Creates a new script task.
pub fn new(script_port: Port<ScriptMsg>, pub fn new(script_port: Port<ScriptMsg>,
script_chan: SharedChan<ScriptMsg>, script_chan: SharedChan<ScriptMsg>,
engine_task: EngineTask,
//FIXME(rust #5192): workaround for lack of working ~Trait
compositor_task: ~fn(ReadyState),
layout_task: LayoutTask, layout_task: LayoutTask,
resource_task: ResourceTask, resource_task: ResourceTask,
image_cache_task: ImageCacheTask) image_cache_task: ImageCacheTask)
-> ScriptTask { -> ScriptTask {
let (script_chan_copy, script_port) = (script_chan.clone(), Cell(script_port)); let (script_chan_copy, script_port) = (script_chan.clone(), Cell(script_port));
let compositor_task = Cell(compositor_task);
// FIXME: rust#6399 // FIXME: rust#6399
let mut the_task = task(); let mut the_task = task();
the_task.sched_mode(SingleThreaded); the_task.sched_mode(SingleThreaded);
@ -77,6 +86,8 @@ impl ScriptTask {
let script_context = ScriptContext::new(layout_task.clone(), let script_context = ScriptContext::new(layout_task.clone(),
script_port.take(), script_port.take(),
script_chan_copy.clone(), script_chan_copy.clone(),
engine_task.clone(),
compositor_task.take(),
resource_task.clone(), resource_task.clone(),
image_cache_task.clone()); image_cache_task.clone());
script_context.start(); script_context.start();
@ -116,6 +127,11 @@ pub struct ScriptContext {
/// messages. /// messages.
script_chan: SharedChan<ScriptMsg>, script_chan: SharedChan<ScriptMsg>,
/// For communicating load url messages to the engine
engine_task: EngineTask,
/// For communicating loading messages to the compositor
compositor_task: ~fn(ReadyState),
/// The JavaScript runtime. /// The JavaScript runtime.
js_runtime: js::rust::rt, js_runtime: js::rust::rt,
/// The JavaScript context. /// The JavaScript context.
@ -167,6 +183,8 @@ impl ScriptContext {
pub fn new(layout_task: LayoutTask, pub fn new(layout_task: LayoutTask,
script_port: Port<ScriptMsg>, script_port: Port<ScriptMsg>,
script_chan: SharedChan<ScriptMsg>, script_chan: SharedChan<ScriptMsg>,
engine_task: EngineTask,
compositor_task: ~fn(ReadyState),
resource_task: ResourceTask, resource_task: ResourceTask,
img_cache_task: ImageCacheTask) img_cache_task: ImageCacheTask)
-> @mut ScriptContext { -> @mut ScriptContext {
@ -190,6 +208,9 @@ impl ScriptContext {
script_port: script_port, script_port: script_port,
script_chan: script_chan, script_chan: script_chan,
engine_task: engine_task,
compositor_task: compositor_task,
js_runtime: js_runtime, js_runtime: js_runtime,
js_context: js_context, js_context: js_context,
js_compartment: compartment, js_compartment: compartment,
@ -243,6 +264,10 @@ impl ScriptContext {
self.handle_fire_timer_msg(timer_data); self.handle_fire_timer_msg(timer_data);
true true
} }
ReflowCompleteMsg => {
self.handle_reflow_complete_msg();
true
}
ExitMsg => { ExitMsg => {
self.handle_exit_msg(); self.handle_exit_msg();
false false
@ -287,6 +312,12 @@ impl ScriptContext {
self.reflow(ReflowForScriptQuery) self.reflow(ReflowForScriptQuery)
} }
/// Handles a notification that reflow completed.
fn handle_reflow_complete_msg(&mut self) {
self.layout_join_port = None;
self.set_ready_state(FinishedLoading)
}
/// Handles a request to exit the script task and shut down layout. /// Handles a request to exit the script task and shut down layout.
fn handle_exit_msg(&mut self) { fn handle_exit_msg(&mut self) {
self.join_layout(); self.join_layout();
@ -297,6 +328,12 @@ impl ScriptContext {
self.layout_task.chan.send(layout_interface::ExitMsg) self.layout_task.chan.send(layout_interface::ExitMsg)
} }
// tells the compositor when loading starts and finishes
// FIXME ~compositor_interface doesn't work right now, which is why this is necessary
fn set_ready_state(&self, msg: ReadyState) {
(self.compositor_task)(msg);
}
/// The entry point to document loading. Defines bindings, sets up the window and document /// The entry point to document loading. Defines bindings, sets up the window and document
/// objects, parses HTML and CSS, and kicks off initial layout. /// objects, parses HTML and CSS, and kicks off initial layout.
fn load(&mut self, url: Url) { fn load(&mut self, url: Url) {
@ -308,6 +345,7 @@ impl ScriptContext {
self.bindings_initialized = true self.bindings_initialized = true
} }
self.set_ready_state(Loading);
// Parse HTML. // Parse HTML.
// //
// Note: We can parse the next document in parallel with any previous documents. // Note: We can parse the next document in parallel with any previous documents.
@ -398,6 +436,9 @@ impl ScriptContext {
// Now, join the layout so that they will see the latest changes we have made. // Now, join the layout so that they will see the latest changes we have made.
self.join_layout(); self.join_layout();
// Tell the user that we're performing layout.
self.set_ready_state(PerformingLayout);
// Layout will let us know when it's done. // Layout will let us know when it's done.
let (join_port, join_chan) = comm::stream(); let (join_port, join_chan) = comm::stream();
self.layout_join_port = Some(join_port); self.layout_join_port = Some(join_port);
@ -410,8 +451,8 @@ impl ScriptContext {
document_root: root_frame.document.root, document_root: root_frame.document.root,
url: copy root_frame.url, url: copy root_frame.url,
goal: goal, goal: goal,
script_chan: self.script_chan.clone(),
window_size: self.window_size, window_size: self.window_size,
script_chan: self.script_chan.clone(),
script_join_chan: join_chan, script_join_chan: join_chan,
damage: replace(&mut self.damage, None).unwrap(), damage: replace(&mut self.damage, None).unwrap(),
}; };
@ -503,7 +544,7 @@ impl ScriptContext {
} }
} }
ClickEvent(point) => { ClickEvent(button, point) => {
debug!("ClickEvent: clicked at %?", point); debug!("ClickEvent: clicked at %?", point);
let root = match self.root_frame { let root = match self.root_frame {
Some(ref frame) => frame.document.root, Some(ref frame) => frame.document.root,
@ -511,14 +552,51 @@ impl ScriptContext {
}; };
match self.query_layout(HitTestQuery(root, point)) { match self.query_layout(HitTestQuery(root, point)) {
Ok(node) => match node { Ok(node) => match node {
HitTestResponse(node) => debug!("clicked on %?", node.debug_str()), HitTestResponse(node) => {
debug!("clicked on %?", node.debug_str());
let mut node = node;
// traverse node generations until a node that is an element is found
while !node.is_element() {
match node.parent_node() {
Some(parent) => {
node = parent;
}
None => break
}
}
if node.is_element() {
do node.with_imm_element |element| {
match element.tag_name {
~"a" => self.load_url_from_element(element),
_ => {}
}
}
}
}
_ => fail!(~"unexpected layout reply") _ => fail!(~"unexpected layout reply")
}, },
Err(()) => { Err(()) => {
println(fmt!("layout query error")); debug!(fmt!("layout query error"));
} }
}; };
} }
MouseDownEvent(*) => {}
MouseUpEvent(*) => {}
}
}
priv fn load_url_from_element(&self, element: &Element) {
// if the node's element is "a," load url from href attr
for element.attrs.each |attr| {
if attr.name == ~"href" {
debug!("clicked on link to %?", attr.value);
let current_url = match self.root_frame {
Some(ref frame) => Some(frame.url.clone()),
None => None
};
let url = make_url(attr.value.clone(), current_url);
self.engine_task.send(LoadUrlMsg(url));
}
} }
} }
} }

View file

@ -6,62 +6,133 @@
use std::time::precise_time_ns; use std::time::precise_time_ns;
use core::cell::Cell; use core::cell::Cell;
use core::comm::{Port, SharedChan}; use core::comm::{Port, SharedChan};
use core::os::getenv; use std::sort::tim_sort;
pub type ProfilerChan = SharedChan<ProfilerMsg>;
pub type ProfilerPort = Port<ProfilerMsg>;
#[deriving(Eq)]
pub enum ProfilerCategory { pub enum ProfilerCategory {
CompositingCategory, CompositingCategory,
LayoutPerformCategory,
LayoutQueryCategory, LayoutQueryCategory,
LayoutPerformCategory,
LayoutAuxInitCategory, LayoutAuxInitCategory,
LayoutSelectorMatchCategory, LayoutSelectorMatchCategory,
LayoutTreeBuilderCategory, LayoutTreeBuilderCategory,
LayoutMainCategory, LayoutMainCategory,
LayoutShapingCategory,
LayoutDispListBuildCategory, LayoutDispListBuildCategory,
GfxRegenAvailableFontsCategory, GfxRegenAvailableFontsCategory,
RenderingPrepBuffCategory, RenderingPrepBuffCategory,
RenderingWaitSubtasksCategory, RenderingWaitSubtasksCategory,
RenderingCategory, RenderingCategory,
// hackish but helps prevent errors when adding new categories
NUM_BUCKETS,
} }
// change this whenever buckets are added/rm'd // FIXME(#5873) this should be initialized by a NUM_BUCKETS cast,
static NUM_BUCKETS: uint = 12; static BUCKETS: uint = 13;
pub type ProfilerChan = SharedChan<(ProfilerCategory, uint)>; pub enum ProfilerMsg {
pub type ProfilerPort = Port<(ProfilerCategory, uint)>; // Normal message used for reporting time
TimeMsg(ProfilerCategory, f64),
// Message used to force print the profiling metrics
ForcePrintMsg,
}
// front-end representation of the profiler used to communicate with the profiler context
pub struct ProfilerTask { pub struct ProfilerTask {
chan: ProfilerChan, chan: ProfilerChan,
} }
// back end of the profiler that handles data aggregation and performance metrics
pub struct ProfilerContext {
port: ProfilerPort,
buckets: ~[(ProfilerCategory, ~[f64])],
verbose: bool,
period: f64,
last_print: f64,
}
impl ProfilerCategory {
// convenience function to not have to cast every time
pub fn num_buckets() -> uint {
NUM_BUCKETS as uint
}
// enumeration of all ProfilerCategory types
// FIXME(tkuehn): this is ugly and error-prone,
// but currently we lack better alternatives without an enum enumeration
priv fn empty_buckets() -> ~[(ProfilerCategory, ~[f64])] {
let mut vec = ~[];
vec.push((CompositingCategory, ~[]));
vec.push((LayoutQueryCategory, ~[]));
vec.push((LayoutPerformCategory, ~[]));
vec.push((LayoutAuxInitCategory, ~[]));
vec.push((LayoutSelectorMatchCategory, ~[]));
vec.push((LayoutTreeBuilderCategory, ~[]));
vec.push((LayoutMainCategory, ~[]));
vec.push((LayoutShapingCategory, ~[]));
vec.push((LayoutDispListBuildCategory, ~[]));
vec.push((GfxRegenAvailableFontsCategory, ~[]));
vec.push((RenderingPrepBuffCategory, ~[]));
vec.push((RenderingWaitSubtasksCategory, ~[]));
vec.push((RenderingCategory, ~[]));
ProfilerCategory::check_order(vec);
vec
}
priv fn check_order(vec: &[(ProfilerCategory, ~[f64])]) {
for vec.each |&(category, _)| {
if category != vec[category as uint].first() {
fail!("Enum category does not match bucket index. This is a bug.");
}
}
}
// some categories are subcategories of LayoutPerformCategory
// and should be printed to indicate this
pub fn format(self) -> ~str {
let padding = match self {
LayoutAuxInitCategory | LayoutSelectorMatchCategory | LayoutTreeBuilderCategory |
LayoutMainCategory | LayoutDispListBuildCategory | LayoutShapingCategory=> " - ",
_ => ""
};
fmt!("%s%?", padding, self)
}
}
impl ProfilerTask { impl ProfilerTask {
pub fn new(prof_port: ProfilerPort, pub fn new(profiler_port: ProfilerPort,
prof_chan: ProfilerChan) profiler_chan: ProfilerChan,
period: Option<f64>)
-> ProfilerTask { -> ProfilerTask {
let prof_port = Cell(prof_port); let profiler_port = Cell(profiler_port);
do spawn { do spawn {
let mut profiler_context = ProfilerContext::new(prof_port.take()); let mut profiler_context = ProfilerContext::new(profiler_port.take(), period);
profiler_context.start(); profiler_context.start();
} }
ProfilerTask { ProfilerTask {
chan: prof_chan chan: profiler_chan
} }
} }
} }
pub struct ProfilerContext {
port: ProfilerPort,
buckets: [~[uint], ..NUM_BUCKETS],
verbose: Option<~str>,
mut last_print: u64,
}
impl ProfilerContext { impl ProfilerContext {
pub fn new(port: ProfilerPort) -> ProfilerContext { pub fn new(port: ProfilerPort, period: Option<f64>) -> ProfilerContext {
let (verbose, period) = match period {
Some(period) => (true, period),
None => (false, 0f64)
};
ProfilerContext { ProfilerContext {
port: port, port: port,
buckets: [~[], ..NUM_BUCKETS], buckets: ProfilerCategory::empty_buckets(),
verbose: getenv("SERVO_PROFILER"), verbose: verbose,
last_print: 0, period: period,
last_print: 0f64,
} }
} }
@ -72,52 +143,64 @@ impl ProfilerContext {
} }
} }
priv fn handle_msg(&mut self, msg: (ProfilerCategory, uint)) { priv fn handle_msg(&mut self, msg: ProfilerMsg) {
let (prof_msg, t) = msg; match msg {
self.buckets[prof_msg as uint].push(t); TimeMsg(category, t) => {
if self.verbose.is_some() { // FIXME(#3874): this should be a let (cat, ref mut bucket) = ...,
let cur_time = precise_time_ns() / 1000000000u64; // not a match
if cur_time - self.last_print > 5 { match self.buckets[category as uint] {
self.last_print = cur_time; (_, ref mut data) => {
let mut i = 0; data.push(t);
for self.buckets.each |bucket| { }
let prof_msg = match i { }
// must be in same order as ProfilerCategory
0 => CompositingCategory, if self.verbose {
1 => LayoutPerformCategory, let cur_time = precise_time_ns() as f64 / 1000000000f64;
2 => LayoutQueryCategory, if cur_time - self.last_print > self.period {
3 => LayoutAuxInitCategory, self.last_print = cur_time;
4 => LayoutSelectorMatchCategory, self.print_buckets();
5 => LayoutTreeBuilderCategory, }
6 => LayoutMainCategory, }
7 => LayoutDispListBuildCategory, }
8 => GfxRegenAvailableFontsCategory, ForcePrintMsg => self.print_buckets(),
9 => RenderingPrepBuffCategory, };
10 => RenderingWaitSubtasksCategory, }
11 => RenderingCategory,
_ => fail!() priv fn print_buckets(&mut self) {
}; println(fmt!("%31s %15s %15s %15s %15s %15s",
io::println(fmt!("%?: %f", prof_msg, "_category (ms)_", "_mean (ms)_", "_median (ms)_",
(bucket.foldl(0 as uint, |a, b| a + *b) as float) / "_min (ms)_", "_max (ms)_", "_bucket size_"));
(bucket.len() as float))); for vec::each_mut(self.buckets) |bucket| {
i += 1; match *bucket {
(category, ref mut data) => {
tim_sort(*data);
let data_len = data.len();
if data_len > 0 {
let (mean, median, min, max) =
(data.foldl(0f64, |a, b| a + *b) / (data_len as f64),
data[data_len / 2],
data.min(),
data.max());
println(fmt!("%-30s: %15.4? %15.4? %15.4? %15.4? %15u",
category.format(), mean, median, min, max, data_len));
}
} }
io::println("");
} }
} }
println("");
} }
} }
pub fn profile<T>(cat: ProfilerCategory,
prof_chan: ProfilerChan, pub fn profile<T>(category: ProfilerCategory,
profiler_chan: ProfilerChan,
callback: &fn() -> T) callback: &fn() -> T)
-> T { -> T {
let start_time = precise_time_ns(); let start_time = precise_time_ns();
let val = callback(); let val = callback();
let end_time = precise_time_ns(); let end_time = precise_time_ns();
let ms = ((end_time - start_time) / 1000000u64) as uint; let ms = ((end_time - start_time) as f64 / 1000000f64);
prof_chan.send((cat, ms)); profiler_chan.send(TimeMsg(category, ms));
return val; return val;
} }
@ -125,9 +208,9 @@ pub fn time<T>(msg: &str, callback: &fn() -> T) -> T{
let start_time = precise_time_ns(); let start_time = precise_time_ns();
let val = callback(); let val = callback();
let end_time = precise_time_ns(); let end_time = precise_time_ns();
let ms = ((end_time - start_time) / 1000000u64) as uint; let ms = ((end_time - start_time) as f64 / 1000000f64);
if ms >= 5 { if ms >= 5f64 {
debug!("%s took %u ms", msg, ms); debug!("%s took %? ms", msg, ms);
} }
return val; return val;
} }

View file

@ -30,8 +30,13 @@ pub fn make_url(str_url: ~str, current_url: Option<Url>) -> Url {
} else { } else {
let current_url = current_url.get(); let current_url = current_url.get();
debug!("make_url: current_url: %?", current_url); debug!("make_url: current_url: %?", current_url);
if current_url.path.is_empty() || current_url.path.ends_with("/") { if str_url.starts_with("//") {
current_url.scheme + "://" + current_url.host + "/" + str_url current_url.scheme + ":" + str_url
} else if current_url.path.is_empty() ||
str_url.starts_with("/") {
current_url.scheme + "://" +
current_url.host + "/" +
str_url.trim_left_chars([ '/' ])
} else { } else {
let mut path = ~[]; let mut path = ~[];
for str::each_split_char(current_url.path, '/') |p| { for str::each_split_char(current_url.path, '/') |p| {

@ -1 +1 @@
Subproject commit ebf1cc8f2e8a39f31da2575981fad966aa7da904 Subproject commit efba52c020945e08f00bebf8ff54ec292e95eced

@ -1 +1 @@
Subproject commit af960707afc666ac38591f1f0545f18a1bb89471 Subproject commit f4ec5a4590a39804ef38faaab4b6749e5c7eb4c6

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>box model smoketest</title>
<style>
body {
margin: 0;
}
#outer {
background-color: red;
margin: 0;
padding: 0;
}
#inner {
margin: 16px;
border: solid green 16px;
padding: 16px;
background-color: blue;
color: white;
}
</style>
</head>
<body><div id=outer><div id=inner>Ten points for Gryffindor</div></div></body>
</html>

View file

@ -12,3 +12,4 @@ for (var i = 0; i < count; i++) {
} }
var stop = new Date(); var stop = new Date();
window.alert((stop - start) / count * 1e6 + " ns/layout"); window.alert((stop - start) / count * 1e6 + " ns/layout");
window.close();