From a53a7f689d527ecb4b116904e0a0bafaea0df956 Mon Sep 17 00:00:00 2001 From: Tim Kuehn Date: Thu, 6 Jun 2013 15:12:02 -0700 Subject: [PATCH 01/25] Add link following and refactor the profiler. --- src/components/gfx/font.rs | 16 +- src/components/gfx/font_context.rs | 12 +- src/components/gfx/opts.rs | 11 +- src/components/gfx/render_task.rs | 6 +- src/components/gfx/text/text_run.rs | 6 +- src/components/main/compositing/mod.rs | 30 ++- src/components/main/css/select_handler.rs | 10 + src/components/main/engine.rs | 15 +- src/components/main/layout/layout_task.rs | 3 +- .../main/platform/common/glut_windowing.rs | 55 +++-- src/components/main/windowing.rs | 13 +- src/components/script/dom/event.rs | 4 +- src/components/script/script_task.rs | 48 ++++- src/components/util/time.rs | 193 +++++++++++++----- src/support/css/rust-css | 2 +- src/support/glut/rust-glut | 2 +- src/support/netsurfcss/rust-netsurfcss | 2 +- src/test/html/test_slam_layout.js | 4 + 18 files changed, 325 insertions(+), 107 deletions(-) diff --git a/src/components/gfx/font.rs b/src/components/gfx/font.rs index 6405c0f34cb..3cc65061105 100644 --- a/src/components/gfx/font.rs +++ b/src/components/gfx/font.rs @@ -18,6 +18,8 @@ use azure::scaled_font::ScaledFont; use azure::azure_hl::{BackendType, ColorPattern}; use geom::{Point2D, Rect, Size2D}; +use servo_util::time::ProfilerChan; + // FontHandle encapsulates access to the platform's font API, // e.g. quartz, FreeType. It provides access to metrics and tables // needed by the text shaper as well as access to the underlying font @@ -210,13 +212,15 @@ pub struct Font { style: UsedFontStyle, metrics: FontMetrics, backend: BackendType, + profiler_chan: ProfilerChan, } pub impl Font { fn new_from_buffer(ctx: &FontContext, buffer: ~[u8], style: &SpecifiedFontStyle, - backend: BackendType) + backend: BackendType, + profiler_chan: ProfilerChan) -> Result<@mut Font, ()> { let handle = FontHandleMethods::new_from_buffer(&ctx.handle, buffer, style); let handle: FontHandle = if handle.is_ok() { @@ -235,11 +239,13 @@ pub impl Font { style: copy *style, metrics: metrics, backend: backend, + profiler_chan: profiler_chan, }); } 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(); @mut Font { @@ -249,11 +255,13 @@ pub impl Font { style: copy *style, metrics: metrics, backend: backend, + profiler_chan: profiler_chan, } } 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? let styled_handle = match handle.clone_with_style(&fctx.handle, style) { @@ -261,7 +269,7 @@ pub impl Font { 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 { diff --git a/src/components/gfx/font_context.rs b/src/components/gfx/font_context.rs index e954d3e07ed..e2d1abf4137 100644 --- a/src/components/gfx/font_context.rs +++ b/src/components/gfx/font_context.rs @@ -40,17 +40,18 @@ pub struct FontContext { handle: FontContextHandle, backend: BackendType, generic_fonts: HashMap<~str,~str>, + profiler_chan: ProfilerChan, } #[allow(non_implicitly_copyable_typarams)] pub impl<'self> FontContext { fn new(backend: BackendType, needs_font_list: bool, - prof_chan: ProfilerChan) + profiler_chan: ProfilerChan) -> FontContext { let handle = FontContextHandle::new(); let font_list = if needs_font_list { - Some(FontList::new(&handle, prof_chan.clone())) } + Some(FontList::new(&handle, profiler_chan.clone())) } else { None }; // TODO: Allow users to specify these. @@ -69,6 +70,7 @@ pub impl<'self> FontContext { handle: handle, backend: backend, generic_fonts: generic_fonts, + profiler_chan: profiler_chan, } } @@ -125,7 +127,8 @@ pub impl<'self> FontContext { for result.each |font_entry| { found = true; // 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); } }; @@ -163,7 +166,8 @@ pub impl<'self> FontContext { Ok(Font::new_from_adopted_handle(self, handle, &desc.style, - self.backend)) + self.backend, + self.profiler_chan.clone())) }) } }; diff --git a/src/components/gfx/opts.rs b/src/components/gfx/opts.rs index d9c6ff1446c..d3193e2de7f 100644 --- a/src/components/gfx/opts.rs +++ b/src/components/gfx/opts.rs @@ -13,6 +13,7 @@ pub struct Opts { render_backend: BackendType, n_render_threads: uint, tile_size: uint, + profiler_period: Option, } #[allow(non_implicitly_copyable_typarams)] @@ -26,13 +27,13 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts { getopts::optopt(~"r"), // rendering backend getopts::optopt(~"s"), // size of tiles getopts::optopt(~"t"), // threads to render with + getopts::optflagopt(~"p"), // profiler flag and output interval ]; let opt_match = match getopts::getopts(args, opts) { result::Ok(m) => { copy m } result::Err(f) => { fail!(getopts::fail_str(copy f)) } }; - let urls = if opt_match.free.is_empty() { fail!(~"servo asks that you provide 1 or more URLs") } else { @@ -68,10 +69,18 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts { None => 1, // FIXME: Number of cores. }; + let profiler_period: Option = + // 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, + }; + Opts { urls: urls, render_backend: render_backend, n_render_threads: n_render_threads, tile_size: tile_size, + profiler_period: profiler_period, } } diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 2beabee6641..457ae4aa5a4 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -18,9 +18,7 @@ use core::task::SingleThreaded; use std::task_pool::TaskPool; use servo_net::util::spawn_listener; -use servo_util::time::ProfilerChan; -use servo_util::time::profile; -use servo_util::time::time; +use servo_util::time::{ProfilerChan, profile}; use servo_util::time; pub enum Msg { @@ -124,7 +122,7 @@ impl Renderer { fn render(&mut self, render_layer: RenderLayer) { debug!("renderer: rendering"); - do time("rendering") { + do profile(time::RenderingCategory, self.profiler_chan.clone()) { let layer_buffer_set = do render_layers(&render_layer, &self.opts, self.profiler_chan.clone()) |render_layer_ref, diff --git a/src/components/gfx/text/text_run.rs b/src/components/gfx/text/text_run.rs index 3778e4353db..905371488dc 100644 --- a/src/components/gfx/text/text_run.rs +++ b/src/components/gfx/text/text_run.rs @@ -6,6 +6,8 @@ use font_context::FontContext; use geometry::Au; use text::glyph::{BreakTypeNormal, GlyphStore}; use font::{Font, FontDescriptor, RunMetrics}; +use servo_util::time; +use servo_util::time::profile; use servo_util::range::Range; /// A text run. @@ -44,7 +46,9 @@ pub impl<'self> TextRun { fn new(font: @mut Font, text: ~str, underline: bool) -> TextRun { let mut glyph_store = GlyphStore::new(str::char_len(text)); 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 { text: text, diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index c0b08be2f15..39d67aaf002 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -5,8 +5,10 @@ use compositing::resize_rate_limiter::ResizeRateLimiter; use platform::{Application, Window}; use script::script_task::{LoadMsg, ScriptMsg, SendEventMsg}; -use windowing::{ApplicationMethods, WindowMethods}; -use script::dom::event::ClickEvent; +use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClickEvent}; +use windowing::{WindowMouseDownEvent, WindowMouseUpEvent}; + +use script::dom::event::{Event, ClickEvent, MouseDownEvent, MouseUpEvent}; use azure::azure_hl::{DataSourceSurface, DrawTarget, SourceSurfaceMethods}; use core::cell::Cell; @@ -233,12 +235,24 @@ fn run_main_loop(port: Port, 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 triggers a mouse event, perform appropriate hit testing + do window.set_mouse_callback |window_mouse_event: WindowMouseEvent| { + let event: Event; + let world_mouse_point = |layer_mouse_point: Point2D| { + layer_mouse_point + *world_offset + }; + 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. diff --git a/src/components/main/css/select_handler.rs b/src/components/main/css/select_handler.rs index 56312ee210b..c04c48f688a 100644 --- a/src/components/main/css/select_handler.rs +++ b/src/components/main/css/select_handler.rs @@ -24,6 +24,16 @@ fn with_node_name(node: AbstractNode, f: &fn(&str) -> R) -> R { } impl SelectHandler> for NodeSelectHandler { + // FIXME(tkuehn): placeholder to get servo to compile + fn node_has_class(&self, node: &AbstractNode, s: &str) -> bool { + true + } + + // FIXME(tkuehn): placeholder to get servo to compile + fn with_node_classes(&self, node: &AbstractNode, f: &fn(Option<&str>) -> R) -> R { + f(None) + } + fn with_node_name(&self, node: &AbstractNode, f: &fn(&str) -> R) -> R { with_node_name(*node, f) } diff --git a/src/components/main/engine.rs b/src/components/main/engine.rs index 1d396c1a70f..6dd245214c8 100644 --- a/src/components/main/engine.rs +++ b/src/components/main/engine.rs @@ -18,7 +18,7 @@ use script::script_task; use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient}; use servo_net::resource_task::ResourceTask; use servo_net::resource_task; -use servo_util::time::{ProfilerChan, ProfilerPort, ProfilerTask}; +use servo_util::time::{profiler_force_print, ProfilerChan, ProfilerPort, ProfilerTask}; use std::net::url::Url; pub type EngineTask = Chan; @@ -39,6 +39,12 @@ pub struct Engine { profiler_task: ProfilerTask, } +impl Drop for Engine { + fn finalize(&self) { + profiler_force_print(self.profiler_task.chan.clone()); + } +} + impl Engine { pub fn start(compositor: CompositorTask, opts: &Opts, @@ -58,9 +64,12 @@ impl Engine { opts.with_ref(|o| copy *o), profiler_chan.clone()); - let profiler_task = ProfilerTask::new(profiler_port.take(), profiler_chan.clone()); - 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(), image_cache_task.clone(), opts, diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index 385014a1172..b96656abbd6 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -342,13 +342,14 @@ impl Layout { 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; + // iterate in reverse to ensure we have the most recently painted render box for display_list.each_reverse |display_item| { 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 && bounds.origin.x <= x && y < bounds.origin.y + bounds.size.height && diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index 07a2f3bf9c2..0af1a0295d8 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -7,8 +7,9 @@ /// 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, ClickCallback}; -use windowing::{ResizeCallback, ScrollCallback, WindowMethods}; +use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, MouseCallback}; +use windowing::{ResizeCallback, ScrollCallback, WindowMethods, WindowMouseEvent, WindowClickEvent}; +use windowing::{WindowMouseDownEvent, WindowMouseUpEvent}; use alert::{Alert, AlertMethods}; use core::libc::c_int; @@ -36,10 +37,12 @@ pub struct Window { composite_callback: Option, resize_callback: Option, load_url_callback: Option, - click_callback: Option, + mouse_callback: Option, scroll_callback: Option, drag_origin: Point2D, + mouse_down_button: @mut c_int, + mouse_down_point: @mut Point2D, } impl WindowMethods for Window { @@ -56,10 +59,12 @@ impl WindowMethods for Window { composite_callback: None, resize_callback: None, load_url_callback: None, - click_callback: None, + mouse_callback: None, scroll_callback: None, drag_origin: Point2D(0, 0), + mouse_down_button: @mut 0, + mouse_down_point: @mut Point2D(0, 0), }; // Register event handlers. @@ -79,9 +84,9 @@ impl WindowMethods for Window { do glut::keyboard_func |key, _, _| { window.handle_key(key) } - do glut::mouse_func |button, _, x, y| { + do glut::mouse_func |button, state, x, y| { 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 }); } @@ -117,9 +122,9 @@ impl WindowMethods for Window { self.load_url_callback = Some(new_load_url_callback) } - /// Registers a callback to be run when a click event occurs. - pub fn set_click_callback(&mut self, new_click_callback: ClickCallback) { - self.click_callback = Some(new_click_callback) + /// Registers a callback to be run when a mouse event occurs. + pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback) { + self.mouse_callback = Some(new_mouse_callback) } /// Registers a callback to be run when the user scrolls. @@ -148,10 +153,36 @@ impl Window { } /// Helper function to handle a click - fn handle_click(&self, x: c_int, y: c_int) { - match self.click_callback { + fn handle_mouse(&self, button: c_int, state: c_int, x: c_int, y: c_int) { + // FIXME(tkuehn): max pixel dist should be based on pixel density + let max_pixel_dist = 10f; + match self.mouse_callback { 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); + } } } diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 9a9cefaf03b..797f1445ceb 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -7,6 +7,12 @@ use geom::point::Point2D; use geom::size::Size2D; +pub enum WindowMouseEvent { + WindowClickEvent(uint, Point2D), + WindowMouseDownEvent(uint, Point2D), + WindowMouseUpEvent(uint, Point2D), +} + /// Type of the function that is called when the screen is to be redisplayed. pub type CompositeCallback = @fn(); @@ -16,9 +22,8 @@ pub type ResizeCallback = @fn(uint, uint); /// Type of the function that is called when a new URL is to be loaded. pub type LoadUrlCallback = @fn(&str); -/// Type of the function that is called when hit testing is to be performed. -/// FIXME this currently does not discriminate between left and right clicks or any modifiers -pub type ClickCallback = @fn(Point2D); +/// Type of the function that is called when a mouse hit test is to be performed. +pub type MouseCallback = @fn(WindowMouseEvent); /// Type of the function that is called when the user scrolls. pub type ScrollCallback = @fn(Point2D); @@ -43,7 +48,7 @@ pub trait WindowMethods { /// 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); + pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback); /// Registers a callback to run when the user scrolls. pub fn set_scroll_callback(&mut self, new_scroll_callback: ScrollCallback); diff --git a/src/components/script/dom/event.rs b/src/components/script/dom/event.rs index a0ef39d8fca..6c11bcd3411 100644 --- a/src/components/script/dom/event.rs +++ b/src/components/script/dom/event.rs @@ -12,7 +12,9 @@ use geom::point::Point2D; pub enum Event { ResizeEvent(uint, uint, comm::Chan<()>), ReflowEvent, - ClickEvent(Point2D), + ClickEvent(uint, Point2D), + MouseDownEvent(uint, Point2D), + MouseUpEvent(uint, Point2D), } pub struct Event_ { diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 66a4e9e4727..69a588667c2 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -7,7 +7,8 @@ use dom::bindings::utils::GlobalStaticData; 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::window::Window; use layout_interface::{AddStylesheetMsg, DocumentDamage, DocumentDamageLevel, HitTestQuery}; @@ -37,7 +38,7 @@ use js; use servo_net::image_cache_task::ImageCacheTask; use servo_net::resource_task::ResourceTask; use servo_util::tree::TreeNodeRef; -use std::net::url::Url; +use std::net::url::{Url, from_str}; use std::net::url; /// Messages used to control the script task. @@ -503,7 +504,7 @@ impl ScriptContext { } } - ClickEvent(point) => { + ClickEvent(button, point) => { debug!("ClickEvent: clicked at %?", point); let root = match self.root_frame { Some(ref frame) => frame.document.root, @@ -511,14 +512,51 @@ impl ScriptContext { }; match self.query_layout(HitTestQuery(root, point)) { 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") }, 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 url = from_str(attr.value); + match url { + Ok(url) => self.script_chan.send(LoadMsg(url)), + Err(msg) => debug!(msg) + }; + break; + } } } } diff --git a/src/components/util/time.rs b/src/components/util/time.rs index 8bb698981f0..8d2aceb8abd 100644 --- a/src/components/util/time.rs +++ b/src/components/util/time.rs @@ -6,62 +6,129 @@ use std::time::precise_time_ns; use core::cell::Cell; use core::comm::{Port, SharedChan}; -use core::os::getenv; +use std::sort::tim_sort; +#[deriving(Eq)] pub enum ProfilerCategory { CompositingCategory, - LayoutPerformCategory, LayoutQueryCategory, + LayoutPerformCategory, LayoutAuxInitCategory, LayoutSelectorMatchCategory, LayoutTreeBuilderCategory, LayoutMainCategory, + LayoutShapingCategory, LayoutDispListBuildCategory, GfxRegenAvailableFontsCategory, RenderingPrepBuffCategory, RenderingWaitSubtasksCategory, RenderingCategory, + // hackish but helps prevent errors when adding new categories + NUM_BUCKETS, } -// change this whenever buckets are added/rm'd -static NUM_BUCKETS: uint = 12; -pub type ProfilerChan = SharedChan<(ProfilerCategory, uint)>; -pub type ProfilerPort = Port<(ProfilerCategory, uint)>; +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) + } +} +// FIXME(#5873) this should be initialized by a NUM_BUCKETS cast, +static BUCKETS: uint = 13; + +pub enum ProfilerMsg { + // Normal message used for reporting time + TimeMsg(ProfilerCategory, f64), + // Message used to force print the profiling metrics + ForcePrintMsg, +} +pub type ProfilerChan = SharedChan; +pub type ProfilerPort = Port; pub struct ProfilerTask { chan: ProfilerChan, } impl ProfilerTask { - pub fn new(prof_port: ProfilerPort, - prof_chan: ProfilerChan) + pub fn new(profiler_port: ProfilerPort, + profiler_chan: ProfilerChan, + period: Option) -> ProfilerTask { - let prof_port = Cell(prof_port); + let profiler_port = Cell(profiler_port); do spawn { - let mut profiler_context = ProfilerContext::new(prof_port.take()); + let mut profiler_context = ProfilerContext::new(profiler_port.take(), period); profiler_context.start(); } ProfilerTask { - chan: prof_chan + chan: profiler_chan } } } pub struct ProfilerContext { port: ProfilerPort, - buckets: [~[uint], ..NUM_BUCKETS], - verbose: Option<~str>, - mut last_print: u64, + buckets: ~[(ProfilerCategory, ~[f64])], + verbose: bool, + period: f64, + mut last_print: f64, } impl ProfilerContext { - pub fn new(port: ProfilerPort) -> ProfilerContext { + pub fn new(port: ProfilerPort, period: Option) -> ProfilerContext { + let (verbose, period) = match period { + Some(period) => (true, period), + None => (false, 0f64) + }; ProfilerContext { port: port, - buckets: [~[], ..NUM_BUCKETS], - verbose: getenv("SERVO_PROFILER"), - last_print: 0, + buckets: ProfilerCategory::empty_buckets(), + verbose: verbose, + period: period, + last_print: 0f64, } } @@ -72,62 +139,76 @@ impl ProfilerContext { } } - priv fn handle_msg(&mut self, msg: (ProfilerCategory, uint)) { - let (prof_msg, t) = msg; - self.buckets[prof_msg as uint].push(t); - if self.verbose.is_some() { - let cur_time = precise_time_ns() / 1000000000u64; - if cur_time - self.last_print > 5 { - self.last_print = cur_time; - let mut i = 0; - for self.buckets.each |bucket| { - let prof_msg = match i { - // must be in same order as ProfilerCategory - 0 => CompositingCategory, - 1 => LayoutPerformCategory, - 2 => LayoutQueryCategory, - 3 => LayoutAuxInitCategory, - 4 => LayoutSelectorMatchCategory, - 5 => LayoutTreeBuilderCategory, - 6 => LayoutMainCategory, - 7 => LayoutDispListBuildCategory, - 8 => GfxRegenAvailableFontsCategory, - 9 => RenderingPrepBuffCategory, - 10 => RenderingWaitSubtasksCategory, - 11 => RenderingCategory, - _ => fail!() - }; - io::println(fmt!("%?: %f", prof_msg, - (bucket.foldl(0 as uint, |a, b| a + *b) as float) / - (bucket.len() as float))); - i += 1; + priv fn handle_msg(&mut self, msg: ProfilerMsg) { + match msg { + TimeMsg(category, t) => { + // FIXME(#3874): this should be a let (cat, ref mut bucket) = ..., + // not a match + match self.buckets[category as uint] { + (_, ref mut data) => { + data.push(t); + tim_sort(*data); + } } - io::println(""); + + if self.verbose { + let cur_time = precise_time_ns() as f64 / 1000000000f64; + if cur_time - self.last_print > self.period { + self.last_print = cur_time; + self.print_buckets(); + } + } + } + ForcePrintMsg => self.print_buckets(), + }; + } + + priv fn print_buckets(&mut self) { + println(fmt!("%31s %15s %15s %15s %15s %15s", + "_category (ms)_", "_mean (ms)_", "_median (ms)_", + "_min (ms)_", "_max (ms)_", "_bucket size_")); + for self.buckets.each |bucket| { + let &(category, data) = bucket; + 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)); } } - + println(""); } } -pub fn profile(cat: ProfilerCategory, - prof_chan: ProfilerChan, + +pub fn profile(category: ProfilerCategory, + profiler_chan: ProfilerChan, callback: &fn() -> T) -> T { let start_time = precise_time_ns(); let val = callback(); let end_time = precise_time_ns(); - let ms = ((end_time - start_time) / 1000000u64) as uint; - prof_chan.send((cat, ms)); + let ms = ((end_time - start_time) as f64 / 1000000f64); + profiler_chan.send(TimeMsg(category, ms)); return val; } + +pub fn profiler_force_print(profiler_chan: ProfilerChan) { + profiler_chan.send(ForcePrintMsg); +} + pub fn time(msg: &str, callback: &fn() -> T) -> T{ let start_time = precise_time_ns(); let val = callback(); let end_time = precise_time_ns(); - let ms = ((end_time - start_time) / 1000000u64) as uint; - if ms >= 5 { - debug!("%s took %u ms", msg, ms); + let ms = ((end_time - start_time) as f64 / 1000000f64); + if ms >= 5f64 { + debug!("%s took %? ms", msg, ms); } return val; } diff --git a/src/support/css/rust-css b/src/support/css/rust-css index 865f5391143..09d2db847c1 160000 --- a/src/support/css/rust-css +++ b/src/support/css/rust-css @@ -1 +1 @@ -Subproject commit 865f539114383a021822583801e8362faf916699 +Subproject commit 09d2db847c11bcab7f1832d5daf5947a7c1384ee diff --git a/src/support/glut/rust-glut b/src/support/glut/rust-glut index 453bf81e021..7db24a19d25 160000 --- a/src/support/glut/rust-glut +++ b/src/support/glut/rust-glut @@ -1 +1 @@ -Subproject commit 453bf81e021008f5eba29b135f07f4529e6c8b2e +Subproject commit 7db24a19d25fbedca2898381ae0b13b723c14135 diff --git a/src/support/netsurfcss/rust-netsurfcss b/src/support/netsurfcss/rust-netsurfcss index 325cd5197ed..8fc7400ed33 160000 --- a/src/support/netsurfcss/rust-netsurfcss +++ b/src/support/netsurfcss/rust-netsurfcss @@ -1 +1 @@ -Subproject commit 325cd5197ed953f5c7c9317111b20ec1599eaffe +Subproject commit 8fc7400ed332c3d9edf358c2a18dd047ebde09a6 diff --git a/src/test/html/test_slam_layout.js b/src/test/html/test_slam_layout.js index 5d1726b904e..b42fba50641 100644 --- a/src/test/html/test_slam_layout.js +++ b/src/test/html/test_slam_layout.js @@ -12,3 +12,7 @@ for (var i = 0; i < count; i++) { } var stop = new Date(); window.alert((stop - start) / count * 1e6 + " ns/layout"); +<<<<<<< HEAD +======= +window.close(); +>>>>>>> 0560988... Add link following and refactor the profiler. From 9085b882c63c866c3bf3d532eb07f55b18e53f24 Mon Sep 17 00:00:00 2001 From: eschweic Date: Thu, 6 Jun 2013 16:10:19 -0700 Subject: [PATCH 02/25] Implement scrolling and better zooming --- src/components/main/compositing/mod.rs | 62 +++++++++++++++++-- .../main/platform/common/glut_windowing.rs | 29 +++++++-- src/components/main/windowing.rs | 5 ++ 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 39d67aaf002..954b13e8014 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -128,6 +128,9 @@ fn run_main_loop(port: Port, let page_size = @mut Size2D(0f32, 0f32); let window_size = @mut Size2D(800, 600); + // Keeps track of the current zoom factor + let world_zoom = @mut 1f32; + let check_for_messages: @fn() = || { // Periodically check if the script task responded to our last resize event resize_rate_limiter.check_resize_response(); @@ -262,16 +265,65 @@ fn run_main_loop(port: Port, *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); + 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 - 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); debug!("compositor: scrolled to %?", *world_offset); - root_layer.common.set_transform(identity().translate(-world_offset.x, - -world_offset.y, - 0.0)); + let mut scroll_transform = identity(); + + 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 |delta| { + let zoom_const = 0.01; + let old_world_zoom = *world_zoom; + + // Determine zoom amount + *world_zoom = (*world_zoom + delta.y * zoom_const).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() } diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index 0af1a0295d8..d301dca365d 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -9,13 +9,13 @@ use windowing::{ApplicationMethods, CompositeCallback, LoadUrlCallback, MouseCallback}; use windowing::{ResizeCallback, ScrollCallback, WindowMethods, WindowMouseEvent, WindowClickEvent}; -use windowing::{WindowMouseDownEvent, WindowMouseUpEvent}; +use windowing::{WindowMouseDownEvent, WindowMouseUpEvent, ZoomCallback}; use alert::{Alert, AlertMethods}; use core::libc::c_int; use geom::point::Point2D; use geom::size::Size2D; -use glut::glut::{DOUBLE, WindowHeight, WindowWidth}; +use glut::glut::{ACTIVE_CTRL, DOUBLE, WindowHeight, WindowWidth}; use glut::glut; use glut::machack; @@ -39,8 +39,10 @@ pub struct Window { load_url_callback: Option, mouse_callback: Option, scroll_callback: Option, + zoom_callback: Option, drag_origin: Point2D, + mouse_down_button: @mut c_int, mouse_down_point: @mut Point2D, } @@ -61,12 +63,15 @@ impl WindowMethods for Window { load_url_callback: None, mouse_callback: None, scroll_callback: None, + zoom_callback: None, drag_origin: Point2D(0, 0), + mouse_down_button: @mut 0, mouse_down_point: @mut Point2D(0, 0), }; + // Register event handlers. do glut::reshape_func(window.glut_window) |width, height| { match window.resize_callback { @@ -132,6 +137,11 @@ impl WindowMethods for Window { 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. pub fn check_loop(@mut self) { glut::check_loop() @@ -147,8 +157,19 @@ impl Window { /// Helper function to handle keyboard events. fn handle_key(&self, key: u8) { debug!("got key: %d", key as int); - if key == 12 { // ^L - self.load_url() + match key { + 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(Point2D(0.0, 20.0)); + } + } + k if k == 31 && (glut::get_modifiers() & ACTIVE_CTRL) != 0 => { // Ctrl+- + for self.zoom_callback.each |&callback| { + callback(Point2D(0.0, -20.0)); + } + } + _ => {} } } diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 797f1445ceb..d3dd7b1bf0b 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -28,6 +28,9 @@ pub type MouseCallback = @fn(WindowMouseEvent); /// Type of the function that is called when the user scrolls. pub type ScrollCallback = @fn(Point2D); +///Type of the function that is called when the user zooms. +pub type ZoomCallback = @fn(Point2D); + /// Methods for an abstract Application. pub trait ApplicationMethods { fn new() -> Self; @@ -51,6 +54,8 @@ pub trait WindowMethods { pub fn set_mouse_callback(&mut self, new_mouse_callback: MouseCallback); /// Registers a callback to run when the user scrolls. 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. pub fn check_loop(@mut self); From abe6a06cbf7faca68512d3fc485f762aed91976e Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 6 Jun 2013 16:48:49 -0700 Subject: [PATCH 03/25] Update rust-layers for GL_LINEAR zooming --- src/support/layers/rust-layers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/support/layers/rust-layers b/src/support/layers/rust-layers index af960707afc..f4ec5a4590a 160000 --- a/src/support/layers/rust-layers +++ b/src/support/layers/rust-layers @@ -1 +1 @@ -Subproject commit af960707afc666ac38591f1f0545f18a1bb89471 +Subproject commit f4ec5a4590a39804ef38faaab4b6749e5c7eb4c6 From bf4df245215b1ffe90fab9e7b4781098db708c78 Mon Sep 17 00:00:00 2001 From: Tim Kuehn Date: Thu, 6 Jun 2013 15:16:05 -0700 Subject: [PATCH 04/25] Provide an interface to the engine for the script task --- src/components/main/engine.rs | 27 +++++------- src/components/main/servo.rc | 5 ++- src/components/net/util.rs | 10 ++--- src/components/script/engine_interface.rs | 17 ++++++++ src/components/script/script.rc | 1 + src/components/script/script_task.rs | 11 ++++- src/components/util/time.rs | 53 +++++++++++------------ 7 files changed, 74 insertions(+), 50 deletions(-) create mode 100644 src/components/script/engine_interface.rs diff --git a/src/components/main/engine.rs b/src/components/main/engine.rs index 6dd245214c8..6458876c8b6 100644 --- a/src/components/main/engine.rs +++ b/src/components/main/engine.rs @@ -4,13 +4,13 @@ use compositing::CompositorTask; use layout::layout_task; -use util::task::spawn_listener; use core::cell::Cell; -use core::comm::{Chan, Port, SharedChan}; +use core::comm::{Port, SharedChan}; use gfx::opts::Opts; use gfx::render_task::RenderTask; use gfx::render_task; +use script::engine_interface::{EngineTask, ExitMsg, LoadUrlMsg, Msg}; use script::layout_interface::LayoutTask; use script::layout_interface; use script::script_task::{ExecuteMsg, LoadMsg, ScriptMsg, ScriptTask}; @@ -18,15 +18,7 @@ use script::script_task; use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient}; use servo_net::resource_task::ResourceTask; use servo_net::resource_task; -use servo_util::time::{profiler_force_print, ProfilerChan, ProfilerPort, ProfilerTask}; -use std::net::url::Url; - -pub type EngineTask = Chan; - -pub enum Msg { - LoadUrlMsg(Url), - ExitMsg(Chan<()>), -} +use servo_util::time::{ProfilerChan, ProfilerPort, ProfilerTask, ForcePrintMsg}; pub struct Engine { request_port: Port, @@ -41,7 +33,7 @@ pub struct Engine { impl Drop for Engine { fn finalize(&self) { - profiler_force_print(self.profiler_task.chan.clone()); + self.profiler_task.chan.send(ForcePrintMsg); } } @@ -56,10 +48,13 @@ impl Engine { profiler_chan: ProfilerChan) -> EngineTask { let (script_port, script_chan) = (Cell(script_port), Cell(script_chan)); + let (request_port, request_chan) = comm::stream(); + let (request_port, request_chan) = (Cell(request_port), SharedChan::new(request_chan)); + let request_chan_clone = request_chan.clone(); let profiler_port = Cell(profiler_port); let opts = Cell(copy *opts); - do spawn_listener:: |request| { + do task::spawn { let render_task = RenderTask::new(compositor.clone(), opts.with_ref(|o| copy *o), profiler_chan.clone()); @@ -77,13 +72,14 @@ impl Engine { let script_task = ScriptTask::new(script_port.take(), script_chan.take(), + request_chan_clone.clone(), layout_task.clone(), resource_task.clone(), image_cache_task.clone()); Engine { - request_port: request, + request_port: request_port.take(), compositor: compositor.clone(), render_task: render_task, resource_task: resource_task.clone(), @@ -91,8 +87,9 @@ impl Engine { layout_task: layout_task, script_task: script_task, profiler_task: profiler_task, - }.run() + }.run(); } + request_chan.clone() } fn run(&self) { diff --git a/src/components/main/servo.rc b/src/components/main/servo.rc index 50a599a0634..b092d4b30a6 100755 --- a/src/components/main/servo.rc +++ b/src/components/main/servo.rc @@ -34,7 +34,8 @@ extern mod core_graphics; extern mod core_text; use compositing::CompositorTask; -use engine::{Engine, LoadUrlMsg}; +use engine::Engine; +use script::engine_interface::{ExitMsg, LoadUrlMsg}; use core::comm::SharedChan; use gfx::opts; @@ -121,7 +122,7 @@ fn run(opts: &Opts) { // Shut the engine down. debug!("master: Shut down"); 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(); } diff --git a/src/components/net/util.rs b/src/components/net/util.rs index ec1aea92e86..cd8a177a7ef 100644 --- a/src/components/net/util.rs +++ b/src/components/net/util.rs @@ -5,11 +5,11 @@ use core::comm::{Chan, Port}; pub fn spawn_listener(f: ~fn(Port)) -> Chan { - let (setup_po, setup_ch) = comm::stream(); + let (setup_port, setup_chan) = comm::stream(); do task::spawn { - let (po, ch) = comm::stream(); - setup_ch.send(ch); - f(po); + let (port, chan) = comm::stream(); + setup_chan.send(chan); + f(port); } - setup_po.recv() + setup_port.recv() } diff --git a/src/components/script/engine_interface.rs b/src/components/script/engine_interface.rs new file mode 100644 index 00000000000..2705ebdb137 --- /dev/null +++ b/src/components/script/engine_interface.rs @@ -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; + +pub enum Msg { + LoadUrlMsg(Url), + ExitMsg(Chan<()>), +} + diff --git a/src/components/script/script.rc b/src/components/script/script.rc index 0c40fc8aab3..dc303783472 100644 --- a/src/components/script/script.rc +++ b/src/components/script/script.rc @@ -65,5 +65,6 @@ pub mod html { } pub mod layout_interface; +pub mod engine_interface; pub mod script_task; diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 69a588667c2..716440dfffe 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -16,6 +16,7 @@ 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 engine_interface::{EngineTask, LoadUrlMsg}; use core::cast::transmute; use core::cell::Cell; @@ -65,6 +66,7 @@ impl ScriptTask { /// Creates a new script task. pub fn new(script_port: Port, script_chan: SharedChan, + engine_task: EngineTask, layout_task: LayoutTask, resource_task: ResourceTask, image_cache_task: ImageCacheTask) @@ -78,6 +80,7 @@ impl ScriptTask { let script_context = ScriptContext::new(layout_task.clone(), script_port.take(), script_chan_copy.clone(), + engine_task.clone(), resource_task.clone(), image_cache_task.clone()); script_context.start(); @@ -117,6 +120,9 @@ pub struct ScriptContext { /// messages. script_chan: SharedChan, + /// For communicating load url messages to the engine + engine_task: EngineTask, + /// The JavaScript runtime. js_runtime: js::rust::rt, /// The JavaScript context. @@ -168,6 +174,7 @@ impl ScriptContext { pub fn new(layout_task: LayoutTask, script_port: Port, script_chan: SharedChan, + engine_task: EngineTask, resource_task: ResourceTask, img_cache_task: ImageCacheTask) -> @mut ScriptContext { @@ -191,6 +198,8 @@ impl ScriptContext { script_port: script_port, script_chan: script_chan, + engine_task: engine_task, + js_runtime: js_runtime, js_context: js_context, js_compartment: compartment, @@ -552,7 +561,7 @@ impl ScriptContext { debug!("clicked on link to %?", attr.value); let url = from_str(attr.value); match url { - Ok(url) => self.script_chan.send(LoadMsg(url)), + Ok(url) => self.engine_task.send(LoadUrlMsg(url)), Err(msg) => debug!(msg) }; break; diff --git a/src/components/util/time.rs b/src/components/util/time.rs index 8d2aceb8abd..c986676ec72 100644 --- a/src/components/util/time.rs +++ b/src/components/util/time.rs @@ -8,6 +8,9 @@ use core::cell::Cell; use core::comm::{Port, SharedChan}; use std::sort::tim_sort; +pub type ProfilerChan = SharedChan; +pub type ProfilerPort = Port; + #[deriving(Eq)] pub enum ProfilerCategory { CompositingCategory, @@ -26,6 +29,29 @@ pub enum ProfilerCategory { // hackish but helps prevent errors when adding new categories NUM_BUCKETS, } +// FIXME(#5873) this should be initialized by a NUM_BUCKETS cast, +static BUCKETS: uint = 13; + +pub enum ProfilerMsg { + // 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 { + 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 { @@ -76,20 +102,6 @@ impl ProfilerCategory { fmt!("%s%?", padding, self) } } -// FIXME(#5873) this should be initialized by a NUM_BUCKETS cast, -static BUCKETS: uint = 13; - -pub enum ProfilerMsg { - // Normal message used for reporting time - TimeMsg(ProfilerCategory, f64), - // Message used to force print the profiling metrics - ForcePrintMsg, -} -pub type ProfilerChan = SharedChan; -pub type ProfilerPort = Port; -pub struct ProfilerTask { - chan: ProfilerChan, -} impl ProfilerTask { pub fn new(profiler_port: ProfilerPort, @@ -109,14 +121,6 @@ impl ProfilerTask { } } -pub struct ProfilerContext { - port: ProfilerPort, - buckets: ~[(ProfilerCategory, ~[f64])], - verbose: bool, - period: f64, - mut last_print: f64, -} - impl ProfilerContext { pub fn new(port: ProfilerPort, period: Option) -> ProfilerContext { let (verbose, period) = match period { @@ -197,11 +201,6 @@ pub fn profile(category: ProfilerCategory, return val; } - -pub fn profiler_force_print(profiler_chan: ProfilerChan) { - profiler_chan.send(ForcePrintMsg); -} - pub fn time(msg: &str, callback: &fn() -> T) -> T{ let start_time = precise_time_ns(); let val = callback(); From ff1178f7ca6cf6ec89b79b38f0dc3bb226462841 Mon Sep 17 00:00:00 2001 From: Tim Kuehn Date: Thu, 6 Jun 2013 16:09:23 -0700 Subject: [PATCH 05/25] handle relative url's when clicking --- src/components/script/script_task.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 716440dfffe..0482febe570 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -39,7 +39,8 @@ use js; use servo_net::image_cache_task::ImageCacheTask; use servo_net::resource_task::ResourceTask; use servo_util::tree::TreeNodeRef; -use std::net::url::{Url, from_str}; +use servo_util::url::make_url; +use std::net::url::Url; use std::net::url; /// Messages used to control the script task. @@ -559,12 +560,12 @@ impl ScriptContext { for element.attrs.each |attr| { if attr.name == ~"href" { debug!("clicked on link to %?", attr.value); - let url = from_str(attr.value); - match url { - Ok(url) => self.engine_task.send(LoadUrlMsg(url)), - Err(msg) => debug!(msg) + let current_url = match self.root_frame { + Some(ref frame) => Some(frame.url.clone()), + None => None }; - break; + let url = make_url(attr.value.clone(), current_url); + self.engine_task.send(LoadUrlMsg(url)); } } } From 0bbf2fc30a872df42bfbd65c51df5b7f36860f81 Mon Sep 17 00:00:00 2001 From: Eric Atkinson Date: Fri, 7 Jun 2013 22:02:44 -0700 Subject: [PATCH 06/25] Refactor flow tree construction and actually use display property. --- src/components/main/layout/box.rs | 23 ++++++++++--------- src/components/main/layout/box_builder.rs | 8 +++---- .../main/platform/common/glut_windowing.rs | 15 +++++++++--- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/components/main/layout/box.rs b/src/components/main/layout/box.rs index 78934a937b4..48b5ede54c8 100644 --- a/src/components/main/layout/box.rs +++ b/src/components/main/layout/box.rs @@ -627,19 +627,20 @@ pub impl RenderBox { }, GenericRenderBoxClass(_) => { + // FIXME(pcwalton): This is somewhat of an abuse of the logging system. debug!("%?", { - // Compute the text box bounds and draw a border surrounding them. - do list.with_mut_ref |list| { - let border_display_item = ~BorderDisplayItem { - base: BaseDisplayItem { - bounds: absolute_box_bounds, - extra: ExtraDisplayListData::new(*self), - }, - width: Au::from_px(1), - color: rgb(0, 0, 0).to_gfx_color(), - }; + // Compute the text box bounds and draw a border surrounding them. + do list.with_mut_ref |list| { + let border_display_item = ~BorderDisplayItem { + base: BaseDisplayItem { + bounds: absolute_box_bounds, + extra: ExtraDisplayListData::new(*self), + }, + width: Au::from_px(1), + color: rgb(0, 0, 0).to_gfx_color(), + }; list.append_item(BorderDisplayItemClass(border_display_item)) - } + } }); } diff --git a/src/components/main/layout/box_builder.rs b/src/components/main/layout/box_builder.rs index 99153d51ee4..b446f8a7172 100644 --- a/src/components/main/layout/box_builder.rs +++ b/src/components/main/layout/box_builder.rs @@ -451,7 +451,7 @@ pub impl LayoutTreeBuilder { let first_child = do parent_flow.with_base |parent_node| { parent_node.first_child }; - for first_child.each |first_flow| { + for first_child.each |&first_flow| { if first_flow.starts_inline_flow() { // FIXME: workaround for rust#6393 let mut do_remove = false; @@ -466,7 +466,7 @@ pub impl LayoutTreeBuilder { } } 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| { parent_node.last_child }; - for last_child.each |last_flow| { + for last_child.each |&last_flow| { if last_flow.starts_inline_flow() { // FIXME: workaround for rust#6393 let mut do_remove = false; @@ -489,7 +489,7 @@ pub impl LayoutTreeBuilder { } } if (do_remove) { - (*parent_flow).remove_child(*last_flow); + (*parent_flow).remove_child(last_flow); } } } diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index d301dca365d..b50698c5674 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -15,7 +15,7 @@ use alert::{Alert, AlertMethods}; use core::libc::c_int; use geom::point::Point2D; use geom::size::Size2D; -use glut::glut::{ACTIVE_CTRL, DOUBLE, WindowHeight, WindowWidth}; +use glut::glut::{ACTIVE_CTRL, DOUBLE, HAVE_PRECISE_MOUSE_WHEEL, WindowHeight, WindowWidth}; use glut::glut; use glut::machack; @@ -92,10 +92,19 @@ impl WindowMethods for Window { do glut::mouse_func |button, state, x, y| { if button < 3 { window.handle_mouse(button, state, x, y); - } else { - window.handle_scroll(if button == 4 { -30.0 } else { 30.0 }); } } + do glut::mouse_wheel_func |button, direction, x, y| { + let delta = if HAVE_PRECISE_MOUSE_WHEEL { + (direction as f32) / 10000.0 + } else { + (direction as f32) * 30.0 + }; + + println(fmt!("delta is %f", delta as float)); + + window.handle_scroll(delta); + } machack::perform_scroll_wheel_hack(); From 1fbfd7d45e8950eef6713cf063ded756bf17f1bd Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 8 Jun 2013 18:15:37 -0700 Subject: [PATCH 07/25] Implement horizontal scrolling and pinch-to-zoom --- src/components/main/compositing/mod.rs | 5 ++-- .../main/platform/common/glut_windowing.rs | 26 +++++++++++++------ src/components/main/windowing.rs | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 954b13e8014..ad065a9966f 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -290,12 +290,11 @@ fn run_main_loop(port: Port, // When the user pinch-zooms, scale the layer - do window.set_zoom_callback |delta| { - let zoom_const = 0.01; + do window.set_zoom_callback |magnification| { let old_world_zoom = *world_zoom; // Determine zoom amount - *world_zoom = (*world_zoom + delta.y * zoom_const).max(&1.0); + *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; diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index b50698c5674..27db4d71154 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -94,16 +94,18 @@ impl WindowMethods for Window { window.handle_mouse(button, state, x, y); } } - do glut::mouse_wheel_func |button, direction, x, y| { + 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 }; - println(fmt!("delta is %f", delta as float)); - - window.handle_scroll(delta); + match wheel { + 1 => window.handle_scroll(Point2D(delta, 0.0)), + 2 => window.handle_zoom(delta), + _ => window.handle_scroll(Point2D(0.0, delta)), + } } machack::perform_scroll_wheel_hack(); @@ -170,12 +172,12 @@ impl Window { 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(Point2D(0.0, 20.0)); + callback(0.1); } } k if k == 31 && (glut::get_modifiers() & ACTIVE_CTRL) != 0 => { // Ctrl+- for self.zoom_callback.each |&callback| { - callback(Point2D(0.0, -20.0)); + callback(-0.1); } } _ => {} @@ -217,10 +219,18 @@ impl Window { } /// Helper function to handle a scroll. - fn handle_scroll(&mut self, delta: f32) { + fn handle_scroll(&mut self, delta: Point2D) { match self.scroll_callback { 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), } } diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index d3dd7b1bf0b..42cce45c60a 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -29,7 +29,7 @@ pub type MouseCallback = @fn(WindowMouseEvent); pub type ScrollCallback = @fn(Point2D); ///Type of the function that is called when the user zooms. -pub type ZoomCallback = @fn(Point2D); +pub type ZoomCallback = @fn(f32); /// Methods for an abstract Application. pub trait ApplicationMethods { From 7b284621930299a625b53a744f3ee9fd2493e17e Mon Sep 17 00:00:00 2001 From: Tim Kuehn Date: Fri, 7 Jun 2013 16:43:03 -0700 Subject: [PATCH 08/25] Send status messages to the compositor --- src/components/main/compositing/mod.rs | 24 ++++++++++++++++--- src/components/main/engine.rs | 19 ++++++++++----- .../main/platform/common/glut_windowing.rs | 4 ++++ src/components/main/windowing.rs | 2 ++ src/components/script/compositor_interface.rs | 19 +++++++++++++++ src/components/script/dom/window.rs | 1 + src/components/script/script.rc | 3 ++- src/components/script/script_task.rs | 19 ++++++++++++++- 8 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 src/components/script/compositor_interface.rs diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index ad065a9966f..4f8ff79e8f0 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -9,6 +9,8 @@ use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClick use windowing::{WindowMouseDownEvent, WindowMouseUpEvent}; 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 core::cell::Cell; @@ -36,6 +38,13 @@ pub struct CompositorTask { chan: SharedChan, } +impl CompositorInterface for CompositorTask { + fn send_compositor_msg(&self, msg: ReadyState) { + let msg = ChangeReadyState(msg); + self.chan.send(msg); + } +} + impl CompositorTask { /// 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. @@ -63,10 +72,12 @@ impl CompositorTask { /// Messages to the compositor. pub enum Msg { - /// Requests that the compositor paint the given layer buffer set for the given page size. - Paint(LayerBufferSet, Size2D), /// Requests that the compositor shut down. Exit, + /// Requests that the compositor paint the given layer buffer set for the given page size. + Paint(LayerBufferSet, Size2D), + /// Alerts the compositor to the current status of page loading + ChangeReadyState(ReadyState), } /// Azure surface wrapping to work with the layers infrastructure. @@ -134,12 +145,19 @@ fn run_main_loop(port: Port, let check_for_messages: @fn() = || { // Periodically check if the script task responded to our last resize event resize_rate_limiter.check_resize_response(); - // Handle messages while port.peek() { match port.recv() { Exit => *done = true, + ChangeReadyState(ready_state) => { + let window_title = match ready_state { + compositor_interface::FinishedLoading => ~"Servo", + _ => fmt!("%? — Servo", ready_state), + }; + window.set_title(window_title); + } + Paint(new_layer_buffer_set, new_size) => { debug!("osmain: received new frame"); diff --git a/src/components/main/engine.rs b/src/components/main/engine.rs index 6458876c8b6..8abbcaef096 100644 --- a/src/components/main/engine.rs +++ b/src/components/main/engine.rs @@ -10,6 +10,7 @@ use core::comm::{Port, SharedChan}; use gfx::opts::Opts; use gfx::render_task::RenderTask; 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; @@ -48,13 +49,15 @@ impl Engine { profiler_chan: ProfilerChan) -> EngineTask { let (script_port, script_chan) = (Cell(script_port), Cell(script_chan)); - let (request_port, request_chan) = comm::stream(); - let (request_port, request_chan) = (Cell(request_port), SharedChan::new(request_chan)); - let request_chan_clone = request_chan.clone(); + 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 opts = Cell(copy *opts); do task::spawn { + let compositor = compositor.take(); let render_task = RenderTask::new(compositor.clone(), opts.with_ref(|o| copy *o), profiler_chan.clone()); @@ -70,16 +73,20 @@ impl Engine { opts, profiler_task.chan.clone()); + let compositor_clone = compositor.clone(); let script_task = ScriptTask::new(script_port.take(), script_chan.take(), - request_chan_clone.clone(), + engine_chan_clone.clone(), + |msg: ReadyState| { + compositor_clone.send_compositor_msg(msg) + }, layout_task.clone(), resource_task.clone(), image_cache_task.clone()); Engine { - request_port: request_port.take(), + request_port: engine_port.take(), compositor: compositor.clone(), render_task: render_task, resource_task: resource_task.clone(), @@ -89,7 +96,7 @@ impl Engine { profiler_task: profiler_task, }.run(); } - request_chan.clone() + engine_chan.clone() } fn run(&self) { diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index 27db4d71154..7e3dfd33044 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -162,6 +162,10 @@ impl WindowMethods for Window { pub fn set_needs_display(@mut self) { glut::post_redisplay() } + + pub fn set_title(@mut self, title: &str) { + glut::set_window_title(self.glut_window, title); + } } impl Window { diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 42cce45c60a..829d1f26c40 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -61,5 +61,7 @@ pub trait WindowMethods { pub fn check_loop(@mut self); /// Schedules a redisplay at the next turn of the event loop. pub fn set_needs_display(@mut self); + /// Sets the title of the window + pub fn set_title(@mut self, title: &str); } diff --git a/src/components/script/compositor_interface.rs b/src/components/script/compositor_interface.rs new file mode 100644 index 00000000000..78e74f5fee1 --- /dev/null +++ b/src/components/script/compositor_interface.rs @@ -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 rendering. Used for setting status + Rendering, + /// Informs the compositor that a page is finished loading. Used for setting status + FinishedLoading, +} + +pub trait CompositorInterface: Clone { + fn send_compositor_msg(&self, ReadyState); +} diff --git a/src/components/script/dom/window.rs b/src/components/script/dom/window.rs index a5904b642c7..aa375a4d614 100644 --- a/src/components/script/dom/window.rs +++ b/src/components/script/dom/window.rs @@ -4,6 +4,7 @@ use dom::bindings::utils::WrapperCache; use dom::bindings::window; + use layout_interface::ReflowForScriptQuery; use script_task::{ExitMsg, FireTimerMsg, ScriptMsg, ScriptContext}; diff --git a/src/components/script/script.rc b/src/components/script/script.rc index dc303783472..2d608e4d25b 100644 --- a/src/components/script/script.rc +++ b/src/components/script/script.rc @@ -64,7 +64,8 @@ pub mod html { pub mod hubbub_html_parser; } -pub mod layout_interface; +pub mod compositor_interface; pub mod engine_interface; +pub mod layout_interface; pub mod script_task; diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index 0482febe570..d3aba93a8f9 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -5,6 +5,7 @@ /// The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing /// and layout tasks. +use compositor_interface::{ReadyState, Loading, Rendering, FinishedLoading}; use dom::bindings::utils::GlobalStaticData; use dom::document::Document; use dom::element::Element; @@ -68,12 +69,14 @@ impl ScriptTask { pub fn new(script_port: Port, script_chan: SharedChan, engine_task: EngineTask, + //FIXME(rust #5192): workaround for lack of working ~Trait + compositor_task: ~fn(ReadyState), layout_task: LayoutTask, resource_task: ResourceTask, image_cache_task: ImageCacheTask) -> ScriptTask { let (script_chan_copy, script_port) = (script_chan.clone(), Cell(script_port)); - + let compositor_task = Cell(compositor_task); // FIXME: rust#6399 let mut the_task = task(); the_task.sched_mode(SingleThreaded); @@ -82,6 +85,7 @@ impl ScriptTask { script_port.take(), script_chan_copy.clone(), engine_task.clone(), + compositor_task.take(), resource_task.clone(), image_cache_task.clone()); script_context.start(); @@ -123,6 +127,8 @@ pub struct ScriptContext { /// 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. js_runtime: js::rust::rt, @@ -176,6 +182,7 @@ impl ScriptContext { script_port: Port, script_chan: SharedChan, engine_task: EngineTask, + compositor_task: ~fn(ReadyState), resource_task: ResourceTask, img_cache_task: ImageCacheTask) -> @mut ScriptContext { @@ -200,6 +207,7 @@ impl ScriptContext { script_chan: script_chan, engine_task: engine_task, + compositor_task: compositor_task, js_runtime: js_runtime, js_context: js_context, @@ -308,6 +316,12 @@ impl ScriptContext { 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 send_compositor_msg(&self, msg: ReadyState) { + (self.compositor_task)(msg); + } + /// The entry point to document loading. Defines bindings, sets up the window and document /// objects, parses HTML and CSS, and kicks off initial layout. fn load(&mut self, url: Url) { @@ -319,6 +333,7 @@ impl ScriptContext { self.bindings_initialized = true } + self.send_compositor_msg(Loading); // Parse HTML. // // Note: We can parse the next document in parallel with any previous documents. @@ -359,6 +374,7 @@ impl ScriptContext { url: url }); + self.send_compositor_msg(Rendering); // Perform the initial reflow. self.damage = Some(DocumentDamage { root: root_node, @@ -376,6 +392,7 @@ impl ScriptContext { ~"???", 1); } + self.send_compositor_msg(FinishedLoading); } /// Sends a ping to layout and waits for the response. The response will arrive when the From a9ed2d809d6b90716edec32057c3901122b0abd7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sun, 9 Jun 2013 17:06:04 -0700 Subject: [PATCH 09/25] Spin the event loop every 50 ms to allow Rust channels to be processed. This is the nail in the coffin for GLUT; we need to fix this. --- src/components/main/platform/common/glut_windowing.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index 7e3dfd33044..847c4346c31 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -12,6 +12,7 @@ use windowing::{ResizeCallback, ScrollCallback, WindowMethods, WindowMouseEvent, use windowing::{WindowMouseDownEvent, WindowMouseUpEvent, ZoomCallback}; use alert::{Alert, AlertMethods}; +use core::cell::Cell; use core::libc::c_int; use geom::point::Point2D; use geom::size::Size2D; @@ -71,6 +72,15 @@ impl WindowMethods for Window { mouse_down_point: @mut Point2D(0, 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); + }; // Register event handlers. do glut::reshape_func(window.glut_window) |width, height| { @@ -107,6 +117,7 @@ impl WindowMethods for Window { _ => window.handle_scroll(Point2D(0.0, delta)), } } + (*register_timer_callback)(); machack::perform_scroll_wheel_hack(); From e50cee9adcdbf593efe49c165f1d5eec27a5fd40 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sun, 9 Jun 2013 17:37:29 -0700 Subject: [PATCH 10/25] Resolve relative URLs that begin with '//' --- src/components/script/html/cssparse.rs | 1 + src/components/script/html/hubbub_html_parser.rs | 6 ++++-- src/components/util/url.rs | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/script/html/cssparse.rs b/src/components/script/html/cssparse.rs index 9ee789d6e3e..7d1e4214462 100644 --- a/src/components/script/html/cssparse.rs +++ b/src/components/script/html/cssparse.rs @@ -43,6 +43,7 @@ pub fn spawn_css_parser(provenance: StylesheetProvenance, fn data_stream(provenance: StylesheetProvenance, resource_task: ResourceTask) -> DataStream { match provenance { UrlProvenance(url) => { + debug!("cssparse: loading style sheet at %s", url.to_str()); let (input_port, input_chan) = comm::stream(); resource_task.send(Load(url, input_chan)); resource_port_to_data_stream(input_port) diff --git a/src/components/script/html/hubbub_html_parser.rs b/src/components/script/html/hubbub_html_parser.rs index db67b48f035..f604038c58b 100644 --- a/src/components/script/html/hubbub_html_parser.rs +++ b/src/components/script/html/hubbub_html_parser.rs @@ -129,11 +129,13 @@ fn js_script_listener(to_parent: Chan<~[~[u8]]>, buf += data; } Done(Ok(*)) => { - result_chan.send(buf); + result_chan.send(Some(buf)); break; } Done(Err(*)) => { 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); } diff --git a/src/components/util/url.rs b/src/components/util/url.rs index fea0df23b73..bc7db8f5f32 100644 --- a/src/components/util/url.rs +++ b/src/components/util/url.rs @@ -32,6 +32,8 @@ pub fn make_url(str_url: ~str, current_url: Option) -> Url { debug!("make_url: current_url: %?", current_url); if current_url.path.is_empty() || current_url.path.ends_with("/") { current_url.scheme + "://" + current_url.host + "/" + str_url + } else if str_url.starts_with("//") { + current_url.scheme + ":" + str_url } else { let mut path = ~[]; for str::each_split_char(current_url.path, '/') |p| { From f3ad95fa01e2c1363d3a9de674ae77828421b1e9 Mon Sep 17 00:00:00 2001 From: eschweic Date: Mon, 10 Jun 2013 18:34:14 -0700 Subject: [PATCH 11/25] Added a command-line argument for rendering tiles at higher resolutions --- src/components/gfx/compositor.rs | 2 ++ src/components/gfx/opts.rs | 11 +++++++++++ src/components/gfx/render_layers.rs | 13 ++++++++----- src/components/gfx/render_task.rs | 11 +++++++++-- src/components/main/compositing/mod.rs | 2 +- src/support/geom/rust-geom | 2 +- 6 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/components/gfx/compositor.rs b/src/components/gfx/compositor.rs index 742894f739a..3dfb99b4385 100644 --- a/src/components/gfx/compositor.rs +++ b/src/components/gfx/compositor.rs @@ -12,6 +12,8 @@ pub struct LayerBuffer { // The rect in the containing RenderLayer that this represents. rect: Rect, + screen_pos: Rect, + // NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH. stride: uint } diff --git a/src/components/gfx/opts.rs b/src/components/gfx/opts.rs index d3193e2de7f..07c38f9a1bf 100644 --- a/src/components/gfx/opts.rs +++ b/src/components/gfx/opts.rs @@ -14,6 +14,10 @@ pub struct Opts { n_render_threads: uint, tile_size: uint, profiler_period: Option, + + /// 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)] @@ -28,6 +32,7 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts { getopts::optopt(~"s"), // size of tiles 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) { @@ -76,11 +81,17 @@ pub fn from_cmdline_args(args: &[~str]) -> Opts { 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 { urls: urls, render_backend: render_backend, n_render_threads: n_render_threads, tile_size: tile_size, profiler_period: profiler_period, + zoom: zoom, } } diff --git a/src/components/gfx/render_layers.rs b/src/components/gfx/render_layers.rs index a2cab09e2c1..a0741480dd7 100644 --- a/src/components/gfx/render_layers.rs +++ b/src/components/gfx/render_layers.rs @@ -37,6 +37,7 @@ pub fn render_layers(layer_ref: *RenderLayer, f: RenderFn) -> LayerBufferSet { let tile_size = opts.tile_size; + let scale = opts.zoom; // FIXME: Try not to create a new array here. let mut new_buffer_ports = ~[]; @@ -45,12 +46,12 @@ pub fn render_layers(layer_ref: *RenderLayer, do time::profile(time::RenderingPrepBuffCategory, prof_chan.clone()) { let layer: &RenderLayer = unsafe { cast::transmute(layer_ref) }; let mut y = 0; - while y < layer.size.height { + while y < layer.size.height * scale { let mut x = 0; - while x < layer.size.width { + while x < layer.size.width * scale { // Figure out the dimension of this tile. - let right = uint::min(x + tile_size, layer.size.width); - let bottom = uint::min(y + tile_size, layer.size.height); + let right = uint::min(x + tile_size, layer.size.width * scale); + let bottom = uint::min(y + tile_size, layer.size.height * scale); let width = right - x; let height = bottom - y; @@ -65,7 +66,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 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; // FIXME: Try harder to search for a matching tile. @@ -112,6 +114,7 @@ pub fn render_layers(layer_ref: *RenderLayer, stride, B8G8R8A8), rect: tile_rect, + screen_pos: screen_rect, stride: stride as uint }; //} diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index 457ae4aa5a4..aae8b8ecbd2 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -140,17 +140,24 @@ impl Renderer { // Apply the translation to render the tile we want. let matrix: Matrix2D = Matrix2D::identity(); - let matrix = matrix.translate(&-(layer_buffer.rect.origin.x as AzFloat), - &-(layer_buffer.rect.origin.y as AzFloat)); + let scale = thread_render_context.opts.zoom as f32; + + 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); // Clear the buffer. ctx.clear(); + // Draw the display list. let render_layer: &RenderLayer = unsafe { cast::transmute(render_layer_ref) }; + render_layer.display_list.draw_into_context(&ctx); } diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 4f8ff79e8f0..2eafb59eeb9 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -206,7 +206,7 @@ fn run_main_loop(port: Port, 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); // Set the layer's transform. diff --git a/src/support/geom/rust-geom b/src/support/geom/rust-geom index ebf1cc8f2e8..0b8ba61c6a9 160000 --- a/src/support/geom/rust-geom +++ b/src/support/geom/rust-geom @@ -1 +1 @@ -Subproject commit ebf1cc8f2e8a39f31da2575981fad966aa7da904 +Subproject commit 0b8ba61c6a9f33e6703be3e1e58bf4a74b248117 From 1aa8d6437d62f9854c577e876c7899ed4d2487d6 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 11 Jun 2013 11:15:45 -0700 Subject: [PATCH 12/25] Fix URL relativization so that links on Wikipedia work --- src/components/util/url.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/util/url.rs b/src/components/util/url.rs index bc7db8f5f32..0d2b86f75a2 100644 --- a/src/components/util/url.rs +++ b/src/components/util/url.rs @@ -30,10 +30,13 @@ pub fn make_url(str_url: ~str, current_url: Option) -> Url { } else { let current_url = current_url.get(); debug!("make_url: current_url: %?", current_url); - if current_url.path.is_empty() || current_url.path.ends_with("/") { - current_url.scheme + "://" + current_url.host + "/" + str_url - } else if str_url.starts_with("//") { + if str_url.starts_with("//") { 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 { let mut path = ~[]; for str::each_split_char(current_url.path, '/') |p| { From 96b9be6c335f526162664e2072ff81d80970acd8 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 11 Jun 2013 12:23:27 -0700 Subject: [PATCH 13/25] Add a cheesy progress indicator --- src/components/main/compositing/mod.rs | 8 +---- .../main/platform/common/glut_windowing.rs | 31 +++++++++++++++++-- src/components/main/windowing.rs | 5 +-- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 2eafb59eeb9..33e02afbae0 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -150,13 +150,7 @@ fn run_main_loop(port: Port, match port.recv() { Exit => *done = true, - ChangeReadyState(ready_state) => { - let window_title = match ready_state { - compositor_interface::FinishedLoading => ~"Servo", - _ => fmt!("%? — Servo", ready_state), - }; - window.set_title(window_title); - } + ChangeReadyState(ready_state) => window.set_ready_state(ready_state), Paint(new_layer_buffer_set, new_size) => { debug!("osmain: received new frame"); diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index 847c4346c31..275e20bdf42 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -19,6 +19,9 @@ use geom::size::Size2D; use glut::glut::{ACTIVE_CTRL, DOUBLE, HAVE_PRECISE_MOUSE_WHEEL, WindowHeight, WindowWidth}; use glut::glut; use glut::machack; +use script::compositor_interface::{FinishedLoading, Loading, Rendering, ReadyState}; + +static THROBBER: [char, ..8] = [ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' ]; /// A structure responsible for setting up and tearing down the entire windowing system. pub struct Application; @@ -46,6 +49,9 @@ pub struct Window { mouse_down_button: @mut c_int, mouse_down_point: @mut Point2D, + + ready_state: ReadyState, + throbber_frame: u8, } impl WindowMethods for Window { @@ -70,6 +76,9 @@ impl WindowMethods for Window { mouse_down_button: @mut 0, mouse_down_point: @mut Point2D(0, 0), + + ready_state: FinishedLoading, + throbber_frame: 0, }; // Spin the event loop every 50 ms to allow the Rust channels to be polled. @@ -80,6 +89,8 @@ impl WindowMethods for Window { 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. @@ -174,12 +185,28 @@ impl WindowMethods for Window { glut::post_redisplay() } - pub fn set_title(@mut self, title: &str) { - glut::set_window_title(self.glut_window, title); + /// Sets the ready state. + pub fn set_ready_state(@mut self, ready_state: ReadyState) { + self.ready_state = ready_state; + self.update_window_title() } } 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)) + } + Rendering => { + glut::set_window_title(self.glut_window, fmt!("%c Rendering — Servo", throbber)) + } + FinishedLoading => glut::set_window_title(self.glut_window, "Servo"), + } + } + /// Helper function to handle keyboard events. fn handle_key(&self, key: u8) { debug!("got key: %d", key as int); diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 829d1f26c40..8cced181ed3 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -6,6 +6,7 @@ use geom::point::Point2D; use geom::size::Size2D; +use script::compositor_interface::ReadyState; pub enum WindowMouseEvent { WindowClickEvent(uint, Point2D), @@ -61,7 +62,7 @@ pub trait WindowMethods { pub fn check_loop(@mut self); /// Schedules a redisplay at the next turn of the event loop. pub fn set_needs_display(@mut self); - /// Sets the title of the window - pub fn set_title(@mut self, title: &str); + /// Sets the ready state of the current page. + pub fn set_ready_state(@mut self, ready_state: ReadyState); } From 204c5b663ad551831b02168b984b8d4b8827c1a3 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 11 Jun 2013 16:10:39 -0700 Subject: [PATCH 14/25] Add a spinner for layout --- src/components/gfx/compositor.rs | 8 ++++++ src/components/gfx/render_task.rs | 4 ++- src/components/main/compositing/mod.rs | 18 ++++++++----- src/components/main/engine.rs | 2 +- src/components/main/layout/layout_task.rs | 6 ++++- .../main/platform/common/glut_windowing.rs | 26 ++++++++++++++++--- src/components/main/windowing.rs | 3 +++ src/components/script/compositor_interface.rs | 8 +++--- src/components/script/script_task.rs | 25 +++++++++++++----- 9 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/components/gfx/compositor.rs b/src/components/gfx/compositor.rs index 3dfb99b4385..ea87d548661 100644 --- a/src/components/gfx/compositor.rs +++ b/src/components/gfx/compositor.rs @@ -24,9 +24,17 @@ pub struct LayerBufferSet { 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 /// submit them to be drawn to the display. pub trait Compositor { fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D); + fn set_render_state(&self, render_state: RenderState); } diff --git a/src/components/gfx/render_task.rs b/src/components/gfx/render_task.rs index aae8b8ecbd2..4b76e2ac0f6 100644 --- a/src/components/gfx/render_task.rs +++ b/src/components/gfx/render_task.rs @@ -5,7 +5,7 @@ // The task that handles all rendering/painting. use azure::AzFloat; -use compositor::Compositor; +use compositor::{Compositor, IdleRenderState, RenderingRenderState}; use font_context::FontContext; use geom::matrix2d::Matrix2D; use opts::Opts; @@ -122,6 +122,7 @@ impl Renderer { fn render(&mut self, render_layer: RenderLayer) { debug!("renderer: rendering"); + self.compositor.set_render_state(RenderingRenderState); do profile(time::RenderingCategory, self.profiler_chan.clone()) { let layer_buffer_set = do render_layers(&render_layer, &self.opts, @@ -168,6 +169,7 @@ impl Renderer { debug!("renderer: returning surface"); self.compositor.paint(layer_buffer_set, render_layer.size); + self.compositor.set_render_state(IdleRenderState); } } } diff --git a/src/components/main/compositing/mod.rs b/src/components/main/compositing/mod.rs index 33e02afbae0..452f4bbb06d 100644 --- a/src/components/main/compositing/mod.rs +++ b/src/components/main/compositing/mod.rs @@ -8,6 +8,7 @@ use script::script_task::{LoadMsg, ScriptMsg, SendEventMsg}; use windowing::{ApplicationMethods, WindowMethods, WindowMouseEvent, WindowClickEvent}; 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; @@ -20,7 +21,7 @@ use core::util; use geom::matrix::identity; use geom::point::Point2D; 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::{Image, ImageData, ImageLayer, ImageLayerKind, RGB24Format, WithDataFn}; use layers::rendergl; @@ -39,8 +40,8 @@ pub struct CompositorTask { } impl CompositorInterface for CompositorTask { - fn send_compositor_msg(&self, msg: ReadyState) { - let msg = ChangeReadyState(msg); + fn set_ready_state(&self, ready_state: ReadyState) { + let msg = ChangeReadyState(ready_state); self.chan.send(msg); } } @@ -48,8 +49,7 @@ impl CompositorInterface for CompositorTask { impl CompositorTask { /// 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. - pub fn new(script_chan: SharedChan, - profiler_chan: ProfilerChan) + pub fn new(script_chan: SharedChan, profiler_chan: ProfilerChan) -> (CompositorTask, Port<()>) { let script_chan = Cell(script_chan); let (shutdown_port, shutdown_chan) = stream(); @@ -76,8 +76,10 @@ pub enum Msg { Exit, /// Requests that the compositor paint the given layer buffer set for the given page size. Paint(LayerBufferSet, Size2D), - /// Alerts the compositor to the current status of page loading + /// 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. @@ -151,6 +153,7 @@ fn run_main_loop(port: Port, 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) => { debug!("osmain: received new frame"); @@ -356,6 +359,9 @@ impl Compositor for CompositorTask { fn paint(&self, layer_buffer_set: LayerBufferSet, new_size: Size2D) { 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. diff --git a/src/components/main/engine.rs b/src/components/main/engine.rs index 8abbcaef096..921688bd061 100644 --- a/src/components/main/engine.rs +++ b/src/components/main/engine.rs @@ -78,7 +78,7 @@ impl Engine { script_chan.take(), engine_chan_clone.clone(), |msg: ReadyState| { - compositor_clone.send_compositor_msg(msg) + compositor_clone.set_ready_state(msg) }, layout_task.clone(), resource_task.clone(), diff --git a/src/components/main/layout/layout_task.rs b/src/components/main/layout/layout_task.rs index b96656abbd6..9b896c1253e 100644 --- a/src/components/main/layout/layout_task.rs +++ b/src/components/main/layout/layout_task.rs @@ -38,7 +38,7 @@ use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitMsg, 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 script::script_task::{ReflowCompleteMsg, ScriptMsg, SendEventMsg}; use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg}; use servo_net::local_image_cache::LocalImageCache; use servo_util::tree::{TreeNodeRef, TreeUtils}; @@ -255,7 +255,11 @@ impl Layout { debug!("%?", layout_root.dump()); // 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_chan.send(ReflowCompleteMsg); } /// Handles a query from the script task. This is the main routine that DOM functions like diff --git a/src/components/main/platform/common/glut_windowing.rs b/src/components/main/platform/common/glut_windowing.rs index 275e20bdf42..0b35f24ae7c 100644 --- a/src/components/main/platform/common/glut_windowing.rs +++ b/src/components/main/platform/common/glut_windowing.rs @@ -16,10 +16,11 @@ use core::cell::Cell; use core::libc::c_int; use geom::point::Point2D; use geom::size::Size2D; +use gfx::compositor::{IdleRenderState, RenderState, RenderingRenderState}; use glut::glut::{ACTIVE_CTRL, DOUBLE, HAVE_PRECISE_MOUSE_WHEEL, WindowHeight, WindowWidth}; use glut::glut; use glut::machack; -use script::compositor_interface::{FinishedLoading, Loading, Rendering, ReadyState}; +use script::compositor_interface::{FinishedLoading, Loading, PerformingLayout, ReadyState}; static THROBBER: [char, ..8] = [ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' ]; @@ -51,6 +52,7 @@ pub struct Window { mouse_down_point: @mut Point2D, ready_state: ReadyState, + render_state: RenderState, throbber_frame: u8, } @@ -78,6 +80,7 @@ impl WindowMethods for Window { mouse_down_point: @mut Point2D(0, 0), ready_state: FinishedLoading, + render_state: IdleRenderState, throbber_frame: 0, }; @@ -190,6 +193,12 @@ impl WindowMethods for Window { 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 { @@ -200,10 +209,19 @@ impl Window { Loading => { glut::set_window_title(self.glut_window, fmt!("%c Loading — Servo", throbber)) } - Rendering => { - glut::set_window_title(self.glut_window, fmt!("%c Rendering — 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"), + } } - FinishedLoading => glut::set_window_title(self.glut_window, "Servo"), } } diff --git a/src/components/main/windowing.rs b/src/components/main/windowing.rs index 8cced181ed3..888851cbbf1 100644 --- a/src/components/main/windowing.rs +++ b/src/components/main/windowing.rs @@ -6,6 +6,7 @@ use geom::point::Point2D; use geom::size::Size2D; +use gfx::compositor::RenderState; use script::compositor_interface::ReadyState; pub enum WindowMouseEvent { @@ -64,5 +65,7 @@ pub trait WindowMethods { 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); } diff --git a/src/components/script/compositor_interface.rs b/src/components/script/compositor_interface.rs index 78e74f5fee1..8e48e9f1492 100644 --- a/src/components/script/compositor_interface.rs +++ b/src/components/script/compositor_interface.rs @@ -8,12 +8,12 @@ pub enum ReadyState { /// Informs the compositor that a page is loading. Used for setting status Loading, - /// Informs the compositor that a page is rendering. Used for setting status - Rendering, + /// 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 send_compositor_msg(&self, ReadyState); +pub trait CompositorInterface : Clone { + fn set_ready_state(&self, ReadyState); } diff --git a/src/components/script/script_task.rs b/src/components/script/script_task.rs index d3aba93a8f9..69890abb5cb 100644 --- a/src/components/script/script_task.rs +++ b/src/components/script/script_task.rs @@ -5,7 +5,7 @@ /// The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing /// and layout tasks. -use compositor_interface::{ReadyState, Loading, Rendering, FinishedLoading}; +use compositor_interface::{ReadyState, Loading, PerformingLayout, FinishedLoading}; use dom::bindings::utils::GlobalStaticData; use dom::document::Document; use dom::element::Element; @@ -54,6 +54,8 @@ pub enum ScriptMsg { SendEventMsg(Event), /// Fires a JavaScript timeout. FireTimerMsg(~TimerData), + /// Notifies script that reflow is finished. + ReflowCompleteMsg, /// Exits the engine. ExitMsg, } @@ -262,6 +264,10 @@ impl ScriptContext { self.handle_fire_timer_msg(timer_data); true } + ReflowCompleteMsg => { + self.handle_reflow_complete_msg(); + true + } ExitMsg => { self.handle_exit_msg(); false @@ -306,6 +312,12 @@ impl ScriptContext { 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. fn handle_exit_msg(&mut self) { self.join_layout(); @@ -318,7 +330,7 @@ impl ScriptContext { // tells the compositor when loading starts and finishes // FIXME ~compositor_interface doesn't work right now, which is why this is necessary - fn send_compositor_msg(&self, msg: ReadyState) { + fn set_ready_state(&self, msg: ReadyState) { (self.compositor_task)(msg); } @@ -333,7 +345,7 @@ impl ScriptContext { self.bindings_initialized = true } - self.send_compositor_msg(Loading); + self.set_ready_state(Loading); // Parse HTML. // // Note: We can parse the next document in parallel with any previous documents. @@ -374,7 +386,6 @@ impl ScriptContext { url: url }); - self.send_compositor_msg(Rendering); // Perform the initial reflow. self.damage = Some(DocumentDamage { root: root_node, @@ -392,7 +403,6 @@ impl ScriptContext { ~"???", 1); } - self.send_compositor_msg(FinishedLoading); } /// Sends a ping to layout and waits for the response. The response will arrive when the @@ -426,6 +436,9 @@ impl ScriptContext { // Now, join the layout so that they will see the latest changes we have made. 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. let (join_port, join_chan) = comm::stream(); self.layout_join_port = Some(join_port); @@ -438,8 +451,8 @@ impl ScriptContext { 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_chan: self.script_chan.clone(), script_join_chan: join_chan, damage: replace(&mut self.damage, None).unwrap(), }; From aad51132eca3721370399113d370b3bc21b5b3b9 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 11 Jun 2013 19:21:03 -0700 Subject: [PATCH 15/25] Update submodules --- src/support/geom/rust-geom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/support/geom/rust-geom b/src/support/geom/rust-geom index 0b8ba61c6a9..efba52c0209 160000 --- a/src/support/geom/rust-geom +++ b/src/support/geom/rust-geom @@ -1 +1 @@ -Subproject commit 0b8ba61c6a9f33e6703be3e1e58bf4a74b248117 +Subproject commit efba52c020945e08f00bebf8ff54ec292e95eced From 162ba83108eb1d4c93ea07413c8af455b47149df Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 12 Jun 2013 11:42:52 -0700 Subject: [PATCH 16/25] Fix merge fallout --- src/components/gfx/font_context.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/gfx/font_context.rs b/src/components/gfx/font_context.rs index e2d1abf4137..310b75944e5 100644 --- a/src/components/gfx/font_context.rs +++ b/src/components/gfx/font_context.rs @@ -142,8 +142,14 @@ pub impl<'self> FontContext { for last_resort.each |family| { let result = list.find_font_in_family(*family,style); for result.each |font_entry| { - let instance = Font::new_from_existing_handle(self, &font_entry.handle, style, self.backend); - do result::iter(&instance) |font: &@mut Font| { fonts.push(*font); } + 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); + } } } From aee26114988be05ad7c6f3471af80d45db179725 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 12 Jun 2013 11:55:12 -0700 Subject: [PATCH 17/25] Make script and style display:none --- src/components/main/css/select.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/main/css/select.rs b/src/components/main/css/select.rs index 46c62de20ea..7ebf2519ba9 100644 --- a/src/components/main/css/select.rs +++ b/src/components/main/css/select.rs @@ -124,6 +124,8 @@ ul, ol, dl { page-break-before: avoid } /* Servo additions */ :link { color: blue } +script { display: none } +style { display: none } " } From e1b9e01e69457e0949e2284c1e27a295b80f3d82 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 12 Jun 2013 14:35:16 -0700 Subject: [PATCH 18/25] Fix merge fallout which was disabling all CSS classes. --- src/components/main/css/select_handler.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/main/css/select_handler.rs b/src/components/main/css/select_handler.rs index c04c48f688a..56312ee210b 100644 --- a/src/components/main/css/select_handler.rs +++ b/src/components/main/css/select_handler.rs @@ -24,16 +24,6 @@ fn with_node_name(node: AbstractNode, f: &fn(&str) -> R) -> R { } impl SelectHandler> for NodeSelectHandler { - // FIXME(tkuehn): placeholder to get servo to compile - fn node_has_class(&self, node: &AbstractNode, s: &str) -> bool { - true - } - - // FIXME(tkuehn): placeholder to get servo to compile - fn with_node_classes(&self, node: &AbstractNode, f: &fn(Option<&str>) -> R) -> R { - f(None) - } - fn with_node_name(&self, node: &AbstractNode, f: &fn(&str) -> R) -> R { with_node_name(*node, f) } From 327e7996a137b94a6bb73cc6e84f34136526101e Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 12 Jun 2013 16:00:00 -0700 Subject: [PATCH 19/25] test: Add a box model smoketest --- src/test/html/box-model-smoketest.html | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/test/html/box-model-smoketest.html diff --git a/src/test/html/box-model-smoketest.html b/src/test/html/box-model-smoketest.html new file mode 100644 index 00000000000..d297b63e018 --- /dev/null +++ b/src/test/html/box-model-smoketest.html @@ -0,0 +1,25 @@ + + + +box model smoketest + + +
Ten points for Gryffindor
+ + From b75b2de8bb8c5d2103ed99ae2a8f87975a33cde6 Mon Sep 17 00:00:00 2001 From: Eric Atkinson Date: Tue, 4 Jun 2013 11:49:16 -0700 Subject: [PATCH 20/25] Compute percent widths/margins properly and fix numerous small visual layout bugs. --- src/components/main/layout/block.rs | 34 ++++++++++++++++------------- src/components/main/layout/model.rs | 19 ++++++++-------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index fc90d3273d2..6bc5e0159a2 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -196,15 +196,15 @@ impl BlockFlowData { 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 margin_top = MaybeAuto::from_margin(style.margin_top(), + remaining_width).spec_or_default(Au(0)); + let margin_bottom = MaybeAuto::from_margin(style.margin_bottom(), + remaining_width).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())); + (MaybeAuto::from_width(style.width(), remaining_width), + MaybeAuto::from_margin(style.margin_left(), remaining_width), + MaybeAuto::from_margin(style.margin_right(), remaining_width)); // FIXME(pcwalton): We discard the width here. Is that correct? let (_, margin_left, margin_right) = self.compute_horiz(width, @@ -218,7 +218,7 @@ impl BlockFlowData { model.margin.left = margin_left; x_offset = model.offset(); - remaining_width = remaining_width - model.noncontent_width(); + remaining_width = width; } do box.with_mut_base |base| { @@ -243,10 +243,12 @@ impl BlockFlowData { pub fn assign_height_block(@mut self, ctx: &LayoutContext) { let mut cur_y = Au(0); + let mut top_offset = Au(0); for self.box.each |&box| { 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 +262,24 @@ impl BlockFlowData { let height = if self.is_root { Au::max(ctx.screen_size.size.height, cur_y) } else { - cur_y + cur_y - top_offset }; - - //TODO(eatkinson): compute heights using the 'height' property. - self.common.position.size.height = height; - + + let mut pb = Au(0); self.box.map(|&box| { do box.with_mut_base |base| { //The associated box is the border box of this flow base.position.origin.y = base.model.margin.top; - let pb = base.model.padding.top + base.model.padding.bottom + + pb = base.model.padding.top + base.model.padding.bottom + base.model.border.top + base.model.border.bottom; base.position.size.height = height + pb; } }); + + //TODO(eatkinson): compute heights using the 'height' property. + self.common.position.size.height = height + pb; + } pub fn build_display_list_block(@mut self, diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs index 0260fd346c7..0b654978400 100644 --- a/src/components/main/layout/model.rs +++ b/src/components/main/layout/model.rs @@ -37,12 +37,12 @@ pub enum MaybeAuto { Specified(Au), } -impl MaybeAuto { - pub fn from_margin(margin: CSSMargin) -> MaybeAuto{ +impl MaybeAuto{ + pub fn from_margin(margin: CSSMargin, cb_width: Au) -> MaybeAuto{ match margin { CSSMarginAuto => Auto, //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 CSSMarginLength(Px(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{ CSSWidthAuto => Auto, - //FIXME(eatkinson): Compute percents properly - CSSWidthPercentage(_) => Specified(Au(0)), + CSSWidthPercentage(percent) => Specified(cb_width.scale_by(percent/100.0)), //FIXME(eatkinson): Compute pt and em values properly CSSWidthLength(Px(v)) | CSSWidthLength(Pt(v)) | @@ -165,12 +164,12 @@ impl RenderBox { let border_width = border.top; let bounds = Rect { origin: Point2D { - x: abs_bounds.origin.x, - y: abs_bounds.origin.y, + x: abs_bounds.origin.x + border_width.scale_by(0.5), + y: abs_bounds.origin.y + border_width.scale_by(0.5), }, size: Size2D { - width: abs_bounds.size.width, - height: abs_bounds.size.height + width: abs_bounds.size.width - border_width, + height: abs_bounds.size.height - border_width } }; From 4017839fe242e1b0a5d571de2e51fbd94f12ee83 Mon Sep 17 00:00:00 2001 From: Eric Atkinson Date: Tue, 4 Jun 2013 12:07:36 -0700 Subject: [PATCH 21/25] Fix padding --- src/components/main/layout/model.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/main/layout/model.rs b/src/components/main/layout/model.rs index 0b654978400..37195aa757e 100644 --- a/src/components/main/layout/model.rs +++ b/src/components/main/layout/model.rs @@ -134,7 +134,7 @@ impl BoxModel { // FIXME(eatkinson): Handle 'em' and 'pt' correctly Au::from_frac_px(v) } - CSSPaddingPercentage(p) => content_box_width.scale_by(p) + CSSPaddingPercentage(p) => content_box_width.scale_by(p/100.0) } } } From badf1b85730bab0d7508256c8ca767c6701c01a7 Mon Sep 17 00:00:00 2001 From: Eric Atkinson Date: Tue, 4 Jun 2013 12:33:35 -0700 Subject: [PATCH 22/25] Vertical margins now contribute to content height. --- src/components/main/layout/block.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index 6bc5e0159a2..e2903d2af00 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -265,20 +265,22 @@ impl BlockFlowData { cur_y - top_offset }; - let mut pb = Au(0); + let mut noncontent_height = Au(0); self.box.map(|&box| { do box.with_mut_base |base| { //The associated box is the border box of this flow base.position.origin.y = base.model.margin.top; - 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.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 + pb; + self.common.position.size.height = height + noncontent_height; } From f3cdbaf6117785f0f6359acafe8074963c62a72e Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 12 Jun 2013 17:36:56 -0700 Subject: [PATCH 23/25] Stop sorting after every profiler datum comes in. --- src/components/main/layout/block.rs | 9 ++++----- src/components/util/time.rs | 27 +++++++++++++++------------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/components/main/layout/block.rs b/src/components/main/layout/block.rs index e2903d2af00..f7f07b14df2 100644 --- a/src/components/main/layout/block.rs +++ b/src/components/main/layout/block.rs @@ -206,11 +206,10 @@ impl BlockFlowData { MaybeAuto::from_margin(style.margin_left(), remaining_width), MaybeAuto::from_margin(style.margin_right(), remaining_width)); - // 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); + let (width, margin_left, margin_right) = self.compute_horiz(width, + margin_left, + margin_right, + available_width); model.margin.top = margin_top; model.margin.right = margin_right; diff --git a/src/components/util/time.rs b/src/components/util/time.rs index c986676ec72..4ea5828e892 100644 --- a/src/components/util/time.rs +++ b/src/components/util/time.rs @@ -151,7 +151,6 @@ impl ProfilerContext { match self.buckets[category as uint] { (_, ref mut data) => { data.push(t); - tim_sort(*data); } } @@ -171,17 +170,21 @@ impl ProfilerContext { println(fmt!("%31s %15s %15s %15s %15s %15s", "_category (ms)_", "_mean (ms)_", "_median (ms)_", "_min (ms)_", "_max (ms)_", "_bucket size_")); - for self.buckets.each |bucket| { - let &(category, data) = bucket; - 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)); + for vec::each_mut(self.buckets) |bucket| { + 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)); + } + } } } println(""); From 2ec341240741b3f34a918cf84be108efd8b0c6c7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 13 Jun 2013 16:35:48 -0700 Subject: [PATCH 24/25] Fix submodules and test_slam_layout --- src/support/css/rust-css | 2 +- src/support/netsurfcss/rust-netsurfcss | 2 +- src/test/html/test_slam_layout.js | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/support/css/rust-css b/src/support/css/rust-css index 09d2db847c1..865f5391143 160000 --- a/src/support/css/rust-css +++ b/src/support/css/rust-css @@ -1 +1 @@ -Subproject commit 09d2db847c11bcab7f1832d5daf5947a7c1384ee +Subproject commit 865f539114383a021822583801e8362faf916699 diff --git a/src/support/netsurfcss/rust-netsurfcss b/src/support/netsurfcss/rust-netsurfcss index 8fc7400ed33..325cd5197ed 160000 --- a/src/support/netsurfcss/rust-netsurfcss +++ b/src/support/netsurfcss/rust-netsurfcss @@ -1 +1 @@ -Subproject commit 8fc7400ed332c3d9edf358c2a18dd047ebde09a6 +Subproject commit 325cd5197ed953f5c7c9317111b20ec1599eaffe diff --git a/src/test/html/test_slam_layout.js b/src/test/html/test_slam_layout.js index b42fba50641..7149fba8044 100644 --- a/src/test/html/test_slam_layout.js +++ b/src/test/html/test_slam_layout.js @@ -12,7 +12,4 @@ for (var i = 0; i < count; i++) { } var stop = new Date(); window.alert((stop - start) / count * 1e6 + " ns/layout"); -<<<<<<< HEAD -======= window.close(); ->>>>>>> 0560988... Add link following and refactor the profiler. From c35abb215c5967b054465361a13c8548a87afc97 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 13 Jun 2013 17:42:25 -0700 Subject: [PATCH 25/25] Update rust-glut --- src/support/glut/rust-glut | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/support/glut/rust-glut b/src/support/glut/rust-glut index 7db24a19d25..453bf81e021 160000 --- a/src/support/glut/rust-glut +++ b/src/support/glut/rust-glut @@ -1 +1 @@ -Subproject commit 7db24a19d25fbedca2898381ae0b13b723c14135 +Subproject commit 453bf81e021008f5eba29b135f07f4529e6c8b2e