diff options
author | Clark Gaebel <cgaebel@mozilla.com> | 2014-08-26 17:02:18 -0700 |
---|---|---|
committer | Clark Gaebel <cgaebel@mozilla.com> | 2014-09-04 14:10:07 -0700 |
commit | dafd0b652d8b5452e485a8430c8e120029fcdbbd (patch) | |
tree | fd7e92578a799b5392210b1cd4cbfad64e320155 | |
parent | ff97135ab974ae3059c1bfe00779b128aad5be34 (diff) | |
download | servo-dafd0b652d8b5452e485a8430c8e120029fcdbbd.tar.gz servo-dafd0b652d8b5452e485a8430c8e120029fcdbbd.zip |
Added a fast no-message RPC interface to the layout task.
-rw-r--r-- | src/components/gfx/font_cache_task.rs | 4 | ||||
-rw-r--r-- | src/components/layout/layout_task.rs | 507 | ||||
-rw-r--r-- | src/components/script/dom/node.rs | 9 | ||||
-rw-r--r-- | src/components/script/layout_interface.rs | 25 | ||||
-rw-r--r-- | src/components/script/page.rs | 35 |
5 files changed, 314 insertions, 266 deletions
diff --git a/src/components/gfx/font_cache_task.rs b/src/components/gfx/font_cache_task.rs index 8444fead414..1b1ff6227cb 100644 --- a/src/components/gfx/font_cache_task.rs +++ b/src/components/gfx/font_cache_task.rs @@ -247,7 +247,7 @@ impl FontCacheTask { } } - pub fn get_font_template(&mut self, family: String, desc: FontTemplateDescriptor) + pub fn get_font_template(&self, family: String, desc: FontTemplateDescriptor) -> Arc<FontTemplateData> { let (response_chan, response_port) = channel(); @@ -262,7 +262,7 @@ impl FontCacheTask { } } - pub fn add_web_font(&mut self, family: String, url: Url) { + pub fn add_web_font(&self, family: String, url: Url) { let (response_chan, response_port) = channel(); self.chan.send(AddWebFont(family, url, response_chan)); response_port.recv(); diff --git a/src/components/layout/layout_task.rs b/src/components/layout/layout_task.rs index e104ea443fe..2244f409e52 100644 --- a/src/components/layout/layout_task.rs +++ b/src/components/layout/layout_task.rs @@ -32,11 +32,11 @@ use layout_traits::{LayoutControlMsg, LayoutTaskFactory}; use script::dom::bindings::js::JS; use script::dom::node::{ElementNodeTypeId, LayoutDataRef, Node}; use script::dom::element::{HTMLBodyElementTypeId, HTMLHtmlElementTypeId}; -use script::layout_interface::{AddStylesheetMsg, ContentBoxQuery, ScriptLayoutChan}; -use script::layout_interface::{ContentBoxesQuery, ContentBoxesResponse, ExitNowMsg, LayoutQuery}; -use script::layout_interface::{HitTestQuery, ContentBoxResponse, HitTestResponse, MouseOverQuery, MouseOverResponse}; +use script::layout_interface::{AddStylesheetMsg, ScriptLayoutChan}; +use script::layout_interface::{TrustedNodeAddress, ContentBoxesResponse, ExitNowMsg}; +use script::layout_interface::{ContentBoxResponse, HitTestResponse, MouseOverResponse}; use script::layout_interface::{ContentChangedDocumentDamage, LayoutChan, Msg, PrepareToExitMsg}; -use script::layout_interface::{QueryMsg, ReapLayoutDataMsg, Reflow, UntrustedNodeAddress}; +use script::layout_interface::{GetRPCMsg, LayoutRPC, ReapLayoutDataMsg, Reflow, UntrustedNodeAddress}; use script::layout_interface::{ReflowForDisplay, ReflowMsg}; use script_traits::{SendEventMsg, ReflowEvent, ReflowCompleteMsg, OpaqueScriptLayoutChannel, ScriptControlChan}; use servo_msg::compositor_msg::Scrollable; @@ -61,6 +61,28 @@ use style::iter_font_face_rules; use sync::{Arc, Mutex}; use url::Url; +/// Mutable data belonging to the LayoutTask. +/// +/// This needs to be protected by a mutex so we can do fast RPCs. +pub struct LayoutTaskData { + /// The local image cache. + pub local_image_cache: Arc<Mutex<LocalImageCache>>, + + /// The size of the viewport. + pub screen_size: Size2D<Au>, + + /// A cached display list. + pub display_list: Option<Arc<DisplayList>>, + + pub stylist: Box<Stylist>, + + /// The workers that we use for parallel operation. + pub parallel_traversal: Option<WorkQueue<*const SharedLayoutContext, UnsafeFlow>>, + + /// The dirty rect. Used during display list construction. + pub dirty: Rect<Au>, +} + /// Information needed by the layout task. pub struct LayoutTask { /// The ID of the pipeline that we belong to. @@ -84,34 +106,23 @@ pub struct LayoutTask { /// The channel on which messages can be sent to the painting task. pub render_chan: RenderChan, + /// The channel on which messages can be sent to the time profiler. + pub time_profiler_chan: TimeProfilerChan, + /// The channel on which messages can be sent to the image cache. pub image_cache_task: ImageCacheTask, /// Public interface to the font cache task. pub font_cache_task: FontCacheTask, - /// The local image cache. - pub local_image_cache: Arc<Mutex<LocalImageCache>>, - - /// The size of the viewport. - pub screen_size: Size2D<Au>, - - /// A cached display list. - pub display_list: Option<Arc<DisplayList>>, - - pub stylist: Box<Stylist>, - - /// The workers that we use for parallel operation. - pub parallel_traversal: Option<WorkQueue<*const SharedLayoutContext,UnsafeFlow>>, - - /// The channel on which messages can be sent to the time profiler. - pub time_profiler_chan: TimeProfilerChan, - /// The command-line options. pub opts: Opts, - /// The dirty rect. Used during display list construction. - pub dirty: Rect<Au>, + /// A mutex to allow for fast, read-only RPC of layout's internal data + /// structures, while still letting the LayoutTask modify them. + /// + /// All the other elements of this struct are read-only. + pub rw_data: Arc<Mutex<LayoutTaskData>>, } /// The damage computation traversal. @@ -293,17 +304,19 @@ impl LayoutTaskFactory for LayoutTask { spawn_named_with_send_on_failure("LayoutTask", proc() { { // Ensures layout task is destroyed before we send shutdown message let sender = chan.sender(); - let mut layout = LayoutTask::new(id, - chan.receiver(), - LayoutChan(sender), - pipeline_port, - constellation_chan, - script_chan, - render_chan, - img_cache_task, - font_cache_task, - &opts, - time_profiler_chan); + let layout = + LayoutTask::new( + id, + chan.receiver(), + LayoutChan(sender), + pipeline_port, + constellation_chan, + script_chan, + render_chan, + img_cache_task, + font_cache_task, + &opts, + time_profiler_chan); layout.start(); } shutdown_chan.send(()); @@ -341,36 +354,38 @@ impl LayoutTask { constellation_chan: constellation_chan, script_chan: script_chan, render_chan: render_chan, + time_profiler_chan: time_profiler_chan, image_cache_task: image_cache_task.clone(), font_cache_task: font_cache_task, - local_image_cache: local_image_cache, - screen_size: screen_size, - - display_list: None, - stylist: box Stylist::new(), - parallel_traversal: parallel_traversal, - time_profiler_chan: time_profiler_chan, opts: opts.clone(), - dirty: Rect::zero(), + rw_data: Arc::new(Mutex::new( + LayoutTaskData { + local_image_cache: local_image_cache, + screen_size: screen_size, + display_list: None, + stylist: box Stylist::new(), + parallel_traversal: parallel_traversal, + dirty: Rect::zero(), + })), } } /// Starts listening on the port. - fn start(&mut self) { + fn start(self) { while self.handle_request() { // Loop indefinitely. } } // Create a layout context for use in building display lists, hit testing, &c. - fn build_shared_layout_context(&self, reflow_root: &LayoutNode, url: &Url) -> SharedLayoutContext { + fn build_shared_layout_context(&self, rw_data: &LayoutTaskData, reflow_root: &LayoutNode, url: &Url) -> SharedLayoutContext { SharedLayoutContext { - image_cache: self.local_image_cache.clone(), - screen_size: self.screen_size.clone(), + image_cache: rw_data.local_image_cache.clone(), + screen_size: rw_data.screen_size.clone(), constellation_chan: self.constellation_chan.clone(), layout_chan: self.chan.clone(), font_cache_task: self.font_cache_task.clone(), - stylist: &*self.stylist, + stylist: &*rw_data.stylist, url: (*url).clone(), reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root), opts: self.opts.clone(), @@ -379,7 +394,7 @@ impl LayoutTask { } /// Receives and dispatches messages from the script and constellation tasks - fn handle_request(&mut self) -> bool { + fn handle_request(&self) -> bool { enum PortToRead { Pipeline, Script, @@ -415,30 +430,29 @@ impl LayoutTask { } /// Receives and dispatches messages from the script task. - fn handle_script_request(&mut self, request: Msg) -> bool { + fn handle_script_request(&self, request: Msg) -> bool { match request { AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet), + GetRPCMsg(response_chan) => { + response_chan.send( + box LayoutRPCImpl( + self.rw_data.clone()) as Box<LayoutRPC + Send>); + }, ReflowMsg(data) => { profile(time::LayoutPerformCategory, self.time_profiler_chan.clone(), || { self.handle_reflow(&*data); }); - } - QueryMsg(query) => { - let mut query = Some(query); - profile(time::LayoutQueryCategory, self.time_profiler_chan.clone(), || { - self.handle_query(query.take_unwrap()); - }); - } + }, ReapLayoutDataMsg(dead_layout_data) => { unsafe { - self.handle_reap_layout_data(dead_layout_data) + LayoutTask::handle_reap_layout_data(dead_layout_data) } - } + }, PrepareToExitMsg(response_chan) => { debug!("layout: PrepareToExitMsg received"); self.prepare_to_exit(response_chan); return false - } + }, ExitNowMsg => { debug!("layout: ExitNowMsg received"); self.exit_now(); @@ -452,13 +466,13 @@ impl LayoutTask { /// Enters a quiescent state in which no new messages except for `ReapLayoutDataMsg` will be /// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given /// response channel. - fn prepare_to_exit(&mut self, response_chan: Sender<()>) { + fn prepare_to_exit(&self, response_chan: Sender<()>) { response_chan.send(()); loop { match self.port.recv() { ReapLayoutDataMsg(dead_layout_data) => { unsafe { - self.handle_reap_layout_data(dead_layout_data) + LayoutTask::handle_reap_layout_data(dead_layout_data) } } ExitNowMsg => { @@ -476,25 +490,29 @@ impl LayoutTask { /// Shuts down the layout task now. If there are any DOM nodes left, layout will now (safely) /// crash. - fn exit_now(&mut self) { + fn exit_now(&self) { let (response_chan, response_port) = channel(); - match self.parallel_traversal { - None => {} - Some(ref mut traversal) => traversal.shutdown(), + { + let mut rw_data = self.rw_data.lock(); + match rw_data.deref_mut().parallel_traversal { + None => {} + Some(ref mut traversal) => traversal.shutdown(), + } } self.render_chan.send(render_task::ExitMsg(Some(response_chan))); response_port.recv() } - fn handle_add_stylesheet(&mut self, sheet: Stylesheet) { + fn handle_add_stylesheet(&self, sheet: Stylesheet) { // Find all font-face rules and notify the font cache of them. // GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!) iter_font_face_rules(&sheet, |family, url| { self.font_cache_task.add_web_font(family.to_string(), url.clone()); }); - self.stylist.add_stylesheet(sheet, AuthorOrigin); + let mut rw_data = self.rw_data.lock(); + rw_data.stylist.add_stylesheet(sheet, AuthorOrigin); } /// Retrieves the flow tree root from the root node. @@ -527,7 +545,7 @@ impl LayoutTask { /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling. #[inline(never)] - fn solve_constraints<'a>(&mut self, + fn solve_constraints<'a>(&self, layout_root: &mut Flow, layout_context: &'a LayoutContext<'a>) { if layout_context.shared.opts.bubble_inline_sizes_separately { @@ -563,7 +581,8 @@ impl LayoutTask { /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling. #[inline(never)] - fn solve_constraints_parallel(&mut self, + fn solve_constraints_parallel(&self, + rw_data: &mut LayoutTaskData, layout_root: &mut FlowRef, shared_layout_context: &SharedLayoutContext) { if shared_layout_context.opts.bubble_inline_sizes_separately { @@ -573,7 +592,7 @@ impl LayoutTask { layout_root.get_mut().traverse_postorder(&mut traversal); } - match self.parallel_traversal { + match rw_data.parallel_traversal { None => fail!("solve_contraints_parallel() called with no parallel traversal ready"), Some(ref mut traversal) => { // NOTE: this currently computes borders, so any pruning should separate that @@ -590,17 +609,17 @@ impl LayoutTask { /// This is only on in debug builds. #[inline(never)] #[cfg(debug)] - fn verify_flow_tree(&mut self, layout_root: &mut FlowRef) { + fn verify_flow_tree(&self, layout_root: &mut FlowRef) { let mut traversal = FlowTreeVerificationTraversal; layout_root.traverse_preorder(&mut traversal); } #[cfg(not(debug))] - fn verify_flow_tree(&mut self, _: &mut FlowRef) { + fn verify_flow_tree(&self, _: &mut FlowRef) { } /// The high-level routine that performs layout tasks. - fn handle_reflow(&mut self, data: &Reflow) { + fn handle_reflow(&self, data: &Reflow) { // FIXME: Isolate this transmutation into a "bridge" module. // FIXME(rust#16366): The following line had to be moved because of a // rustc bug. It should be in the next unsafe block. @@ -614,9 +633,11 @@ impl LayoutTask { debug!("layout: parsed Node tree"); debug!("{:?}", node.dump()); + let mut rw_data = self.rw_data.lock(); + { // Reset the image cache. - let mut local_image_cache = self.local_image_cache.lock(); + let mut local_image_cache = rw_data.local_image_cache.lock(); local_image_cache.next_round(self.make_on_image_available_cb()); } @@ -633,23 +654,24 @@ impl LayoutTask { let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()), Au::from_frac32_px(viewport_size.height.get())); - if self.screen_size != current_screen_size { + if rw_data.screen_size != current_screen_size { all_style_damage = true } - self.screen_size = current_screen_size; + rw_data.screen_size = current_screen_size; // Create a layout context for use throughout the following passes. - let mut shared_layout_ctx = self.build_shared_layout_context(node, &data.url); + let mut shared_layout_ctx = self.build_shared_layout_context(rw_data.deref(), node, &data.url); let mut layout_root = profile(time::LayoutStyleRecalcCategory, self.time_profiler_chan.clone(), || { // Perform CSS selector matching and flow construction. - match self.parallel_traversal { + let rw_data = rw_data.deref_mut(); + match rw_data.parallel_traversal { None => { let layout_ctx = LayoutContext::new(&shared_layout_ctx); let mut applicable_declarations = ApplicableDeclarations::new(); - node.recalc_style_for_subtree(&*self.stylist, + node.recalc_style_for_subtree(&*rw_data.stylist, &layout_ctx, &mut applicable_declarations, None) @@ -678,7 +700,8 @@ impl LayoutTask { // Perform the primary layout passes over the flow tree to compute the locations of all // the boxes. profile(time::LayoutMainCategory, self.time_profiler_chan.clone(), || { - match self.parallel_traversal { + let rw_data = rw_data.deref_mut(); + match rw_data.parallel_traversal { None => { // Sequential mode. let layout_ctx = LayoutContext::new(&shared_layout_ctx); @@ -686,7 +709,7 @@ impl LayoutTask { } Some(_) => { // Parallel mode. - self.solve_constraints_parallel(&mut layout_root, &mut shared_layout_ctx) + self.solve_constraints_parallel(rw_data, &mut layout_root, &mut shared_layout_ctx) } } }); @@ -696,11 +719,12 @@ impl LayoutTask { let writing_mode = flow::base(layout_root.get()).writing_mode; profile(time::LayoutDispListBuildCategory, self.time_profiler_chan.clone(), || { shared_layout_ctx.dirty = flow::base(layout_root.get()).position.to_physical( - writing_mode, self.screen_size); + writing_mode, rw_data.screen_size); flow::mut_base(layout_root.get_mut()).abs_position = - LogicalPoint::zero(writing_mode).to_physical(writing_mode, self.screen_size); + LogicalPoint::zero(writing_mode).to_physical(writing_mode, rw_data.screen_size); - match self.parallel_traversal { + let rw_data = rw_data.deref_mut(); + match rw_data.parallel_traversal { None => { let layout_ctx = LayoutContext::new(&shared_layout_ctx); let mut traversal = BuildDisplayListTraversal { @@ -760,7 +784,7 @@ impl LayoutTask { scroll_policy: Scrollable, }; - self.display_list = Some(display_list.clone()); + rw_data.display_list = Some(display_list.clone()); // TODO(pcwalton): Eventually, when we have incremental reflow, this will have to // be smarter in order to handle retained layer contents properly from reflow to @@ -787,178 +811,199 @@ impl LayoutTask { chan.send(ReflowCompleteMsg(self.id, data.id)); } - /// Handles a query from the script task. This is the main routine that DOM functions like - /// `getClientRects()` or `getBoundingClientRect()` ultimately invoke. - fn handle_query(&self, query: LayoutQuery) { - match query { - // The neat thing here is that in order to answer the following two queries we only - // need to compare nodes for equality. Thus we can safely work only with `OpaqueNode`. - ContentBoxQuery(node, reply_chan) => { - let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node); - fn union_boxes_for_node(accumulator: &mut Option<Rect<Au>>, - mut iter: DisplayItemIterator, - node: OpaqueNode) { - for item in iter { - union_boxes_for_node(accumulator, item.children(), node); - if item.base().node == node { - match *accumulator { - None => *accumulator = Some(item.base().bounds), - Some(ref mut acc) => *acc = acc.union(&item.base().bounds), - } - } + + // When images can't be loaded in time to display they trigger + // this callback in some task somewhere. This will send a message + // to the script task, and ultimately cause the image to be + // re-requested. We probably don't need to go all the way back to + // the script task for this. + fn make_on_image_available_cb(&self) -> Box<ImageResponder+Send> { + // This has a crazy signature because the image cache needs to + // make multiple copies of the callback, and the dom event + // channel is not a copyable type, so this is actually a + // little factory to produce callbacks + box LayoutImageResponder { + id: self.id.clone(), + script_chan: self.script_chan.clone(), + } as Box<ImageResponder+Send> + } + + /// Handles a message to destroy layout data. Layout data must be destroyed on *this* task + /// because it contains local managed pointers. + unsafe fn handle_reap_layout_data(layout_data: LayoutDataRef) { + let mut layout_data_ref = layout_data.borrow_mut(); + let _: Option<LayoutDataWrapper> = mem::transmute( + mem::replace(&mut *layout_data_ref, None)); + } +} + +struct LayoutRPCImpl(Arc<Mutex<LayoutTaskData>>); + +impl LayoutRPC for LayoutRPCImpl { + // The neat thing here is that in order to answer the following two queries we only + // need to compare nodes for equality. Thus we can safely work only with `OpaqueNode`. + fn content_box(&self, node: TrustedNodeAddress) -> ContentBoxResponse { + let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node); + fn union_boxes_for_node(accumulator: &mut Option<Rect<Au>>, + mut iter: DisplayItemIterator, + node: OpaqueNode) { + for item in iter { + union_boxes_for_node(accumulator, item.children(), node); + if item.base().node == node { + match *accumulator { + None => *accumulator = Some(item.base().bounds), + Some(ref mut acc) => *acc = acc.union(&item.base().bounds), } } + } + } - let mut rect = None; - match self.display_list { - None => fail!("no display list!"), - Some(ref display_list) => { - union_boxes_for_node(&mut rect, display_list.iter(), node) - } + let mut rect = None; + { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock(); + match rw_data.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + union_boxes_for_node(&mut rect, display_list.iter(), node) } - reply_chan.send(ContentBoxResponse(rect.unwrap_or(Rect::zero()))) } - ContentBoxesQuery(node, reply_chan) => { - let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node); - - fn add_boxes_for_node(accumulator: &mut Vec<Rect<Au>>, - mut iter: DisplayItemIterator, - node: OpaqueNode) { - for item in iter { - add_boxes_for_node(accumulator, item.children(), node); - if item.base().node == node { - accumulator.push(item.base().bounds) - } - } + } + ContentBoxResponse(rect.unwrap_or(Rect::zero())) + } + + /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call. + fn content_boxes(&self, node: TrustedNodeAddress) -> ContentBoxesResponse { + let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node); + + fn add_boxes_for_node(accumulator: &mut Vec<Rect<Au>>, + mut iter: DisplayItemIterator, + node: OpaqueNode) { + for item in iter { + add_boxes_for_node(accumulator, item.children(), node); + if item.base().node == node { + accumulator.push(item.base().bounds) } + } + } - let mut boxes = vec!(); - match self.display_list { - None => fail!("no display list!"), - Some(ref display_list) => { - add_boxes_for_node(&mut boxes, display_list.iter(), node) - } + let mut boxes = vec!(); + { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock(); + match rw_data.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + add_boxes_for_node(&mut boxes, display_list.iter(), node) } - reply_chan.send(ContentBoxesResponse(boxes)) } - HitTestQuery(_, point, reply_chan) => { - fn hit_test<'a,I:Iterator<&'a DisplayItem>>(x: Au, y: Au, mut iterator: I) - -> Option<HitTestResponse> { - for item in iterator { - match *item { - ClipDisplayItemClass(ref cc) => { - if geometry::rect_contains_point(cc.base.bounds, Point2D(x, y)) { - let ret = hit_test(x, y, cc.children.list.iter().rev()); - if !ret.is_none() { - return ret - } - } - continue + } + ContentBoxesResponse(boxes) + } + + /// Requests the node containing the point of interest + fn hit_test(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()> { + fn hit_test<'a,I:Iterator<&'a DisplayItem>>(x: Au, y: Au, mut iterator: I) + -> Option<HitTestResponse> { + for item in iterator { + match *item { + ClipDisplayItemClass(ref cc) => { + if geometry::rect_contains_point(cc.base.bounds, Point2D(x, y)) { + let ret = hit_test(x, y, cc.children.list.iter().rev()); + if !ret.is_none() { + return ret } - _ => {} } + continue + } + _ => {} + } + let bounds = item.bounds(); + + // TODO(tikue): 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 && + bounds.origin.y <= y { + return Some(HitTestResponse(item.base() + .node + .to_untrusted_node_address())) + } + } + let ret: Option<HitTestResponse> = None; + ret + } + let (x, y) = (Au::from_frac_px(point.x as f64), + Au::from_frac_px(point.y as f64)); + + let resp = { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock(); + match rw_data.display_list { + None => fail!("no display list!"), + Some(ref display_list) => hit_test(x, y, display_list.list.iter().rev()), + } + }; + + if resp.is_some() { + return Ok(resp.unwrap()); + } + Err(()) + } + + fn mouse_over(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()> { + fn mouse_over_test<'a, + I:Iterator<&'a DisplayItem>>( + x: Au, + y: Au, + mut iterator: I, + result: &mut Vec<UntrustedNodeAddress>) { + for item in iterator { + match *item { + ClipDisplayItemClass(ref cc) => { + mouse_over_test(x, y, cc.children.list.iter().rev(), result); + } + _ => { let bounds = item.bounds(); - // TODO(tikue): This check should really be performed by a method of - // DisplayItem. + // TODO(tikue): 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 && bounds.origin.y <= y { - return Some(HitTestResponse(item.base() - .node - .to_untrusted_node_address())) + result.push(item.base() + .node + .to_untrusted_node_address()); } } - let ret: Option<HitTestResponse> = None; - ret - } - let (x, y) = (Au::from_frac_px(point.x as f64), - Au::from_frac_px(point.y as f64)); - let resp = match self.display_list { - None => fail!("no display list!"), - Some(ref display_list) => hit_test(x, y, display_list.list.iter().rev()), - }; - if resp.is_some() { - reply_chan.send(Ok(resp.unwrap())); - return } - reply_chan.send(Err(())); - } - MouseOverQuery(_, point, reply_chan) => { - fn mouse_over_test<'a, - I:Iterator<&'a DisplayItem>>( - x: Au, - y: Au, - mut iterator: I, - result: &mut Vec<UntrustedNodeAddress>) { - for item in iterator { - match *item { - ClipDisplayItemClass(ref cc) => { - mouse_over_test(x, y, cc.children.list.iter().rev(), result); - } - _ => { - let bounds = item.bounds(); - - // TODO(tikue): 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 && - bounds.origin.y <= y { - result.push(item.base() - .node - .to_untrusted_node_address()); - } - } - } - } - } + } - let mut mouse_over_list: Vec<UntrustedNodeAddress> = vec!(); - let (x, y) = (Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64)); - match self.display_list { - None => fail!("no display list!"), - Some(ref display_list) => { - mouse_over_test(x, - y, - display_list.list.iter().rev(), - &mut mouse_over_list); - } - }; + let mut mouse_over_list: Vec<UntrustedNodeAddress> = vec!(); + let (x, y) = (Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64)); - if mouse_over_list.is_empty() { - reply_chan.send(Err(())); - } else { - reply_chan.send(Ok(MouseOverResponse(mouse_over_list))); + { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock(); + match rw_data.display_list { + None => fail!("no display list!"), + Some(ref display_list) => { + mouse_over_test(x, + y, + display_list.list.iter().rev(), + &mut mouse_over_list); } - } + }; } - } - - // When images can't be loaded in time to display they trigger - // this callback in some task somewhere. This will send a message - // to the script task, and ultimately cause the image to be - // re-requested. We probably don't need to go all the way back to - // the script task for this. - fn make_on_image_available_cb(&self) -> Box<ImageResponder+Send> { - // This has a crazy signature because the image cache needs to - // make multiple copies of the callback, and the dom event - // channel is not a copyable type, so this is actually a - // little factory to produce callbacks - box LayoutImageResponder { - id: self.id.clone(), - script_chan: self.script_chan.clone(), - } as Box<ImageResponder+Send> - } - /// Handles a message to destroy layout data. Layout data must be destroyed on *this* task - /// because it contains local managed pointers. - unsafe fn handle_reap_layout_data(&self, layout_data: LayoutDataRef) { - let mut layout_data_ref = layout_data.borrow_mut(); - let _: Option<LayoutDataWrapper> = mem::transmute( - mem::replace(&mut *layout_data_ref, None)); + if mouse_over_list.is_empty() { + Err(()) + } else { + Ok(MouseOverResponse(mouse_over_list)) + } } } diff --git a/src/components/script/dom/node.rs b/src/components/script/dom/node.rs index 33a8674d97e..bf629122605 100644 --- a/src/components/script/dom/node.rs +++ b/src/components/script/dom/node.rs @@ -43,7 +43,7 @@ use dom::virtualmethods::{VirtualMethods, vtable_for}; use dom::window::Window; use geom::rect::Rect; use html::hubbub_html_parser::build_element_from_tag; -use layout_interface::{ContentBoxQuery, ContentBoxResponse, ContentBoxesQuery, ContentBoxesResponse, +use layout_interface::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC, LayoutChan, ReapLayoutDataMsg, TrustedNodeAddress, UntrustedNodeAddress}; use servo_util::geometry::Au; use servo_util::str::{DOMString, null_str_as_empty}; @@ -586,18 +586,17 @@ impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> { fn get_bounding_content_box(&self) -> Rect<Au> { let window = window_from_node(self).root(); let page = window.deref().page(); - let (chan, port) = channel(); let addr = self.to_trusted_node_address(); - let ContentBoxResponse(rect) = page.query_layout(ContentBoxQuery(addr, chan), port); + + let ContentBoxResponse(rect) = page.layout_rpc.content_box(addr); rect } fn get_content_boxes(&self) -> Vec<Rect<Au>> { let window = window_from_node(self).root(); let page = window.deref().page(); - let (chan, port) = channel(); let addr = self.to_trusted_node_address(); - let ContentBoxesResponse(rects) = page.query_layout(ContentBoxesQuery(addr, chan), port); + let ContentBoxesResponse(rects) = page.layout_rpc.content_boxes(addr); rects } diff --git a/src/components/script/layout_interface.rs b/src/components/script/layout_interface.rs index f36c3e83167..1e5e23f9c9a 100644 --- a/src/components/script/layout_interface.rs +++ b/src/components/script/layout_interface.rs @@ -25,8 +25,6 @@ use url::Url; use serialize::{Encodable, Encoder}; /// Asynchronous messages that script can send to layout. -/// -/// FIXME(pcwalton): I think this should probably be merged with `LayoutQuery` below. pub enum Msg { /// Adds the given stylesheet to the document. AddStylesheetMsg(Stylesheet), @@ -34,10 +32,8 @@ pub enum Msg { /// Requests a reflow. ReflowMsg(Box<Reflow>), - /// Performs a synchronous layout request. - /// - /// FIXME(pcwalton): As noted below, this isn't very type safe. - QueryMsg(LayoutQuery), + /// Get an RPC interface. + GetRPCMsg(Sender<Box<LayoutRPC + Send>>), /// Destroys layout data associated with a DOM node. /// @@ -55,14 +51,21 @@ pub enum Msg { } /// Synchronous messages that script can send to layout. -pub enum LayoutQuery { +/// +/// In general, you should use messages to talk to Layout. Use the RPC interface +/// if and only if the work is +/// +/// 1) read-only with respect to LayoutTaskData, +/// 2) small, +// 3) and really needs to be fast. +pub trait LayoutRPC { /// Requests the dimensions of the content box, as in the `getBoundingClientRect()` call. - ContentBoxQuery(TrustedNodeAddress, Sender<ContentBoxResponse>), + fn content_box(&self, node: TrustedNodeAddress) -> ContentBoxResponse; /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call. - ContentBoxesQuery(TrustedNodeAddress, Sender<ContentBoxesResponse>), + fn content_boxes(&self, node: TrustedNodeAddress) -> ContentBoxesResponse; /// Requests the node containing the point of interest - HitTestQuery(TrustedNodeAddress, Point2D<f32>, Sender<Result<HitTestResponse, ()>>), - MouseOverQuery(TrustedNodeAddress, Point2D<f32>, Sender<Result<MouseOverResponse, ()>>), + fn hit_test(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()>; + fn mouse_over(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()>; } /// The address of a node known to be valid. These must only be sent from content -> layout, diff --git a/src/components/script/page.rs b/src/components/script/page.rs index 227a6e66e3c..633a7de204b 100644 --- a/src/components/script/page.rs +++ b/src/components/script/page.rs @@ -13,8 +13,8 @@ use dom::element::{Element, AttributeHandlers}; use dom::node::{Node, NodeHelpers}; use dom::window::Window; use layout_interface::{DocumentDamage}; -use layout_interface::{DocumentDamageLevel, HitTestQuery, HitTestResponse, LayoutQuery, MouseOverQuery, MouseOverResponse}; -use layout_interface::{LayoutChan, QueryMsg}; +use layout_interface::{DocumentDamageLevel, HitTestResponse, MouseOverResponse}; +use layout_interface::{GetRPCMsg, LayoutChan, LayoutRPC}; use layout_interface::{Reflow, ReflowGoal, ReflowMsg}; use layout_interface::UntrustedNodeAddress; use script_traits::ScriptControlChan; @@ -54,6 +54,9 @@ pub struct Page { /// A handle for communicating messages to the layout task. pub layout_chan: Untraceable<LayoutChan>, + /// A handle to perform RPC calls into the layout, quickly. + pub layout_rpc: Untraceable<Box<LayoutRPC>>, + /// The port that we will use to join layout. If this is `None`, then layout is not running. pub layout_join_port: Untraceable<RefCell<Option<Receiver<()>>>>, @@ -126,11 +129,18 @@ impl Page { dom_static: GlobalStaticData(), js_context: Untraceable::new(js_context), }; + let layout_rpc: Box<LayoutRPC> = { + let (rpc_send, rpc_recv) = channel(); + let LayoutChan(ref lchan) = layout_chan; + lchan.send(GetRPCMsg(rpc_send)); + rpc_recv.recv() + }; Page { id: id, subpage_id: subpage_id, frame: Traceable::new(RefCell::new(None)), layout_chan: Untraceable::new(layout_chan), + layout_rpc: Untraceable::new(layout_rpc), layout_join_port: Untraceable::new(RefCell::new(None)), damage: Traceable::new(RefCell::new(None)), window_size: Traceable::new(Cell::new(window_size)), @@ -255,6 +265,10 @@ impl Page { self.url().get_ref().ref0().clone() } + // FIXME(cgaebel): join_layout is racey. What if the compositor triggers a + // reflow between the "join complete" message and returning from this + // function? + /// Sends a ping to layout and waits for the response. The response will arrive when the /// layout task has finished any pending request messages. pub fn join_layout(&self) { @@ -281,17 +295,6 @@ impl Page { } } - /// Sends the given query to layout. - pub fn query_layout<T: Send>(&self, - query: LayoutQuery, - response_port: Receiver<T>) - -> T { - self.join_layout(); - let LayoutChan(ref chan) = *self.layout_chan; - chan.send(QueryMsg(query)); - response_port.recv() - } - /// Reflows the page if it's possible to do so. 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. @@ -382,8 +385,7 @@ impl Page { } let root = root.unwrap(); let root: &JSRef<Node> = NodeCast::from_ref(&*root); - let (chan, port) = channel(); - let address = match self.query_layout(HitTestQuery(root.to_trusted_node_address(), *point, chan), port) { + let address = match self.layout_rpc.hit_test(root.to_trusted_node_address(), *point) { Ok(HitTestResponse(node_address)) => { Some(node_address) } @@ -404,8 +406,7 @@ impl Page { } let root = root.unwrap(); let root: &JSRef<Node> = NodeCast::from_ref(&*root); - let (chan, port) = channel(); - let address = match self.query_layout(MouseOverQuery(root.to_trusted_node_address(), *point, chan), port) { + let address = match self.layout_rpc.mouse_over(root.to_trusted_node_address(), *point) { Ok(MouseOverResponse(node_address)) => { Some(node_address) } |