From 5bade7b0fb093c7eac7473f41fc95e78e000b325 Mon Sep 17 00:00:00 2001 From: Josh Matthews Date: Mon, 15 Apr 2013 15:54:06 +0200 Subject: [PATCH] Add getBoundingClientRect, and make it and getClientRects synchronously query layout. Associate flows with DOM nodes to allow this querying to occur. Alleviate the problem of Element objects not having access to the original AbstractNode by adding a transient field to Node that is non-null while a node downcast is taking place. --- src/servo/content/content_task.rs | 5 +- src/servo/dom/bindings/element.rs | 34 +++++++++++++- src/servo/dom/clientrectlist.rs | 11 ++--- src/servo/dom/element.rs | 78 ++++++++++++++++++++++++++++++- src/servo/dom/node.rs | 20 +++++++- src/servo/dom/window.rs | 11 +++-- src/servo/layout/box_builder.rs | 34 +++++++------- src/servo/layout/flow.rs | 6 +-- src/servo/layout/layout_task.rs | 37 +++++++++++---- src/test/test_bindings.html | 8 ++-- src/test/test_bindings.js | 22 ++++++--- src/test/test_hammer_layout.js | 12 +++-- 12 files changed, 219 insertions(+), 59 deletions(-) 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);