diff --git a/src/servo/content/content_task.rs b/src/servo/content/content_task.rs index ee1466a8233..84f9da7be04 100644 --- a/src/servo/content/content_task.rs +++ b/src/servo/content/content_task.rs @@ -228,7 +228,8 @@ pub impl Content { debug!("js_scripts: %?", js_scripts); let window = Window(self.control_chan.clone(), - self.event_chan.clone()); + self.event_chan.clone(), + ptr::to_mut_unsafe_ptr(&mut *self)); //FIXME store this safely let document = Document(root, Some(window)); do root.with_mut_node |node| { @@ -344,7 +345,7 @@ pub impl Content { } fn query_layout(&mut self, query: layout_task::LayoutQuery) -> layout_task::LayoutQueryResponse { - self.relayout(self.document.get(), &(copy self.doc_url).get()); + //self.relayout(self.document.get(), &(copy self.doc_url).get()); self.join_layout(); let (response_port, response_chan) = comm::stream(); diff --git a/src/servo/dom/bindings/element.rs b/src/servo/dom/bindings/element.rs index ae0599afa65..0fc0728e8a6 100644 --- a/src/servo/dom/bindings/element.rs +++ b/src/servo/dom/bindings/element.rs @@ -86,6 +86,11 @@ pub fn init(compartment: @mut Compartment) { nargs: 0, flags: 0, selfHostedName: null()}, + JSFunctionSpec {name: compartment.add_name(~"getBoundingClientRect"), + call: JSNativeWrapper {op: getBoundingClientRect, info: null()}, + nargs: 0, + flags: 0, + selfHostedName: null()}, JSFunctionSpec {name: compartment.add_name(~"setAttribute"), call: JSNativeWrapper {op: setAttribute, info: null()}, nargs: 0, @@ -137,7 +142,27 @@ extern fn getClientRects(cx: *JSContext, _argc: c_uint, vp: *JSVal) -> JSBool { JS_SET_RVAL(cx, vp, JSVAL_NULL); } else { let cache = node.get_wrappercache(); - let rval = rval.get() as @mut CacheableWrapper; + let rval = rval.get() as @mut CacheableWrapper; + assert!(WrapNewBindingObject(cx, cache.get_wrapper(), + rval, + cast::transmute(vp))); + } + return 1; + } +} + +extern fn getBoundingClientRect(cx: *JSContext, _argc: c_uint, vp: *JSVal) -> JSBool { + unsafe { + let obj = JS_THIS_OBJECT(cx, vp); + let mut node = unwrap(obj); + let rval = do node.with_imm_element |elem| { + elem.getBoundingClientRect() + }; + if rval.is_none() { + JS_SET_RVAL(cx, vp, JSVAL_NULL); + } else { + let cache = node.get_wrappercache(); + let rval = rval.get() as @mut CacheableWrapper; assert!(WrapNewBindingObject(cx, cache.get_wrapper(), rval, cast::transmute(vp))); @@ -192,7 +217,12 @@ extern fn HTMLImageElement_getWidth(cx: *JSContext, _argc: c_uint, vp: *mut JSVa ElementNodeTypeId(HTMLImageElementTypeId) => { let content = task_from_context(cx); match (*content).query_layout(layout_task::ContentBox(node)) { - Ok(rect) => rect.width, + Ok(rect) => { + match rect { + layout_task::ContentRect(rect) => rect.size.width.to_px(), + _ => fail!(~"unexpected layout reply") + } + } Err(()) => 0 } // TODO: if nothing is being rendered(?), return zero dimensions diff --git a/src/servo/dom/clientrectlist.rs b/src/servo/dom/clientrectlist.rs index ab25d5f4764..cd543f1d6ce 100644 --- a/src/servo/dom/clientrectlist.rs +++ b/src/servo/dom/clientrectlist.rs @@ -3,14 +3,14 @@ use dom::bindings::utils::WrapperCache; pub struct ClientRectList { wrapper: WrapperCache, - rects: ~[(f32, f32, f32, f32)] + rects: ~[@mut ClientRect] } pub impl ClientRectList { - fn new() -> @mut ClientRectList { + fn new(rects: ~[@mut ClientRect]) -> @mut ClientRectList { let list = @mut ClientRectList { wrapper: WrapperCache::new(), - rects: ~[(5.6, 80.2, 3.7, 4.8), (800.1, 8001.1, -50.000001, -45.01)] + rects: rects }; list.init_wrapper(); list @@ -22,8 +22,7 @@ pub impl ClientRectList { fn Item(&self, index: u32) -> Option<@mut ClientRect> { if index < self.rects.len() as u32 { - let (top, bottom, left, right) = self.rects[index]; - Some(ClientRect::new(top, bottom, left, right)) + Some(self.rects[index]) } else { None } @@ -33,4 +32,4 @@ pub impl ClientRectList { *found = index < self.rects.len() as u32; self.Item(index) } -} \ No newline at end of file +} diff --git a/src/servo/dom/element.rs b/src/servo/dom/element.rs index 489e9bd7a6b..f6c94bb3347 100644 --- a/src/servo/dom/element.rs +++ b/src/servo/dom/element.rs @@ -7,9 +7,12 @@ // use dom::node::{ElementNodeTypeId, Node}; +use dom::clientrect::ClientRect; use dom::clientrectlist::ClientRectList; use dom::bindings::utils::DOMString; +use layout::layout_task; + use core::str::eq_slice; use core::cell::Cell; use std::net::url::Url; @@ -157,7 +160,80 @@ pub impl<'self> Element { } fn getClientRects(&self) -> Option<@mut ClientRectList> { - Some(ClientRectList::new()) + let rects = match self.parent.owner_doc { + Some(doc) => { + match doc.window { + Some(win) => { + let node = self.parent.abstract.get(); + assert!(node.is_element()); + let content = unsafe { &mut *win.content_task }; + match content.query_layout(layout_task::ContentBoxes(node)) { + Ok(rects) => match rects { + layout_task::ContentRects(rects) => + do rects.map |r| { + ClientRect::new( + r.origin.y.to_f32(), + (r.origin.y + r.size.height).to_f32(), + r.origin.x.to_f32(), + (r.origin.x + r.size.width).to_f32()) + }, + _ => fail!(~"unexpected layout reply") + }, + Err(()) => { + debug!("layout query error"); + ~[] + } + } + } + None => { + debug!("no window"); + ~[] + } + } + } + None => { + debug!("no document"); + ~[] + } + }; + Some(ClientRectList::new(rects)) + } + + fn getBoundingClientRect(&self) -> Option<@mut ClientRect> { + match self.parent.owner_doc { + Some(doc) => { + match doc.window { + Some(win) => { + let node = self.parent.abstract.get(); + assert!(node.is_element()); + let content = unsafe { &mut *win.content_task }; + match content.query_layout(layout_task::ContentBox(node)) { + Ok(rect) => match rect { + layout_task::ContentRect(rect) => + Some(ClientRect::new( + rect.origin.y.to_f32(), + (rect.origin.y + rect.size.height).to_f32(), + rect.origin.x.to_f32(), + (rect.origin.x + rect.size.width).to_f32())), + _ => fail!(~"unexpected layout result") + }, + Err(()) => { + debug!("error querying layout"); + None + } + } + } + None => { + debug!("no window"); + None + } + } + } + None => { + debug!("no document"); + None + } + } } } diff --git a/src/servo/dom/node.rs b/src/servo/dom/node.rs index 050eb85feac..446dc036d62 100644 --- a/src/servo/dom/node.rs +++ b/src/servo/dom/node.rs @@ -43,6 +43,8 @@ pub struct Node { wrapper: WrapperCache, type_id: NodeTypeId, + abstract: Option, + parent_node: Option, first_child: Option, last_child: Option, @@ -238,15 +240,27 @@ pub impl AbstractNode { fn transmute(self, f: &fn(&T) -> R) -> R { unsafe { + let node_box: *mut bindings::utils::rust_box = transmute(self.obj); + let node = &mut (*node_box).payload; + let old = node.abstract; + node.abstract = Some(self); let box: *bindings::utils::rust_box = transmute(self.obj); - f(&(*box).payload) + let rv = f(&(*box).payload); + node.abstract = old; + rv } } fn transmute_mut(self, f: &fn(&mut T) -> R) -> R { unsafe { + let node_box: *mut bindings::utils::rust_box = transmute(self.obj); + let node = &mut (*node_box).payload; + let old = node.abstract; + node.abstract = Some(self); let box: *bindings::utils::rust_box = transmute(self.obj); - f(cast::transmute(&(*box).payload)) + let rv = f(cast::transmute(&(*box).payload)); + node.abstract = old; + rv } } @@ -381,6 +395,8 @@ impl Node { wrapper: WrapperCache::new(), type_id: type_id, + abstract: None, + parent_node: None, first_child: None, last_child: None, diff --git a/src/servo/dom/window.rs b/src/servo/dom/window.rs index 6775c1a26da..7943f8b6bf8 100644 --- a/src/servo/dom/window.rs +++ b/src/servo/dom/window.rs @@ -2,7 +2,7 @@ * 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/. */ -use content::content_task::{ControlMsg, Timer, ExitMsg, global_content}; +use content::content_task::{ControlMsg, Timer, ExitMsg, global_content, Content}; use dom::bindings::utils::WrapperCache; use dom::bindings::window; use dom::event::Event; @@ -19,9 +19,12 @@ pub enum TimerControlMsg { TimerMessage_TriggerExit //XXXjdm this is just a quick hack to talk to the content task } +//FIXME If we're going to store the content task, find a way to do so safely. Currently it's +// only used for querying layout from arbitrary content. pub struct Window { timer_chan: Chan, dom_event_chan: SharedChan, + content_task: *mut Content, wrapper: WrapperCache } @@ -81,7 +84,8 @@ pub impl Window { } pub fn Window(content_chan: comm::SharedChan, - dom_event_chan: comm::SharedChan) -> @mut Window { + dom_event_chan: comm::SharedChan, + content_task: *mut Content) -> @mut Window { let win = @mut Window { wrapper: WrapperCache::new(), @@ -96,7 +100,8 @@ pub fn Window(content_chan: comm::SharedChan, TimerMessage_TriggerExit => content_chan.send(ExitMsg) } } - } + }, + content_task: content_task }; let compartment = global_content().compartment.get(); window::create(compartment, win); diff --git a/src/servo/layout/box_builder.rs b/src/servo/layout/box_builder.rs index ff6d35ff4c1..edaeaf5c00e 100644 --- a/src/servo/layout/box_builder.rs +++ b/src/servo/layout/box_builder.rs @@ -230,16 +230,17 @@ impl BuilderContext { priv fn create_child_flow_of_type(&self, flow_type: FlowContextType, - builder: &mut LayoutTreeBuilder) -> BuilderContext { - let new_flow = builder.make_flow(flow_type); + builder: &mut LayoutTreeBuilder, + node: AbstractNode) -> BuilderContext { + let new_flow = builder.make_flow(flow_type, node); self.attach_child_flow(new_flow); BuilderContext::new(@mut BoxGenerator::new(new_flow)) } - priv fn make_inline_collector(&mut self, builder: &mut LayoutTreeBuilder) -> BuilderContext { + priv fn make_inline_collector(&mut self, builder: &mut LayoutTreeBuilder, node: AbstractNode) -> BuilderContext { debug!("BuilderContext: making new inline collector flow"); - let new_flow = builder.make_flow(Flow_Inline); + let new_flow = builder.make_flow(Flow_Inline, node); let new_generator = @mut BoxGenerator::new(new_flow); self.inline_collector = Some(new_generator); @@ -248,10 +249,10 @@ impl BuilderContext { BuilderContext::new(new_generator) } - priv fn get_inline_collector(&mut self, builder: &mut LayoutTreeBuilder) -> BuilderContext { + priv fn get_inline_collector(&mut self, builder: &mut LayoutTreeBuilder, node: AbstractNode) -> BuilderContext { match copy self.inline_collector { Some(collector) => BuilderContext::new(collector), - None => self.make_inline_collector(builder) + None => self.make_inline_collector(builder, node) } } @@ -278,18 +279,18 @@ impl BuilderContext { // If this is the root node, then use the root flow's // context. Otherwise, make a child block context. match node.parent_node() { - Some(_) => { self.create_child_flow_of_type(Flow_Block, builder) } + Some(_) => { self.create_child_flow_of_type(Flow_Block, builder, node) } None => { self.clone() }, } }, (CSSDisplayBlock, @BlockFlow(*)) => { self.clear_inline_collector(); - self.create_child_flow_of_type(Flow_Block, builder) + self.create_child_flow_of_type(Flow_Block, builder, node) }, (CSSDisplayInline, @InlineFlow(*)) => self.clone(), (CSSDisplayInlineBlock, @InlineFlow(*)) => self.clone(), - (CSSDisplayInline, @BlockFlow(*)) => self.get_inline_collector(builder), - (CSSDisplayInlineBlock, @BlockFlow(*)) => self.get_inline_collector(builder), + (CSSDisplayInline, @BlockFlow(*)) => self.get_inline_collector(builder, node), + (CSSDisplayInlineBlock, @BlockFlow(*)) => self.get_inline_collector(builder, node), _ => self.clone() }; @@ -332,10 +333,9 @@ pub impl LayoutTreeBuilder { // nodes and FlowContexts should not change during layout. let flow = &mut this_ctx.default_collector.flow; for tree::each_child(&FlowTree, flow) |child_flow: &@mut FlowContext| { - for (copy child_flow.d().node).each |node| { - assert!(node.has_layout_data()); - node.layout_data().flow = Some(*child_flow); - } + let node = child_flow.d().node; + assert!(node.has_layout_data()); + node.layout_data().flow = Some(*child_flow); } } @@ -406,7 +406,7 @@ pub impl LayoutTreeBuilder { called on root DOM element. */ fn construct_trees(&mut self, layout_ctx: &LayoutContext, root: AbstractNode) -> Result<@mut FlowContext, ()> { - let new_flow = self.make_flow(Flow_Root); + let new_flow = self.make_flow(Flow_Root, root); let new_generator = @mut BoxGenerator::new(new_flow); let mut root_ctx = BuilderContext::new(new_generator); @@ -415,8 +415,8 @@ pub impl LayoutTreeBuilder { return Ok(new_flow) } - fn make_flow(&mut self, ty: FlowContextType) -> @mut FlowContext { - let data = FlowData(self.next_flow_id()); + fn make_flow(&mut self, ty: FlowContextType, node: AbstractNode) -> @mut FlowContext { + let data = FlowData(self.next_flow_id(), node); let ret = match ty { Flow_Absolute => @mut AbsoluteFlow(data), Flow_Block => @mut BlockFlow(data, BlockFlowData()), diff --git a/src/servo/layout/flow.rs b/src/servo/layout/flow.rs index 29f59ec0770..a4ea3965a7b 100644 --- a/src/servo/layout/flow.rs +++ b/src/servo/layout/flow.rs @@ -70,7 +70,7 @@ pub enum FlowContextType { /* A particular kind of layout context. It manages the positioning of render boxes within the context. */ pub struct FlowData { - node: Option, + node: AbstractNode, /* reference to parent, children flow contexts */ tree: tree::Tree<@mut FlowContext>, /* TODO (Issue #87): debug only */ @@ -84,9 +84,9 @@ pub struct FlowData { position: Rect, } -pub fn FlowData(id: int) -> FlowData { +pub fn FlowData(id: int, node: AbstractNode) -> FlowData { FlowData { - node: None, + node: node, tree: tree::empty(), id: id, diff --git a/src/servo/layout/layout_task.rs b/src/servo/layout/layout_task.rs index 5045760283e..3a3b106324d 100644 --- a/src/servo/layout/layout_task.rs +++ b/src/servo/layout/layout_task.rs @@ -40,13 +40,15 @@ use std::net::url::Url; pub type LayoutTask = SharedChan; pub enum LayoutQuery { - ContentBox(AbstractNode) + ContentBox(AbstractNode), + ContentBoxes(AbstractNode) } pub type LayoutQueryResponse = Result; -enum LayoutQueryResponse_ { - ContentSize(Size2D) +pub enum LayoutQueryResponse_ { + ContentRect(Rect), + ContentRects(~[Rect]) } pub enum Msg { @@ -256,7 +258,10 @@ impl Layout { match query { ContentBox(node) => { let response = match node.layout_data().flow { - None => Err(()), + None => { + error!("no flow present"); + Err(()) + } Some(flow) => { let start_val: Option> = None; let rect = do flow.foldl_boxes_for_node(node, start_val) |acc, box| { @@ -267,16 +272,30 @@ impl Layout { }; match rect { - None => Err(()), - Some(rect) => { - let size = Size2D(rect.size.width.to_px(), - rect.size.height.to_px()); - Ok(ContentSize(size)) + None => { + error!("no boxes for node"); + Err(()) } + Some(rect) => Ok(ContentRect(rect)) } } }; + reply_chan.send(response) + } + ContentBoxes(node) => { + let response = match node.layout_data().flow { + None => Err(()), + Some(flow) => { + let mut boxes = ~[]; + for flow.iter_boxes_for_node(node) |box| { + boxes.push(box.content_box()); + } + + Ok(ContentRects(boxes)) + } + }; + reply_chan.send(response) } } diff --git a/src/test/test_bindings.html b/src/test/test_bindings.html index 7b906908e1c..1d9fd6de5a0 100644 --- a/src/test/test_bindings.html +++ b/src/test/test_bindings.html @@ -3,9 +3,9 @@ -
-
- -
+
fffff



