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.
*/
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<S: Compositor Send Copy>(layout_task: LayoutTask,
resource_task: ResourceTask,
img_cache_task: ImageCacheTask) -> ContentTask {
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
#[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,
struct Content {
layout_task: LayoutTask,
image_cache_task: ImageCacheTask,
from_master: comm::Port<ControlMsg>,
@ -102,29 +91,26 @@ struct Content<C:Compositor> {
compartment: Option<compartment>,
}
fn Content<C:Compositor>(layout_task: LayoutTask,
compositor: C,
from_master: Port<ControlMsg>,
resource_task: ResourceTask,
img_cache_task: ImageCacheTask) -> Content<C> {
fn Content(layout_task: LayoutTask,
from_master: Port<ControlMsg>,
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<C:Compositor>(layout_task: LayoutTask,
resource_task : resource_task,
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() {
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 {
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<C:Compositor> Content<C> {
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<C:Compositor> Content<C> {
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<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) {
#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<C:Compositor> Content<C> {
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<C:Compositor> Content<C> {
return true;
}
ReflowEvent => {
#debug("content got reflow event");
debug!("content got reflow event");
match copy self.document {
None => {
// 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::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;
}

View file

@ -13,6 +13,7 @@ pub struct ImageHolder {
// occasionally while get_image is being called
mut url : Option<Url>,
mut image : Option<ARC<~Image>>,
mut cached_size: Size2D<int>,
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<int> {
self.cached_size
}
/** Query and update current image size */
fn get_size() -> Option<Size2D<int>> {
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
}

View file

@ -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<au> {
pure fn content_box() -> Rect<au> {
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),

View file

@ -140,7 +140,25 @@ impl @FlowContext {
// Actual methods that do not require much flow-specific logic
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 {
RootFlow(d) => match d.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_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<Msg>;
enum LayoutQuery {
ContentBox(Node)
}
type LayoutQueryResponse = Result<LayoutQueryResponse_, ()>;
enum LayoutQueryResponse_ {
ContentSize(Size2D<int>)
}
enum Msg {
BuildMsg(Node, ARC<Stylesheet>, Url, Chan<Event>),
PingMsg(Chan<content_task::PingMsg>),
QueryMsg(LayoutQuery, Chan<LayoutQueryResponse>),
ExitMsg
}
fn LayoutTask(render_task: RenderTask, image_cache_task: ImageCacheTask) -> LayoutTask {
do spawn_listener::<Msg>|request| {
fn LayoutTask(render_task: RenderTask,
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
let layout_data_refs = DVec();
let font_cache = FontCache();
struct Layout {
render_task: RenderTask,
image_cache_task: ImageCacheTask,
from_content: comm::Port<Msg>,
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<Msg>) -> 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<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())
}
};
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, &copy 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<Msg>) -> 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, &copy layout_root.data.position, &dlist);
self.render_task.send(render_task::RenderMsg(dlist));
} // time(layout)
} // BuildMsg
} // match
true
}
}