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.

This commit is contained in:
Josh Matthews 2013-04-15 15:54:06 +02:00
parent 71df18a839
commit 5bade7b0fb
12 changed files with 219 additions and 59 deletions

View file

@ -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();

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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
}
}
}
}

View file

@ -43,6 +43,8 @@ pub struct Node {
wrapper: WrapperCache,
type_id: NodeTypeId,
abstract: Option<AbstractNode>,
parent_node: Option<AbstractNode>,
first_child: Option<AbstractNode>,
last_child: Option<AbstractNode>,
@ -238,15 +240,27 @@ pub impl AbstractNode {
fn transmute<T, R>(self, f: &fn(&T) -> R) -> R {
unsafe {
let node_box: *mut bindings::utils::rust_box<Node> = transmute(self.obj);
let node = &mut (*node_box).payload;
let old = node.abstract;
node.abstract = Some(self);
let box: *bindings::utils::rust_box<T> = transmute(self.obj);
f(&(*box).payload)
let rv = f(&(*box).payload);
node.abstract = old;
rv
}
}
fn transmute_mut<T, R>(self, f: &fn(&mut T) -> R) -> R {
unsafe {
let node_box: *mut bindings::utils::rust_box<Node> = transmute(self.obj);
let node = &mut (*node_box).payload;
let old = node.abstract;
node.abstract = Some(self);
let box: *bindings::utils::rust_box<T> = 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,

View file

@ -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<TimerControlMsg>,
dom_event_chan: SharedChan<Event>,
content_task: *mut Content,
wrapper: WrapperCache
}
@ -81,7 +84,8 @@ pub impl Window {
}
pub fn Window(content_chan: comm::SharedChan<ControlMsg>,
dom_event_chan: comm::SharedChan<Event>) -> @mut Window {
dom_event_chan: comm::SharedChan<Event>,
content_task: *mut Content) -> @mut Window {
let win = @mut Window {
wrapper: WrapperCache::new(),
@ -96,7 +100,8 @@ pub fn Window(content_chan: comm::SharedChan<ControlMsg>,
TimerMessage_TriggerExit => content_chan.send(ExitMsg)
}
}
}
},
content_task: content_task
};
let compartment = global_content().compartment.get();
window::create(compartment, win);

View file

@ -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()),

View file

@ -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<AbstractNode>,
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<Au>,
}
pub fn FlowData(id: int) -> FlowData {
pub fn FlowData(id: int, node: AbstractNode) -> FlowData {
FlowData {
node: None,
node: node,
tree: tree::empty(),
id: id,

View file

@ -40,13 +40,15 @@ use std::net::url::Url;
pub type LayoutTask = SharedChan<Msg>;
pub enum LayoutQuery {
ContentBox(AbstractNode)
ContentBox(AbstractNode),
ContentBoxes(AbstractNode)
}
pub type LayoutQueryResponse = Result<LayoutQueryResponse_, ()>;
enum LayoutQueryResponse_ {
ContentSize(Size2D<int>)
pub enum LayoutQueryResponse_ {
ContentRect(Rect<Au>),
ContentRects(~[Rect<Au>])
}
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<Rect<Au>> = 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)
}
}

View file

@ -3,9 +3,9 @@
<script src="test_bindings.js"></script>
</head>
<body>
<div id="first"></div>
<div id="second"></div>
<span id="third"></div>
<div id="fourth"></div>
<div id="first">fffff<br><br><br><br>fffffffffffffffff</div>
<div id="second">ggg</div>
<span id="third">hhhhhhhh</span>
<div id="fourth">iiiiiiiiiiiiiiiiiii</div>
</body>
</html>

View file

@ -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("<html></html>", "text/html"));
//window.alert(parser.parseFromString("<html></html>", "text/html"));

View file

@ -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);