Skip to content

Commit

Permalink
Implement client{Top, Left, Height, Width} element properties
Browse files Browse the repository at this point in the history
  • Loading branch information
tschneidereit committed Jul 19, 2015
1 parent acf47a0 commit 162ecd0
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 1 deletion.
59 changes: 58 additions & 1 deletion components/layout/layout_task.rs
Expand Up @@ -52,7 +52,7 @@ use net_traits::{load_bytes_iter, PendingAsyncLoad};
use net_traits::image_cache_task::{ImageCacheTask, ImageCacheResult, ImageCacheChan};
use script::dom::bindings::js::LayoutJS;
use script::dom::node::{LayoutData, Node};
use script::layout_interface::{Animation, ContentBoxResponse, ContentBoxesResponse};
use script::layout_interface::{Animation, ContentBoxResponse, ContentBoxesResponse, NodeGeometryResponse};
use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC, MouseOverResponse};
use script::layout_interface::{NewLayoutTaskInfo, Msg, Reflow, ReflowGoal, ReflowQueryType};
use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress};
Expand Down Expand Up @@ -126,6 +126,9 @@ pub struct LayoutTaskData {
/// A queued response for the content boxes of a node.
pub content_boxes_response: Vec<Rect<Au>>,

/// A queued response for the client {top, left, width, height} of a node.
pub client_rect_response: Rect<i32>,

/// The list of currently-running animations.
pub running_animations: Vec<Animation>,

Expand Down Expand Up @@ -358,6 +361,7 @@ impl LayoutTask {
generation: 0,
content_box_response: Rect::zero(),
content_boxes_response: Vec::new(),
client_rect_response: Rect::zero(),
running_animations: Vec::new(),
visible_rects: Arc::new(HashMap::with_hash_state(Default::default())),
new_animations_receiver: new_animations_receiver,
Expand Down Expand Up @@ -843,6 +847,16 @@ impl LayoutTask {
rw_data.content_boxes_response = iterator.rects;
}

fn process_node_geometry_request<'a>(&'a self,
requested_node: TrustedNodeAddress,
layout_root: &mut FlowRef,
rw_data: &mut RWGuard<'a>) {
let requested_node: OpaqueNode = OpaqueNodeMethods::from_script_node(requested_node);
let mut iterator = FragmentLocatingFragmentIterator::new(requested_node);
sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
rw_data.client_rect_response = iterator.client_rect;
}

fn compute_abs_pos_and_build_display_list<'a>(&'a self,
data: &Reflow,
layout_root: &mut FlowRef,
Expand Down Expand Up @@ -1039,6 +1053,9 @@ impl LayoutTask {
ReflowQueryType::ContentBoxesQuery(node) => {
self.process_content_boxes_request(node, &mut root_flow, &mut rw_data)
}
ReflowQueryType::NodeGeometryQuery(node) => {
self.process_node_geometry_request(node, &mut root_flow, &mut rw_data)
}
ReflowQueryType::NoQuery => {}
}

Expand Down Expand Up @@ -1278,6 +1295,14 @@ impl LayoutRPC for LayoutRPCImpl {
ContentBoxesResponse(rw_data.content_boxes_response.clone())
}

fn node_geometry(&self) -> NodeGeometryResponse {
let &LayoutRPCImpl(ref rw_data) = self;
let rw_data = rw_data.lock().unwrap();
NodeGeometryResponse {
client_rect: rw_data.client_rect_response
}
}

/// Requests the node containing the point of interest.
fn hit_test(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()> {
let point = Point2D::new(Au::from_f32_px(point.x), Au::from_f32_px(point.y));
Expand Down Expand Up @@ -1395,6 +1420,38 @@ impl FragmentBorderBoxIterator for CollectingFragmentBorderBoxIterator {
}
}

struct FragmentLocatingFragmentIterator {
node_address: OpaqueNode,
client_rect: Rect<i32>,
}

impl FragmentLocatingFragmentIterator {
fn new(node_address: OpaqueNode) -> FragmentLocatingFragmentIterator {
FragmentLocatingFragmentIterator {
node_address: node_address,
client_rect: Rect::zero()
}
}
}

impl FragmentBorderBoxIterator for FragmentLocatingFragmentIterator {
fn process(&mut self, fragment: &Fragment, border_box: &Rect<Au>) {
let border_style_struct = fragment.style.get_border();
let top_width = border_style_struct.border_top_width;
let right_width = border_style_struct.border_right_width;
let bottom_width = border_style_struct.border_bottom_width;
let left_width = border_style_struct.border_left_width;
self.client_rect.origin.y = top_width.to_px();
self.client_rect.origin.x = left_width.to_px();
self.client_rect.size.width = (border_box.size.width - left_width - right_width).to_px();
self.client_rect.size.height = (border_box.size.height - top_width - bottom_width).to_px();
}

fn should_process(&mut self, fragment: &Fragment) -> bool {
fragment.node == self.node_address
}
}

// The default computed value for background-color is transparent (see
// http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we
// need to propagate the background color from the root HTML/Body
Expand Down
20 changes: 20 additions & 0 deletions components/script/dom/element.rs
Expand Up @@ -1296,6 +1296,26 @@ impl<'a> ElementMethods for &'a Element {
rect.origin.x + rect.size.width)
}

fn ClientTop(self) -> i32 {
let node = NodeCast::from_ref(self);
node.get_client_rect().origin.y
}

fn ClientLeft(self) -> i32 {
let node = NodeCast::from_ref(self);
node.get_client_rect().origin.x
}

fn ClientWidth(self) -> i32 {
let node = NodeCast::from_ref(self);
node.get_client_rect().size.width
}

fn ClientHeight(self) -> i32 {
let node = NodeCast::from_ref(self);
node.get_client_rect().size.height
}

// https://dvcs.w3.org/hg/innerhtml/raw-file/tip/index.html#widl-Element-innerHTML
fn GetInnerHTML(self) -> Fallible<DOMString> {
//XXX TODO: XML case
Expand Down
5 changes: 5 additions & 0 deletions components/script/dom/node.rs
Expand Up @@ -504,6 +504,7 @@ pub trait NodeHelpers {

fn get_bounding_content_box(self) -> Rect<Au>;
fn get_content_boxes(self) -> Vec<Rect<Au>>;
fn get_client_rect(self) -> Rect<i32>;

fn before(self, nodes: Vec<NodeOrString>) -> ErrorResult;
fn after(self, nodes: Vec<NodeOrString>) -> ErrorResult;
Expand Down Expand Up @@ -806,6 +807,10 @@ impl<'a> NodeHelpers for &'a Node {
window_from_node(self).r().content_boxes_query(self.to_trusted_node_address())
}

fn get_client_rect(self) -> Rect<i32> {
window_from_node(self).r().client_rect_query(self.to_trusted_node_address())
}

// https://dom.spec.whatwg.org/#dom-childnode-before
fn before(self, nodes: Vec<NodeOrString>) -> ErrorResult {
match self.parent_node.get() {
Expand Down
5 changes: 5 additions & 0 deletions components/script/dom/webidls/Element.webidl
Expand Up @@ -59,6 +59,11 @@ interface Element : Node {
partial interface Element {
DOMRectList getClientRects();
DOMRect getBoundingClientRect();

readonly attribute long clientTop;
readonly attribute long clientLeft;
readonly attribute long clientWidth;
readonly attribute long clientHeight;
};

// https://domparsing.spec.whatwg.org/#extensions-to-the-element-interface
Expand Down
9 changes: 9 additions & 0 deletions components/script/dom/window.rs
Expand Up @@ -534,6 +534,7 @@ pub trait WindowHelpers {
fn layout(&self) -> &LayoutRPC;
fn content_box_query(self, content_box_request: TrustedNodeAddress) -> Rect<Au>;
fn content_boxes_query(self, content_boxes_request: TrustedNodeAddress) -> Vec<Rect<Au>>;
fn client_rect_query(self, node_geometry_request: TrustedNodeAddress) -> Rect<i32>;
fn handle_reflow_complete_msg(self, reflow_id: u32);
fn handle_resize_inactive_msg(self, new_size: WindowSizeData);
fn set_fragment_name(self, fragment: Option<String>);
Expand Down Expand Up @@ -764,6 +765,13 @@ impl<'a> WindowHelpers for &'a Window {
rects
}

fn client_rect_query(self, node_geometry_request: TrustedNodeAddress) -> Rect<i32> {
self.reflow(ReflowGoal::ForScriptQuery,
ReflowQueryType::NodeGeometryQuery(node_geometry_request),
ReflowReason::Query);
self.layout_rpc.node_geometry().client_rect
}

fn handle_reflow_complete_msg(self, reflow_id: u32) {
let last_reflow_id = self.last_reflow_id.get();
if last_reflow_id == reflow_id {
Expand Down Expand Up @@ -1072,6 +1080,7 @@ fn debug_reflow_events(goal: &ReflowGoal, query_type: &ReflowQueryType, reason:
ReflowQueryType::NoQuery => "\tNoQuery",
ReflowQueryType::ContentBoxQuery(_n) => "\tContentBoxQuery",
ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
});

debug_msg.push_str(match *reason {
Expand Down
6 changes: 6 additions & 0 deletions components/script/layout_interface.rs
Expand Up @@ -95,13 +95,18 @@ pub trait LayoutRPC {
fn content_box(&self) -> ContentBoxResponse;
/// Requests the dimensions of all the content boxes, as in the `getClientRects()` call.
fn content_boxes(&self) -> ContentBoxesResponse;
/// Requests the clientTop.
fn node_geometry(&self) -> NodeGeometryResponse;
/// Requests the node containing the point of interest
fn hit_test(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()>;
fn mouse_over(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()>;
}

pub struct ContentBoxResponse(pub Rect<Au>);
pub struct ContentBoxesResponse(pub Vec<Rect<Au>>);
pub struct NodeGeometryResponse {
pub client_rect: Rect<i32>,
}
pub struct HitTestResponse(pub UntrustedNodeAddress);
pub struct MouseOverResponse(pub Vec<UntrustedNodeAddress>);

Expand All @@ -120,6 +125,7 @@ pub enum ReflowQueryType {
NoQuery,
ContentBoxQuery(TrustedNodeAddress),
ContentBoxesQuery(TrustedNodeAddress),
NodeGeometryQuery(TrustedNodeAddress),
}

/// Information needed for a reflow.
Expand Down
6 changes: 6 additions & 0 deletions tests/wpt/mozilla/meta/MANIFEST.json
Expand Up @@ -275,6 +275,12 @@
"url": "/_mozilla/mozilla/child_reparenting.html"
}
],
"mozilla/client-top-left-height-width.html": [
{
"path": "mozilla/client-top-left-height-width.html",
"url": "/_mozilla/mozilla/client-top-left-height-width.html"
}
],
"mozilla/collections.html": [
{
"path": "mozilla/collections.html",
Expand Down
105 changes: 105 additions & 0 deletions tests/wpt/mozilla/tests/mozilla/client-top-left-height-width.html
@@ -0,0 +1,105 @@
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
@font-face {
font-family: 'ahem';
src: url(../css/fonts/ahem/ahem.ttf);
}
#no-border {
width: 100px;
height: 100px;
}
#with-border {
border: 10px solid black;
width: 100px;
height: 100px;
}
#with-border-and-padding {
border: 10px solid black;
padding: 10px;
width: 100px;
height: 100px;
}
#abs1 {
position: absolute;
background-color: red;
top: 45px;
left: 155px;
width: 100px;
height: 120px;
}
#abs2 {
position: absolute;
background-color: green;
top: 5px;
left: 5px;
width: 80px;
height: 80px;
}
#abs3 {
position: absolute;
background-color: blue;
top: 12px;
left: 14px;
width: 48px;
height: 40px;
}
#span1 {
font-family: 'ahem';
font-size: 20px;
line-height: 1;
}
#rotated {
width: 100px;
height: 100px;
background-color: blue;
transform: rotate(45deg);
}
#inline-block {
width: 100px;
height: 100px;
background-color: green;
display: inline-block;
}
</style>
</head>
<body>
<div id="no-border">my div</div>
<div id="with-border">my div</div>
<div id="with-border-and-padding">my div</div>
<div id="abs1">
<div id="abs2">
<div id="abs3">
<span id="span1">X</span>
</div>
</div>
</div>
<div id='rotated'>rotated</div>
<div id='inline-block'>inline-block</div>
<script>
test_rect = function(name, left, top, height, width) {
var div = document.getElementById(name);
var rect = div.getBoundingClientRect();

assert_equals(div.clientLeft, left);
assert_equals(div.clientTop, top);
assert_equals(div.clientHeight, height);
assert_equals(div.clientWidth, width);
}

test(function() {
test_rect('no-border', 0, 0, 100, 100);
test_rect('with-border', 10, 10, 100, 100);
test_rect('with-border-and-padding', 10, 10, 120, 120);
test_rect('abs1', 0, 0, 120, 100);
test_rect('rotated', 0, 0, 100, 100);
test_rect('abs2', 0, 0, 80, 80);
test_rect('abs3', 0, 0, 40, 48);
test_rect('span1', 0, 0, 0, 0);
test_rect('inline-block', 0, 0, 100, 100);
});
</script>
</body>
</html>

0 comments on commit 162ecd0

Please sign in to comment.