diff --git a/src/servo/content/content_task.rs b/src/servo/content/content_task.rs index 05740a64a51..b96f6c52ad7 100644 --- a/src/servo/content/content_task.rs +++ b/src/servo/content/content_task.rs @@ -3,9 +3,10 @@ The content task is the main task that runs JavaScript and spawns layout tasks. */ -export ContentTask; +export Content, ContentTask; export ControlMsg, ExecuteMsg, ParseMsg, ExitMsg, Timer; export PingMsg, PongMsg; +export task_from_context; use std::arc::{ARC, clone}; use comm::{Port, Chan, listen, select2}; @@ -43,8 +44,8 @@ use task::{task, SingleThreaded}; use js::glue::bindgen::RUST_JSVAL_TO_OBJECT; use js::JSVAL_NULL; -use js::jsapi::jsval; -use js::jsapi::bindgen::JS_CallFunctionValue; +use js::jsapi::{JSContext, jsval}; +use js::jsapi::bindgen::{JS_CallFunctionValue, JS_GetContextPrivate}; use ptr::null; enum ControlMsg { @@ -65,25 +66,13 @@ fn ContentTask(layout_task: LayoutTask, resource_task: ResourceTask, img_cache_task: ImageCacheTask) -> ContentTask { do task().sched_mode(SingleThreaded).spawn_listener:: |from_master| { - Content(layout_task, compositor, from_master, resource_task, img_cache_task).start(); + let content = Content(layout_task, from_master, resource_task, img_cache_task); + compositor.add_event_listener(content.event_port.chan()); + content.start(); } } -/// Sends a ping to layout and waits for the response -#[allow(non_implicitly_copyable_typarams)] -fn join_layout(scope: NodeScope, layout_task: LayoutTask) { - - if scope.is_reader_forked() { - listen(|response_from_layout| { - layout_task.send(layout_task::PingMsg(response_from_layout)); - response_from_layout.recv(); - }); - scope.reader_joined(); - } -} - -struct Content { - compositor: C, +struct Content { layout_task: LayoutTask, image_cache_task: ImageCacheTask, from_master: comm::Port, @@ -102,29 +91,26 @@ struct Content { compartment: Option, } -fn Content(layout_task: LayoutTask, - compositor: C, - from_master: Port, - resource_task: ResourceTask, - img_cache_task: ImageCacheTask) -> Content { - +fn Content(layout_task: LayoutTask, + from_master: Port, + resource_task: ResourceTask, + img_cache_task: ImageCacheTask) -> Content { + let jsrt = jsrt(); let cx = jsrt.cx(); let event_port = Port(); - compositor.add_event_listener(event_port.chan()); - cx.set_default_options_and_version(); cx.set_logging_error_reporter(); + let compartment = match cx.new_compartment(global_class) { Ok(c) => Some(c), Err(()) => None }; - Content { + let content = Content { layout_task : layout_task, image_cache_task : img_cache_task, - compositor : compositor, from_master : from_master, event_port : event_port, @@ -138,10 +124,18 @@ fn Content(layout_task: LayoutTask, resource_task : resource_task, compartment : compartment - } + }; + + cx.set_cx_private(ptr::to_unsafe_ptr(&content) as *()); + + content } -impl Content { +fn task_from_context(cx: *JSContext) -> &Content unsafe { + cast::reinterpret_cast(&JS_GetContextPrivate(cx)) +} + +impl Content { fn start() { while self.handle_msg(select2(self.from_master, self.event_port)) { @@ -159,7 +153,7 @@ impl Content { fn handle_control_msg(control_msg: ControlMsg) -> bool { match control_msg { ParseMsg(url) => { - #debug["content: Received url `%s` to parse", url_to_str(copy url)]; + debug!("content: Received url `%s` to parse", url_to_str(copy url)); // Note: we can parse the next document in parallel // with any previous documents. @@ -174,9 +168,9 @@ impl Content { let js_scripts = result.js_port.recv(); // Apply the css rules to the dom tree: - #debug["css_rules: %?", css_rules]; + debug!("css_rules: %?", css_rules); - #debug["js_scripts: %?", js_scripts]; + debug!("js_scripts: %?", js_scripts); let document = Document(root, self.scope, css_rules); let window = Window(self.from_master); @@ -215,11 +209,11 @@ impl Content { ExecuteMsg(url) => { - #debug["content: Received url `%s` to execute", url_to_str(copy url)]; + debug!("content: Received url `%s` to execute", url_to_str(copy url)); match read_whole_file(&Path(url.path)) { Err(msg) => { - println(#fmt["Error opening %s: %s", url_to_str(copy url), msg]); + println(fmt!("Error opening %s: %s", url_to_str(copy url), msg)); } Ok(bytes) => { let compartment = option::expect(self.compartment, ~"TODO error checking"); @@ -237,12 +231,31 @@ impl Content { } } + /** + Sends a ping to layout and waits for the response (i.e., it has finished any + pending layout request messages). + */ + fn join_layout() { + if self.scope.is_reader_forked() { + listen(|response_from_layout| { + self.layout_task.send(layout_task::PingMsg(response_from_layout)); + response_from_layout.recv(); + }); + self.scope.reader_joined(); + } + } + + /** + This method will wait until the layout task has completed its current action, + join the layout task, and then request a new layout run. It won't wait for the + new layout computation to finish. + */ fn relayout(document: Document, doc_url: &Url) { - #debug("content: performing relayout"); + debug!("content: performing relayout"); // Now, join the layout so that they will see the latest // changes we have made. - join_layout(self.scope, self.layout_task); + self.join_layout(); // Send new document and relevant styles to layout // FIXME: Put CSS rules in an arc or something. @@ -253,10 +266,23 @@ impl Content { self.scope.reader_forked(); } + fn query_layout(query: layout_task::LayoutQuery) -> layout_task::LayoutQueryResponse { + self.relayout(*self.document.get(), &self.doc_url.get()); + self.join_layout(); + + let response_port = Port(); + self.layout_task.send(layout_task::QueryMsg(query, response_port.chan())); + return response_port.recv() + } + + /** + This is the main entry point for receiving and dispatching DOM events. + */ + // TODO: actually perform DOM event dispatch. fn handle_event(event: Event) -> bool { match event { ResizeEvent(new_width, new_height) => { - #debug("content got resize event: %d, %d", new_width, new_height); + debug!("content got resize event: %d, %d", new_width, new_height); match copy self.document { None => { // Nothing to do. @@ -269,7 +295,7 @@ impl Content { return true; } ReflowEvent => { - #debug("content got reflow event"); + debug!("content got reflow event"); match copy self.document { None => { // Nothing to do. diff --git a/src/servo/dom/bindings/element.rs b/src/servo/dom/bindings/element.rs index 933838b96cc..e58d9bc9701 100644 --- a/src/servo/dom/bindings/element.rs +++ b/src/servo/dom/bindings/element.rs @@ -6,11 +6,13 @@ use js::{JS_ARGV, JSCLASS_HAS_RESERVED_SLOTS, JSPROP_ENUMERATE, JSPROP_SHARED, J use js::jsapi::{JSContext, jsval, JSObject, JSBool, jsid, JSClass, JSFreeOp, JSPropertySpec}; use js::jsapi::bindgen::{JS_ValueToString, JS_GetStringCharsZAndLength, JS_ReportError, JS_GetReservedSlot, JS_SetReservedSlot, JS_NewStringCopyN, - JS_DefineFunctions, JS_DefineProperty, JS_GetContextPrivate}; + JS_DefineFunctions, JS_DefineProperty}; use js::jsapi::bindgen::*; use js::glue::bindgen::*; use js::crust::{JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ConvertStub}; +use content::content_task::{Content, task_from_context}; +use layout::layout_task; use dom::node::{Node, NodeScope, Element}; use dom::element::*; use node::NodeBundle; @@ -70,20 +72,20 @@ extern fn HTMLImageElement_getWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva } let bundle = unwrap(obj); - let width = (*bundle).payload.scope.write((*bundle).payload.node, |nd| { + let node = (*bundle).payload.node; + let scope = (*bundle).payload.scope; + let width = scope.write(node, |nd| { match nd.kind { ~Element(ed) => { match ed.kind { ~HTMLImageElement(*) => { - // TODO: this should actually come from rendered dimensions! - match ed.get_attr(~"width") { - Some(s) => match int::from_str(s) { - Some(w) => au::from_px(w), - None => au(0) /* failed to parse a number */ - }, - None => au(0) /* no width attr. */ + let content : &Content = task_from_context(cx); + match content.query_layout(layout_task::ContentBox(node)) { + Ok(rect) => rect.width, + Err(()) => 0, } - }, + // TODO: if nothing is being rendered(?), return zero dimensions + } _ => fail ~"why is this not an image element?" } }, @@ -91,7 +93,7 @@ extern fn HTMLImageElement_getWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva } }); *vp = RUST_INT_TO_JSVAL( - (au::to_px(width) & (i32::max_value as int)) as libc::c_int); + (width & (i32::max_value as int)) as libc::c_int); return 1; } diff --git a/src/servo/image/holder.rs b/src/servo/image/holder.rs index 6934e8e1806..55531cc16dc 100644 --- a/src/servo/image/holder.rs +++ b/src/servo/image/holder.rs @@ -13,6 +13,7 @@ pub struct ImageHolder { // occasionally while get_image is being called mut url : Option, mut image : Option>, + mut cached_size: Size2D, image_cache_task: ImageCacheTask, reflow_cb: fn~(), @@ -23,6 +24,7 @@ fn ImageHolder(url : &Url, image_cache_task: ImageCacheTask, cb: fn~()) -> Image let holder = ImageHolder { url : Some(copy *url), image : None, + cached_size : Size2D(0,0), image_cache_task : image_cache_task, reflow_cb : copy cb, }; @@ -39,13 +41,26 @@ fn ImageHolder(url : &Url, image_cache_task: ImageCacheTask, cb: fn~()) -> Image } impl ImageHolder { + /** + This version doesn't perform any computation, but may be stale w.r.t. + newly-available image data that determines size. + + The intent is that the impure version is used during layout when + dimensions are used for computing layout. + */ + pure fn size() -> Size2D { + self.cached_size + } + + /** Query and update current image size */ fn get_size() -> Option> { debug!("get_size() %?", self.url); match self.get_image() { Some(img) => { let img_ref = get(&img); - Some(Size2D(img_ref.width as int, - img_ref.height as int)) + self.cached_size = Size2D(img_ref.width as int, + img_ref.height as int); + Some(copy self.cached_size) }, None => None } diff --git a/src/servo/layout/box.rs b/src/servo/layout/box.rs index 83987b11337..8f19a16debf 100644 --- a/src/servo/layout/box.rs +++ b/src/servo/layout/box.rs @@ -183,10 +183,10 @@ impl @RenderBox { /* The box formed by the content edge, as defined in CSS 2.1 Section 8.1. Coordinates are relative to the owning flow. */ - fn content_box() -> Rect { + pure fn content_box() -> Rect { match self.kind { ImageBox(i) => { - let size = i.get_size().get_default(Size2D(0,0)); + let size = i.size(); Rect { origin: copy self.data.position.origin, size: Size2D(au::from_px(size.width), diff --git a/src/servo/layout/flow.rs b/src/servo/layout/flow.rs index ce126bb8747..2c074e84675 100644 --- a/src/servo/layout/flow.rs +++ b/src/servo/layout/flow.rs @@ -140,7 +140,25 @@ impl @FlowContext { // Actual methods that do not require much flow-specific logic impl @FlowContext { - fn iter_boxes_for_node(node: Node, cb: pure fn&(@RenderBox) -> T) { + pure fn foldl_boxes_for_node(node: Node, seed: B, blk: pure fn&(B,@RenderBox) -> B) -> B { + match self.kind { + RootFlow(d) => match d.box { + Some(box) if box.node == node => { blk(seed, box) }, + _ => seed + }, + BlockFlow(d) => match d.box { + Some(box) if box.node == node => { blk(seed, box) }, + _ => seed + }, + InlineFlow(d) => do d.boxes.foldl(seed) |acc, box| { + if box.node == node { blk(acc, box) } + else { acc } + }, + _ => fail fmt!("Don't know how to iterate node's RenderBoxes for %?", self.kind) + } + } + + pure fn iter_boxes_for_node(node: Node, cb: pure fn&(@RenderBox) -> T) { match self.kind { RootFlow(d) => match d.box { Some(box) if box.node == node => { cb(box); }, diff --git a/src/servo/layout/layout_task.rs b/src/servo/layout/layout_task.rs index ca28b784548..26ad3e6d049 100644 --- a/src/servo/layout/layout_task.rs +++ b/src/servo/layout/layout_task.rs @@ -19,6 +19,7 @@ use gfx::render_task; use layout::box::RenderBox; use layout::box_builder::LayoutTreeBuilder; use layout::context::LayoutContext; +use opt = core::option; use render_task::RenderTask; use resource::image_cache_task::ImageCacheTask; use servo_text::font_cache::FontCache; @@ -31,72 +32,149 @@ use task::*; type LayoutTask = Chan; +enum LayoutQuery { + ContentBox(Node) +} + +type LayoutQueryResponse = Result; + +enum LayoutQueryResponse_ { + ContentSize(Size2D) +} + enum Msg { BuildMsg(Node, ARC, Url, Chan), PingMsg(Chan), + QueryMsg(LayoutQuery, Chan), ExitMsg } -fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> LayoutTask { - do spawn_listener::|request| { +fn LayoutTask(render_task: RenderTask, + img_cache_task: ImageCacheTask) -> LayoutTask { + do spawn_listener::|from_content| { + Layout(render_task, img_cache_task, from_content).start(); + } +} - // This just keeps our dom aux objects alive - let layout_data_refs = DVec(); - let font_cache = FontCache(); +struct Layout { + render_task: RenderTask, + image_cache_task: ImageCacheTask, + from_content: comm::Port, - loop { - match request.recv() { - PingMsg(ping_channel) => ping_channel.send(content_task::PongMsg), - ExitMsg => { - debug!("layout: ExitMsg received"); - break; - } - BuildMsg(node, styles, doc_url, event_chan) => { - debug!("layout: received layout request for: %s", doc_url.to_str()); - debug!("layout: parsed Node tree"); - node.dump(); + font_cache: @FontCache, + // This is used to root auxilliary RCU reader data + layout_refs: DVec<@LayoutData> +} - let layout_ctx = LayoutContext { - image_cache: image_cache_task, - font_cache: font_cache, - doc_url: doc_url, - reflow_cb: || event_chan.send(ReflowEvent), - // TODO: obtain screen size from a real data source - screen_size: Rect(Point2D(au(0), au(0)), Size2D(au::from_px(800), au::from_px(600))) - }; +fn Layout(render_task: RenderTask, + image_cache_task: ImageCacheTask, + from_content: comm::Port) -> Layout { - do util::time::time(~"layout") { - // TODO: this is dumb. we don't need 3 separate traversals. - node.initialize_style_for_subtree(&layout_ctx, &layout_data_refs); - node.recompute_style_for_subtree(&layout_ctx, styles); - /* resolve styles (convert relative values) down the node tree */ - apply_style(&layout_ctx, node, layout_ctx.reflow_cb); + Layout { + render_task: render_task, + image_cache_task: image_cache_task, + from_content: from_content, + font_cache: FontCache(), + layout_refs: DVec() + } +} + +impl Layout { + + fn handle_query(query: LayoutQuery, + reply_chan: comm::Chan) { + match query { + ContentBox(node) => { + // TODO: extract me to a method when I get sibling arms + let response = match node.aux(|a| a).flow { + None => Err(()), + Some(flow) => { + let start_val : Option> = None; + let rect = do flow.foldl_boxes_for_node(node, start_val) |acc, box| { + match acc { + Some(acc) => Some(acc.union(&box.content_box())), + None => Some(box.content_box()) + } + }; - let builder = LayoutTreeBuilder(); - let layout_root: @FlowContext = match builder.construct_trees(&layout_ctx, node) { - Ok(root) => root, - Err(*) => fail ~"Root flow should always exist" - }; - - debug!("layout: constructed Flow tree"); - layout_root.dump(); - - /* perform layout passes over the flow tree */ - do layout_root.traverse_postorder |f| { f.bubble_widths(&layout_ctx) } - do layout_root.traverse_preorder |f| { f.assign_widths(&layout_ctx) } - do layout_root.traverse_postorder |f| { f.assign_height(&layout_ctx) } - - let dlist = DVec(); - let builder = dl::DisplayListBuilder { - ctx: &layout_ctx, - }; - // TODO: set options on the builder before building - // TODO: be smarter about what needs painting - layout_root.build_display_list(&builder, © layout_root.data.position, &dlist); - render_task.send(render_task::RenderMsg(dlist)); + match rect { + None => Err(()), + Some(rect) => { + let size = Size2D(au::to_px(rect.size.width), + au::to_px(rect.size.height)); + Ok(ContentSize(move size)) + } + } } - } + }; + + reply_chan.send(response) } } } + + fn start() { + while self.handle_request(self.from_content) { + // loop indefinitely + } + } + + fn handle_request(request: comm::Port) -> bool { + match request.recv() { + PingMsg(ping_channel) => ping_channel.send(content_task::PongMsg), + QueryMsg(query, chan) => self.handle_query(query, chan), + ExitMsg => { + debug!("layout: ExitMsg received"); + return false + }, + BuildMsg(node, styles, doc_url, to_content) => { + debug!("layout: received layout request for: %s", doc_url.to_str()); + debug!("layout: parsed Node tree"); + node.dump(); + + let layout_ctx = LayoutContext { + image_cache: self.image_cache_task, + font_cache: self.font_cache, + doc_url: doc_url, + reflow_cb: || to_content.send(ReflowEvent), + // TODO: obtain screen size from a real data source + screen_size: Rect(Point2D(au(0), au(0)), Size2D(au::from_px(800), au::from_px(600))) + }; + + do util::time::time(~"layout") { + // TODO: this is dumb. we don't need 3 separate traversals. + node.initialize_style_for_subtree(&layout_ctx, &self.layout_refs); + node.recompute_style_for_subtree(&layout_ctx, styles); + /* resolve styles (convert relative values) down the node tree */ + apply_style(&layout_ctx, node, layout_ctx.reflow_cb); + + let builder = LayoutTreeBuilder(); + let layout_root: @FlowContext = match builder.construct_trees(&layout_ctx, node) { + Ok(root) => root, + Err(*) => fail ~"Root flow should always exist" + }; + + debug!("layout: constructed Flow tree"); + layout_root.dump(); + + /* perform layout passes over the flow tree */ + do layout_root.traverse_postorder |f| { f.bubble_widths(&layout_ctx) } + do layout_root.traverse_preorder |f| { f.assign_widths(&layout_ctx) } + do layout_root.traverse_postorder |f| { f.assign_height(&layout_ctx) } + + let dlist = DVec(); + let builder = dl::DisplayListBuilder { + ctx: &layout_ctx, + }; + // TODO: set options on the builder before building + // TODO: be smarter about what needs painting + layout_root.build_display_list(&builder, © layout_root.data.position, &dlist); + self.render_task.send(render_task::RenderMsg(dlist)); + } // time(layout) + } // BuildMsg + } // match + + true + } } +