Store pointer to Content in JSContext private slot; add methods for pure folding over a flow's boxes; Set up layout query protocol; Hook up HTMLImageElement.width to use content->layout blocking queries.

This commit is contained in:
Brian J. Burg 2012-09-25 16:07:38 -07:00
parent 0b42b7f537
commit b9b3895a8a
6 changed files with 248 additions and 109 deletions

View file

@ -3,9 +3,10 @@ The content task is the main task that runs JavaScript and spawns layout
tasks. tasks.
*/ */
export ContentTask; export Content, ContentTask;
export ControlMsg, ExecuteMsg, ParseMsg, ExitMsg, Timer; export ControlMsg, ExecuteMsg, ParseMsg, ExitMsg, Timer;
export PingMsg, PongMsg; export PingMsg, PongMsg;
export task_from_context;
use std::arc::{ARC, clone}; use std::arc::{ARC, clone};
use comm::{Port, Chan, listen, select2}; use comm::{Port, Chan, listen, select2};
@ -43,8 +44,8 @@ use task::{task, SingleThreaded};
use js::glue::bindgen::RUST_JSVAL_TO_OBJECT; use js::glue::bindgen::RUST_JSVAL_TO_OBJECT;
use js::JSVAL_NULL; use js::JSVAL_NULL;
use js::jsapi::jsval; use js::jsapi::{JSContext, jsval};
use js::jsapi::bindgen::JS_CallFunctionValue; use js::jsapi::bindgen::{JS_CallFunctionValue, JS_GetContextPrivate};
use ptr::null; use ptr::null;
enum ControlMsg { enum ControlMsg {
@ -65,25 +66,13 @@ fn ContentTask<S: Compositor Send Copy>(layout_task: LayoutTask,
resource_task: ResourceTask, resource_task: ResourceTask,
img_cache_task: ImageCacheTask) -> ContentTask { img_cache_task: ImageCacheTask) -> ContentTask {
do task().sched_mode(SingleThreaded).spawn_listener::<ControlMsg> |from_master| { do task().sched_mode(SingleThreaded).spawn_listener::<ControlMsg> |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 struct Content {
#[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<C:Compositor> {
compositor: C,
layout_task: LayoutTask, layout_task: LayoutTask,
image_cache_task: ImageCacheTask, image_cache_task: ImageCacheTask,
from_master: comm::Port<ControlMsg>, from_master: comm::Port<ControlMsg>,
@ -102,29 +91,26 @@ struct Content<C:Compositor> {
compartment: Option<compartment>, compartment: Option<compartment>,
} }
fn Content<C:Compositor>(layout_task: LayoutTask, fn Content(layout_task: LayoutTask,
compositor: C,
from_master: Port<ControlMsg>, from_master: Port<ControlMsg>,
resource_task: ResourceTask, resource_task: ResourceTask,
img_cache_task: ImageCacheTask) -> Content<C> { img_cache_task: ImageCacheTask) -> Content {
let jsrt = jsrt(); let jsrt = jsrt();
let cx = jsrt.cx(); let cx = jsrt.cx();
let event_port = Port(); let event_port = Port();
compositor.add_event_listener(event_port.chan());
cx.set_default_options_and_version(); cx.set_default_options_and_version();
cx.set_logging_error_reporter(); cx.set_logging_error_reporter();
let compartment = match cx.new_compartment(global_class) { let compartment = match cx.new_compartment(global_class) {
Ok(c) => Some(c), Ok(c) => Some(c),
Err(()) => None Err(()) => None
}; };
Content { let content = Content {
layout_task : layout_task, layout_task : layout_task,
image_cache_task : img_cache_task, image_cache_task : img_cache_task,
compositor : compositor,
from_master : from_master, from_master : from_master,
event_port : event_port, event_port : event_port,
@ -138,10 +124,18 @@ fn Content<C:Compositor>(layout_task: LayoutTask,
resource_task : resource_task, resource_task : resource_task,
compartment : compartment compartment : compartment
} };
cx.set_cx_private(ptr::to_unsafe_ptr(&content) as *());
content
} }
impl<C:Compositor> Content<C> { fn task_from_context(cx: *JSContext) -> &Content unsafe {
cast::reinterpret_cast(&JS_GetContextPrivate(cx))
}
impl Content {
fn start() { fn start() {
while self.handle_msg(select2(self.from_master, self.event_port)) { while self.handle_msg(select2(self.from_master, self.event_port)) {
@ -159,7 +153,7 @@ impl<C:Compositor> Content<C> {
fn handle_control_msg(control_msg: ControlMsg) -> bool { fn handle_control_msg(control_msg: ControlMsg) -> bool {
match control_msg { match control_msg {
ParseMsg(url) => { 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 // Note: we can parse the next document in parallel
// with any previous documents. // with any previous documents.
@ -174,9 +168,9 @@ impl<C:Compositor> Content<C> {
let js_scripts = result.js_port.recv(); let js_scripts = result.js_port.recv();
// Apply the css rules to the dom tree: // 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 document = Document(root, self.scope, css_rules);
let window = Window(self.from_master); let window = Window(self.from_master);
@ -215,11 +209,11 @@ impl<C:Compositor> Content<C> {
ExecuteMsg(url) => { 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)) { match read_whole_file(&Path(url.path)) {
Err(msg) => { 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) => { Ok(bytes) => {
let compartment = option::expect(self.compartment, ~"TODO error checking"); let compartment = option::expect(self.compartment, ~"TODO error checking");
@ -237,12 +231,31 @@ impl<C:Compositor> Content<C> {
} }
} }
/**
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) { 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 // Now, join the layout so that they will see the latest
// changes we have made. // changes we have made.
join_layout(self.scope, self.layout_task); self.join_layout();
// Send new document and relevant styles to layout // Send new document and relevant styles to layout
// FIXME: Put CSS rules in an arc or something. // FIXME: Put CSS rules in an arc or something.
@ -253,10 +266,23 @@ impl<C:Compositor> Content<C> {
self.scope.reader_forked(); 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 { fn handle_event(event: Event) -> bool {
match event { match event {
ResizeEvent(new_width, new_height) => { 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 { match copy self.document {
None => { None => {
// Nothing to do. // Nothing to do.
@ -269,7 +295,7 @@ impl<C:Compositor> Content<C> {
return true; return true;
} }
ReflowEvent => { ReflowEvent => {
#debug("content got reflow event"); debug!("content got reflow event");
match copy self.document { match copy self.document {
None => { None => {
// Nothing to do. // Nothing to do.

View file

@ -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::{JSContext, jsval, JSObject, JSBool, jsid, JSClass, JSFreeOp, JSPropertySpec};
use js::jsapi::bindgen::{JS_ValueToString, JS_GetStringCharsZAndLength, JS_ReportError, use js::jsapi::bindgen::{JS_ValueToString, JS_GetStringCharsZAndLength, JS_ReportError,
JS_GetReservedSlot, JS_SetReservedSlot, JS_NewStringCopyN, JS_GetReservedSlot, JS_SetReservedSlot, JS_NewStringCopyN,
JS_DefineFunctions, JS_DefineProperty, JS_GetContextPrivate}; JS_DefineFunctions, JS_DefineProperty};
use js::jsapi::bindgen::*; use js::jsapi::bindgen::*;
use js::glue::bindgen::*; use js::glue::bindgen::*;
use js::crust::{JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ConvertStub}; 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::node::{Node, NodeScope, Element};
use dom::element::*; use dom::element::*;
use node::NodeBundle; use node::NodeBundle;
@ -70,20 +72,20 @@ extern fn HTMLImageElement_getWidth(cx: *JSContext, _argc: c_uint, vp: *mut jsva
} }
let bundle = unwrap(obj); 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 { match nd.kind {
~Element(ed) => { ~Element(ed) => {
match ed.kind { match ed.kind {
~HTMLImageElement(*) => { ~HTMLImageElement(*) => {
// TODO: this should actually come from rendered dimensions! let content : &Content = task_from_context(cx);
match ed.get_attr(~"width") { match content.query_layout(layout_task::ContentBox(node)) {
Some(s) => match int::from_str(s) { Ok(rect) => rect.width,
Some(w) => au::from_px(w), Err(()) => 0,
None => au(0) /* failed to parse a number */ }
}, // TODO: if nothing is being rendered(?), return zero dimensions
None => au(0) /* no width attr. */
} }
},
_ => fail ~"why is this not an image element?" _ => 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( *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; return 1;
} }

View file

@ -13,6 +13,7 @@ pub struct ImageHolder {
// occasionally while get_image is being called // occasionally while get_image is being called
mut url : Option<Url>, mut url : Option<Url>,
mut image : Option<ARC<~Image>>, mut image : Option<ARC<~Image>>,
mut cached_size: Size2D<int>,
image_cache_task: ImageCacheTask, image_cache_task: ImageCacheTask,
reflow_cb: fn~(), reflow_cb: fn~(),
@ -23,6 +24,7 @@ fn ImageHolder(url : &Url, image_cache_task: ImageCacheTask, cb: fn~()) -> Image
let holder = ImageHolder { let holder = ImageHolder {
url : Some(copy *url), url : Some(copy *url),
image : None, image : None,
cached_size : Size2D(0,0),
image_cache_task : image_cache_task, image_cache_task : image_cache_task,
reflow_cb : copy cb, reflow_cb : copy cb,
}; };
@ -39,13 +41,26 @@ fn ImageHolder(url : &Url, image_cache_task: ImageCacheTask, cb: fn~()) -> Image
} }
impl ImageHolder { 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<int> {
self.cached_size
}
/** Query and update current image size */
fn get_size() -> Option<Size2D<int>> { fn get_size() -> Option<Size2D<int>> {
debug!("get_size() %?", self.url); debug!("get_size() %?", self.url);
match self.get_image() { match self.get_image() {
Some(img) => { Some(img) => {
let img_ref = get(&img); let img_ref = get(&img);
Some(Size2D(img_ref.width as int, self.cached_size = Size2D(img_ref.width as int,
img_ref.height as int)) img_ref.height as int);
Some(copy self.cached_size)
}, },
None => None None => None
} }

View file

@ -183,10 +183,10 @@ impl @RenderBox {
/* The box formed by the content edge, as defined in CSS 2.1 Section 8.1. /* The box formed by the content edge, as defined in CSS 2.1 Section 8.1.
Coordinates are relative to the owning flow. */ Coordinates are relative to the owning flow. */
fn content_box() -> Rect<au> { pure fn content_box() -> Rect<au> {
match self.kind { match self.kind {
ImageBox(i) => { ImageBox(i) => {
let size = i.get_size().get_default(Size2D(0,0)); let size = i.size();
Rect { Rect {
origin: copy self.data.position.origin, origin: copy self.data.position.origin,
size: Size2D(au::from_px(size.width), size: Size2D(au::from_px(size.width),

View file

@ -140,7 +140,25 @@ impl @FlowContext {
// Actual methods that do not require much flow-specific logic // Actual methods that do not require much flow-specific logic
impl @FlowContext { impl @FlowContext {
fn iter_boxes_for_node<T>(node: Node, cb: pure fn&(@RenderBox) -> T) { pure fn foldl_boxes_for_node<B: Copy>(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<T>(node: Node, cb: pure fn&(@RenderBox) -> T) {
match self.kind { match self.kind {
RootFlow(d) => match d.box { RootFlow(d) => match d.box {
Some(box) if box.node == node => { cb(box); }, Some(box) if box.node == node => { cb(box); },

View file

@ -19,6 +19,7 @@ use gfx::render_task;
use layout::box::RenderBox; use layout::box::RenderBox;
use layout::box_builder::LayoutTreeBuilder; use layout::box_builder::LayoutTreeBuilder;
use layout::context::LayoutContext; use layout::context::LayoutContext;
use opt = core::option;
use render_task::RenderTask; use render_task::RenderTask;
use resource::image_cache_task::ImageCacheTask; use resource::image_cache_task::ImageCacheTask;
use servo_text::font_cache::FontCache; use servo_text::font_cache::FontCache;
@ -31,43 +32,118 @@ use task::*;
type LayoutTask = Chan<Msg>; type LayoutTask = Chan<Msg>;
enum LayoutQuery {
ContentBox(Node)
}
type LayoutQueryResponse = Result<LayoutQueryResponse_, ()>;
enum LayoutQueryResponse_ {
ContentSize(Size2D<int>)
}
enum Msg { enum Msg {
BuildMsg(Node, ARC<Stylesheet>, Url, Chan<Event>), BuildMsg(Node, ARC<Stylesheet>, Url, Chan<Event>),
PingMsg(Chan<content_task::PingMsg>), PingMsg(Chan<content_task::PingMsg>),
QueryMsg(LayoutQuery, Chan<LayoutQueryResponse>),
ExitMsg ExitMsg
} }
fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> LayoutTask { fn LayoutTask(render_task: RenderTask,
do spawn_listener::<Msg>|request| { img_cache_task: ImageCacheTask) -> LayoutTask {
do spawn_listener::<Msg>|from_content| {
Layout(render_task, img_cache_task, from_content).start();
}
}
// This just keeps our dom aux objects alive struct Layout {
let layout_data_refs = DVec(); render_task: RenderTask,
let font_cache = FontCache(); image_cache_task: ImageCacheTask,
from_content: comm::Port<Msg>,
loop { font_cache: @FontCache,
// This is used to root auxilliary RCU reader data
layout_refs: DVec<@LayoutData>
}
fn Layout(render_task: RenderTask,
image_cache_task: ImageCacheTask,
from_content: comm::Port<Msg>) -> Layout {
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<LayoutQueryResponse>) {
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<Rect<au>> = 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())
}
};
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<Msg>) -> bool {
match request.recv() { match request.recv() {
PingMsg(ping_channel) => ping_channel.send(content_task::PongMsg), PingMsg(ping_channel) => ping_channel.send(content_task::PongMsg),
QueryMsg(query, chan) => self.handle_query(query, chan),
ExitMsg => { ExitMsg => {
debug!("layout: ExitMsg received"); debug!("layout: ExitMsg received");
break; return false
} },
BuildMsg(node, styles, doc_url, event_chan) => { BuildMsg(node, styles, doc_url, to_content) => {
debug!("layout: received layout request for: %s", doc_url.to_str()); debug!("layout: received layout request for: %s", doc_url.to_str());
debug!("layout: parsed Node tree"); debug!("layout: parsed Node tree");
node.dump(); node.dump();
let layout_ctx = LayoutContext { let layout_ctx = LayoutContext {
image_cache: image_cache_task, image_cache: self.image_cache_task,
font_cache: font_cache, font_cache: self.font_cache,
doc_url: doc_url, doc_url: doc_url,
reflow_cb: || event_chan.send(ReflowEvent), reflow_cb: || to_content.send(ReflowEvent),
// TODO: obtain screen size from a real data source // 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))) screen_size: Rect(Point2D(au(0), au(0)), Size2D(au::from_px(800), au::from_px(600)))
}; };
do util::time::time(~"layout") { do util::time::time(~"layout") {
// TODO: this is dumb. we don't need 3 separate traversals. // TODO: this is dumb. we don't need 3 separate traversals.
node.initialize_style_for_subtree(&layout_ctx, &layout_data_refs); node.initialize_style_for_subtree(&layout_ctx, &self.layout_refs);
node.recompute_style_for_subtree(&layout_ctx, styles); node.recompute_style_for_subtree(&layout_ctx, styles);
/* resolve styles (convert relative values) down the node tree */ /* resolve styles (convert relative values) down the node tree */
apply_style(&layout_ctx, node, layout_ctx.reflow_cb); apply_style(&layout_ctx, node, layout_ctx.reflow_cb);
@ -93,10 +169,12 @@ fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> Layo
// TODO: set options on the builder before building // TODO: set options on the builder before building
// TODO: be smarter about what needs painting // TODO: be smarter about what needs painting
layout_root.build_display_list(&builder, &copy layout_root.data.position, &dlist); layout_root.build_display_list(&builder, &copy layout_root.data.position, &dlist);
render_task.send(render_task::RenderMsg(dlist)); self.render_task.send(render_task::RenderMsg(dlist));
} } // time(layout)
} } // BuildMsg
} } // match
}
true
} }
} }