diff options
-rw-r--r-- | components/layout/layout_task.rs | 62 | ||||
-rw-r--r-- | components/script/dom/element.rs | 20 | ||||
-rw-r--r-- | components/script/dom/node.rs | 5 | ||||
-rw-r--r-- | components/script/dom/webidls/Element.webidl | 5 | ||||
-rw-r--r-- | components/script/dom/window.rs | 9 | ||||
-rw-r--r-- | components/script/layout_interface.rs | 6 | ||||
-rw-r--r-- | tests/wpt/mozilla/meta/MANIFEST.json | 6 | ||||
-rw-r--r-- | tests/wpt/mozilla/tests/mozilla/client-top-left-height-width.html | 105 |
8 files changed, 217 insertions, 1 deletions
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 7d0df0fc5e6..4aafbb087ad 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -53,7 +53,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}; @@ -70,6 +70,7 @@ use std::sync::mpsc::{channel, Sender, Receiver, Select}; use std::sync::{Arc, Mutex, MutexGuard}; use style::computed_values::{filter, mix_blend_mode}; use style::media_queries::{MediaType, MediaQueryList, Device}; +use style::properties::style_structs; use style::selector_matching::Stylist; use style::stylesheets::{Origin, Stylesheet, CSSRuleIteratorExt}; use url::Url; @@ -126,6 +127,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 in pixels. + pub client_rect_response: Rect<i32>, + /// The list of currently-running animations. pub running_animations: Vec<Animation>, @@ -368,6 +372,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, @@ -850,6 +855,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, @@ -1044,6 +1059,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 => {} } @@ -1283,6 +1301,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)); @@ -1400,6 +1426,40 @@ 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 style_structs::Border { + border_top_width: top_width, + border_right_width: right_width, + border_bottom_width: bottom_width, + border_left_width: left_width, + .. + } = *fragment.style.get_border(); + 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 diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index dd6afd3061b..10b7950f82e 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -1317,6 +1317,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 diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 42ad7608580..54535fdb7af 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -492,6 +492,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; @@ -794,6 +795,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() { diff --git a/components/script/dom/webidls/Element.webidl b/components/script/dom/webidls/Element.webidl index 81c6f329b47..f2f18a5cc0b 100644 --- a/components/script/dom/webidls/Element.webidl +++ b/components/script/dom/webidls/Element.webidl @@ -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 diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 04f6b3ee6ce..7bea165b46d 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -540,6 +540,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 set_fragment_name(self, fragment: Option<String>); fn steal_fragment_name(self) -> Option<String>; @@ -771,6 +772,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 { @@ -1077,6 +1085,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 { diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs index b94758f56b7..d94f6e4a581 100644 --- a/components/script/layout_interface.rs +++ b/components/script/layout_interface.rs @@ -95,6 +95,8 @@ 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 geometry of this node. Used by APIs such as `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, ()>; @@ -102,6 +104,9 @@ pub trait LayoutRPC { 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>); @@ -120,6 +125,7 @@ pub enum ReflowQueryType { NoQuery, ContentBoxQuery(TrustedNodeAddress), ContentBoxesQuery(TrustedNodeAddress), + NodeGeometryQuery(TrustedNodeAddress), } /// Information needed for a reflow. diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 5410b15ebeb..eb000fe529f 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -287,6 +287,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", diff --git a/tests/wpt/mozilla/tests/mozilla/client-top-left-height-width.html b/tests/wpt/mozilla/tests/mozilla/client-top-left-height-width.html new file mode 100644 index 00000000000..480ad34c9c1 --- /dev/null +++ b/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; +} +#display-none { + width: 100px; + height: 100px; + background-color: green; + display: none; +} +</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"> + <span id="span1">X</span> + </div> + <div id='rotated'>rotated</div> + <div id='inline-block'>inline-block</div> + <div id='display-none'>display-none</div> + <script> + function test_rect(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); }, 'Block without border'); + test(function() { test_rect('with-border', 10, 10, 100, 100); }, 'Block with border'); + test(function() { test_rect('with-border-and-padding', 10, 10, 120, 120); }, 'Block without border and padding'); + test(function() { test_rect('abs1', 0, 0, 120, 100); }, 'Absolutely positioned block'); + test(function() { test_rect('rotated', 0, 0, 100, 100); }, 'Rotated block'); + test(function() { test_rect('span1', 0, 0, 0, 0); }, 'Span'); + test(function() { test_rect('inline-block', 0, 0, 100, 100); }, 'Inline block'); + test(function() { test_rect('display-none', 0, 0, 0, 0); }, 'display: none'); + </script> +</body> +</html> |