fffffffffffffffff
+
ggg
+ hhhhhhhh +
iiiiiiiiiiiiiiiiiii
diff --git a/src/test/test_bindings.js b/src/test/test_bindings.js index 0897476d3b3..5019616de62 100644 --- a/src/test/test_bindings.js +++ b/src/test/test_bindings.js @@ -1,16 +1,24 @@ //window.alert(ClientRect); //window.alert(ClientRectList); - -window.alert("1"); -let elem = document.documentElement; +window.alert("==1=="); +let elem = document.getElementsByTagName('div')[0]; window.alert(elem.nodeType); window.alert(elem); -window.alert("2"); +window.alert("==1.5=="); +var rect = elem.getBoundingClientRect(); +window.alert(rect); +window.alert(rect.top); +window.alert(rect.bottom); +window.alert(rect.left); +window.alert(rect.right); +window.alert(rect.width); +window.alert(rect.height); +window.alert("==2=="); var rects = elem.getClientRects(); -window.alert("3"); +window.alert("==3=="); window.alert(rects); window.alert(rects.length); -window.alert("4"); +window.alert("==4=="); let rect = rects[0]; window.alert(rect); /*window.alert(Object.prototype.toString.call(rect.__proto__)); @@ -40,4 +48,4 @@ window.alert("DOMParser:"); window.alert(DOMParser); let parser = new DOMParser(); window.alert(parser); -window.alert(parser.parseFromString("", "text/html")); \ No newline at end of file +//window.alert(parser.parseFromString("", "text/html")); diff --git a/src/test/test_hammer_layout.js b/src/test/test_hammer_layout.js index 201b41e752e..9b28847e9aa 100644 --- a/src/test/test_hammer_layout.js +++ b/src/test/test_hammer_layout.js @@ -1,6 +1,12 @@ var divs = document.getElementsByTagName("div"); var div = divs[0]; -for (var i = 0; i < 1000000; i++) { - div.setAttribute('id', 'styled'); -} +var count = 1000000; +var start = new Date(); +for (var i = 0; i < count; i++) { + div.setAttribute('id', 'styled'); + div.getBoundingClientRect(); + window.alert(i); +} +var stop = new Date(); +window.alert((stop - start) / count * 1e6);