aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGlenn Watson <gw@intuitionlibrary.com>2015-07-27 15:20:27 +1000
committerGlenn Watson <gw@intuitionlibrary.com>2015-08-03 11:55:38 +1000
commit9e5687e3e72f2c79bf17bcfb33ea33f0659377ac (patch)
tree747b19a3de028cebc1dca77f6edd1a38fba1cf33
parent1809748dc12ec63e3179b66109c91983f744c235 (diff)
downloadservo-9e5687e3e72f2c79bf17bcfb33ea33f0659377ac.tar.gz
servo-9e5687e3e72f2c79bf17bcfb33ea33f0659377ac.zip
Implement offsetParent/Top/Left/Width/Height.
-rw-r--r--components/layout/block.rs2
-rw-r--r--components/layout/flow.rs3
-rw-r--r--components/layout/fragment.rs2
-rw-r--r--components/layout/inline.rs2
-rw-r--r--components/layout/layout_task.rs133
-rw-r--r--components/layout/list_item.rs4
-rw-r--r--components/layout/multicol.rs3
-rw-r--r--components/layout/sequential.rs10
-rw-r--r--components/layout/table.rs3
-rw-r--r--components/layout/table_caption.rs3
-rw-r--r--components/layout/table_cell.rs3
-rw-r--r--components/layout/table_colgroup.rs1
-rw-r--r--components/layout/table_row.rs3
-rw-r--r--components/layout/table_rowgroup.rs3
-rw-r--r--components/layout/table_wrapper.rs3
-rw-r--r--components/script/dom/htmlelement.rs59
-rw-r--r--components/script/dom/webidls/HTMLElement.webidl11
-rw-r--r--components/script/dom/window.rs27
-rw-r--r--components/script/layout_interface.rs17
-rw-r--r--tests/wpt/mozilla/meta/MANIFEST.json6
-rw-r--r--tests/wpt/mozilla/tests/mozilla/element_parentOffset.html51
21 files changed, 325 insertions, 24 deletions
diff --git a/components/layout/block.rs b/components/layout/block.rs
index c7304865ecb..599c0737bbc 100644
--- a/components/layout/block.rs
+++ b/components/layout/block.rs
@@ -1968,12 +1968,14 @@ impl Flow for BlockFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
if !iterator.should_process(&self.fragment) {
return
}
iterator.process(&self.fragment,
+ level,
&self.fragment
.stacking_relative_border_box(&self.base.stacking_relative_position,
&self.base
diff --git a/components/layout/flow.rs b/components/layout/flow.rs
index 64ee4d2e0d2..ad36062ddc0 100644
--- a/components/layout/flow.rs
+++ b/components/layout/flow.rs
@@ -281,8 +281,11 @@ pub trait Flow: fmt::Debug + Sync {
fn compute_overflow(&self) -> Rect<Au>;
/// Iterates through border boxes of all of this flow's fragments.
+ /// Level provides a zero based index indicating the current
+ /// depth of the flow tree during fragment iteration.
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>);
/// Mutably iterates through fragments in this flow.
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
index 7285adca0e3..16e7bbeeeca 100644
--- a/components/layout/fragment.rs
+++ b/components/layout/fragment.rs
@@ -2157,7 +2157,7 @@ bitflags! {
/// A top-down fragment border box iteration handler.
pub trait FragmentBorderBoxIterator {
/// The operation to perform.
- fn process(&mut self, fragment: &Fragment, overflow: &Rect<Au>);
+ fn process(&mut self, fragment: &Fragment, level: i32, overflow: &Rect<Au>);
/// Returns true if this fragment must be processed in-order. If this returns false,
/// we skip the operation for this fragment, but continue processing siblings.
diff --git a/components/layout/inline.rs b/components/layout/inline.rs
index abf23e488dd..b36320cc3f9 100644
--- a/components/layout/inline.rs
+++ b/components/layout/inline.rs
@@ -1623,6 +1623,7 @@ impl Flow for InlineFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
// FIXME(#2795): Get the real container size.
for fragment in self.fragments.fragments.iter() {
@@ -1636,6 +1637,7 @@ impl Flow for InlineFlow {
let relative_containing_block_mode =
self.base.absolute_position_info.relative_containing_block_mode;
iterator.process(fragment,
+ level,
&fragment.stacking_relative_border_box(stacking_relative_position,
relative_containing_block_size,
relative_containing_block_mode,
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs
index c7eab0fdb50..e9a644105b2 100644
--- a/components/layout/layout_task.rs
+++ b/components/layout/layout_task.rs
@@ -15,7 +15,7 @@ use data::LayoutDataWrapper;
use display_list_builder::ToGfxColor;
use flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
use flow_ref::FlowRef;
-use fragment::{Fragment, FragmentBorderBoxIterator};
+use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
use incremental::{LayoutDamageComputation, REFLOW, REFLOW_ENTIRE_DOCUMENT, REPAINT};
use layout_debug;
use opaque_node::OpaqueNodeMethods;
@@ -53,7 +53,7 @@ use net_traits::image_cache_task::{ImageCacheTask, ImageCacheResult, ImageCacheC
use script::dom::bindings::js::LayoutJS;
use script::dom::node::{LayoutData, Node};
use script::layout_interface::{Animation, ContentBoxResponse, ContentBoxesResponse, NodeGeometryResponse};
-use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC, MouseOverResponse};
+use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC, MouseOverResponse, OffsetParentResponse};
use script::layout_interface::{NewLayoutTaskInfo, Msg, Reflow, ReflowGoal, ReflowQueryType};
use script::layout_interface::{ResolvedStyleResponse, ScriptLayoutChan, ScriptReflow, TrustedNodeAddress};
use script_traits::{ConstellationControlMsg, LayoutControlMsg, OpaqueScriptLayoutChannel};
@@ -69,7 +69,7 @@ use std::ops::{Deref, DerefMut};
use std::sync::mpsc::{channel, Sender, Receiver, Select};
use std::sync::{Arc, Mutex, MutexGuard};
use string_cache::Atom;
-use style::computed_values::{filter, mix_blend_mode};
+use style::computed_values::{self, filter, mix_blend_mode};
use style::media_queries::{MediaType, MediaQueryList, Device};
use style::properties::style_structs;
use style::properties::longhands::{display, position};
@@ -137,6 +137,9 @@ pub struct LayoutTaskData {
/// A queued response for the resolved style property of an element.
pub resolved_style_response: Option<String>,
+ /// A queued response for the offset parent/rect of a node.
+ pub offset_parent_response: OffsetParentResponse,
+
/// The list of currently-running animations.
pub running_animations: Arc<HashMap<OpaqueNode,Vec<Animation>>>,
@@ -382,6 +385,7 @@ impl LayoutTask {
client_rect_response: Rect::zero(),
resolved_style_response: None,
running_animations: Arc::new(HashMap::new()),
+ offset_parent_response: OffsetParentResponse::empty(),
visible_rects: Arc::new(HashMap::with_hash_state(Default::default())),
new_animations_receiver: new_animations_receiver,
new_animations_sender: new_animations_sender,
@@ -999,6 +1003,29 @@ impl LayoutTask {
};
}
+ fn process_offset_parent_query<'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 = ParentOffsetBorderBoxIterator::new(requested_node);
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+ let parent_info_index = iterator.parent_nodes.iter().rposition(|info| info.is_some());
+ match parent_info_index {
+ Some(parent_info_index) => {
+ let parent = iterator.parent_nodes[parent_info_index].as_ref().unwrap();
+ let origin = iterator.node_border_box.origin - parent.border_box.origin;
+ let size = iterator.node_border_box.size;
+ rw_data.offset_parent_response = OffsetParentResponse {
+ node_address: Some(parent.node_address.to_untrusted_node_address()),
+ rect: Rect::new(origin, size),
+ };
+ }
+ None => {
+ rw_data.offset_parent_response = OffsetParentResponse::empty();
+ }
+ }
+ }
fn compute_abs_pos_and_build_display_list<'a>(&'a self,
data: &Reflow,
@@ -1198,6 +1225,8 @@ impl LayoutTask {
self.process_node_geometry_request(node, &mut root_flow, &mut rw_data),
ReflowQueryType::ResolvedStyleQuery(node, ref pseudo, ref property) =>
self.process_resolved_style_request(node, pseudo, property, &mut root_flow, &mut rw_data),
+ ReflowQueryType::OffsetParentQuery(node) =>
+ self.process_offset_parent_query(node, &mut root_flow, &mut rw_data),
ReflowQueryType::NoQuery => {}
}
@@ -1516,6 +1545,12 @@ impl LayoutRPC for LayoutRPCImpl {
Ok(MouseOverResponse(response_list))
}
}
+
+ fn offset_parent(&self) -> OffsetParentResponse {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock().unwrap();
+ rw_data.offset_parent_response.clone()
+ }
}
struct UnioningFragmentBorderBoxIterator {
@@ -1533,7 +1568,7 @@ impl UnioningFragmentBorderBoxIterator {
}
impl FragmentBorderBoxIterator for UnioningFragmentBorderBoxIterator {
- fn process(&mut self, _: &Fragment, border_box: &Rect<Au>) {
+ fn process(&mut self, _: &Fragment, _: i32, border_box: &Rect<Au>) {
self.rect = match self.rect {
Some(rect) => {
Some(rect.union(border_box))
@@ -1564,7 +1599,7 @@ impl CollectingFragmentBorderBoxIterator {
}
impl FragmentBorderBoxIterator for CollectingFragmentBorderBoxIterator {
- fn process(&mut self, _: &Fragment, border_box: &Rect<Au>) {
+ fn process(&mut self, _: &Fragment, _: i32, border_box: &Rect<Au>) {
self.rects.push(*border_box);
}
@@ -1587,8 +1622,33 @@ impl FragmentLocatingFragmentIterator {
}
}
+struct ParentBorderBoxInfo {
+ node_address: OpaqueNode,
+ border_box: Rect<Au>,
+}
+
+struct ParentOffsetBorderBoxIterator {
+ node_address: OpaqueNode,
+ last_level: i32,
+ has_found_node: bool,
+ node_border_box: Rect<Au>,
+ parent_nodes: Vec<Option<ParentBorderBoxInfo>>,
+}
+
+impl ParentOffsetBorderBoxIterator {
+ fn new(node_address: OpaqueNode) -> ParentOffsetBorderBoxIterator {
+ ParentOffsetBorderBoxIterator {
+ node_address: node_address,
+ last_level: -1,
+ has_found_node: false,
+ node_border_box: Rect::zero(),
+ parent_nodes: Vec::new(),
+ }
+ }
+}
+
impl FragmentBorderBoxIterator for FragmentLocatingFragmentIterator {
- fn process(&mut self, fragment: &Fragment, border_box: &Rect<Au>) {
+ fn process(&mut self, fragment: &Fragment, _: i32, border_box: &Rect<Au>) {
let style_structs::Border {
border_top_width: top_width,
border_right_width: right_width,
@@ -1649,7 +1709,7 @@ impl PositionRetrievingFragmentBorderBoxIterator {
}
impl FragmentBorderBoxIterator for PositionRetrievingFragmentBorderBoxIterator {
- fn process(&mut self, _: &Fragment, border_box: &Rect<Au>) {
+ fn process(&mut self, _: &Fragment, _: i32, border_box: &Rect<Au>) {
self.result =
Some(match self.property {
PositionProperty::Left => self.position.x,
@@ -1691,7 +1751,7 @@ impl MarginRetrievingFragmentBorderBoxIterator {
}
impl FragmentBorderBoxIterator for MarginRetrievingFragmentBorderBoxIterator {
- fn process(&mut self, fragment: &Fragment, _: &Rect<Au>) {
+ fn process(&mut self, fragment: &Fragment, _: i32, _: &Rect<Au>) {
let rect = match self.margin_padding {
MarginPadding::Margin => &fragment.margin,
MarginPadding::Padding => &fragment.border_padding
@@ -1709,6 +1769,63 @@ impl FragmentBorderBoxIterator for MarginRetrievingFragmentBorderBoxIterator {
}
}
+// https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface
+impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator {
+ fn process(&mut self, fragment: &Fragment, level: i32, border_box: &Rect<Au>) {
+ if fragment.node == self.node_address {
+ // Found the fragment in the flow tree that matches the
+ // DOM node being looked for.
+ self.has_found_node = true;
+ self.node_border_box = *border_box;
+
+ // offsetParent returns null if the node is fixed.
+ if fragment.style.get_box().position == computed_values::position::T::fixed {
+ self.parent_nodes.clear();
+ }
+ } else if level > self.last_level {
+ // TODO(gw): Is there a less fragile way of checking whether this
+ // fragment is the body element, rather than just checking that
+ // the parent nodes stack contains the root node only?
+ let is_body_element = self.parent_nodes.len() == 1;
+
+ let is_valid_parent = match (is_body_element,
+ fragment.style.get_box().position,
+ &fragment.specific) {
+ // Spec says it's valid if any of these are true:
+ // 1) Is the body element
+ // 2) Is static position *and* is a table or table cell
+ // 3) Is not static position
+ (true, _, _) |
+ (false, computed_values::position::T::static_, &SpecificFragmentInfo::Table) |
+ (false, computed_values::position::T::static_, &SpecificFragmentInfo::TableCell) |
+ (false, computed_values::position::T::absolute, _) |
+ (false, computed_values::position::T::relative, _) |
+ (false, computed_values::position::T::fixed, _) => true,
+
+ // Otherwise, it's not a valid parent
+ (false, computed_values::position::T::static_, _) => false,
+ };
+
+ let parent_info = if is_valid_parent {
+ Some(ParentBorderBoxInfo {
+ border_box: *border_box,
+ node_address: fragment.node,
+ })
+ } else {
+ None
+ };
+
+ self.parent_nodes.push(parent_info);
+ } else if level < self.last_level {
+ self.parent_nodes.pop();
+ }
+ }
+
+ fn should_process(&mut self, _: &Fragment) -> bool {
+ !self.has_found_node
+ }
+}
+
// 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/layout/list_item.rs b/components/layout/list_item.rs
index a1a108848ac..27b0d4589b9 100644
--- a/components/layout/list_item.rs
+++ b/components/layout/list_item.rs
@@ -158,13 +158,15 @@ impl Flow for ListItemFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
- self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position);
+ self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position);
if let Some(ref marker) = self.marker {
if iterator.should_process(marker) {
iterator.process(
marker,
+ level,
&marker.stacking_relative_border_box(&self.block_flow
.base
.stacking_relative_position,
diff --git a/components/layout/multicol.rs b/components/layout/multicol.rs
index 20cc7d5c1c5..3e9ff257c9c 100644
--- a/components/layout/multicol.rs
+++ b/components/layout/multicol.rs
@@ -98,8 +98,9 @@ impl Flow for MulticolFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
- self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position)
+ self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
diff --git a/components/layout/sequential.rs b/components/layout/sequential.rs
index b4c8270e804..6f806f69bf8 100644
--- a/components/layout/sequential.rs
+++ b/components/layout/sequential.rs
@@ -122,22 +122,24 @@ pub fn build_display_list_for_subtree(root: &mut FlowRef,
pub fn iterate_through_flow_tree_fragment_border_boxes(root: &mut FlowRef,
iterator: &mut FragmentBorderBoxIterator) {
fn doit(flow: &mut Flow,
+ level: i32,
iterator: &mut FragmentBorderBoxIterator,
stacking_context_position: &Point2D<Au>) {
- flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position);
+ flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position);
for kid in flow::mut_base(flow).child_iter() {
let stacking_context_position =
if kid.is_block_flow() && kid.as_block().fragment.establishes_stacking_context() {
- *stacking_context_position + flow::base(kid).stacking_relative_position
+ let margin = Point2D::new(kid.as_block().fragment.margin.inline_start, Au(0));
+ *stacking_context_position + flow::base(kid).stacking_relative_position + margin
} else {
*stacking_context_position
};
// FIXME(#2795): Get the real container size.
- doit(kid, iterator, &stacking_context_position);
+ doit(kid, level+1, iterator, &stacking_context_position);
}
}
- doit(&mut **root, iterator, &ZERO_POINT);
+ doit(&mut **root, 0, iterator, &ZERO_POINT);
}
diff --git a/components/layout/table.rs b/components/layout/table.rs
index 633454097e2..197dc89db17 100644
--- a/components/layout/table.rs
+++ b/components/layout/table.rs
@@ -547,8 +547,9 @@ impl Flow for TableFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
- self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position)
+ self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
diff --git a/components/layout/table_caption.rs b/components/layout/table_caption.rs
index c840ab866ce..003f03c92c2 100644
--- a/components/layout/table_caption.rs
+++ b/components/layout/table_caption.rs
@@ -95,8 +95,9 @@ impl Flow for TableCaptionFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
- self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position)
+ self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs
index 66f9ec72259..90cc27854cc 100644
--- a/components/layout/table_cell.rs
+++ b/components/layout/table_cell.rs
@@ -209,8 +209,9 @@ impl Flow for TableCellFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
- self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position)
+ self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
diff --git a/components/layout/table_colgroup.rs b/components/layout/table_colgroup.rs
index d58c361da4d..3af39fe0347 100644
--- a/components/layout/table_colgroup.rs
+++ b/components/layout/table_colgroup.rs
@@ -107,6 +107,7 @@ impl Flow for TableColGroupFlow {
fn iterate_through_fragment_border_boxes(&self,
_: &mut FragmentBorderBoxIterator,
+ _: i32,
_: &Point2D<Au>) {}
fn mutate_fragments(&mut self, _: &mut FnMut(&mut Fragment)) {}
diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs
index 5048644b755..e1cde61b361 100644
--- a/components/layout/table_row.rs
+++ b/components/layout/table_row.rs
@@ -444,8 +444,9 @@ impl Flow for TableRowFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
- self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position)
+ self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs
index 51c700ae480..f633d241218 100644
--- a/components/layout/table_rowgroup.rs
+++ b/components/layout/table_rowgroup.rs
@@ -234,8 +234,9 @@ impl Flow for TableRowGroupFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
- self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position)
+ self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs
index 56934ea875a..ecf9596f746 100644
--- a/components/layout/table_wrapper.rs
+++ b/components/layout/table_wrapper.rs
@@ -431,8 +431,9 @@ impl Flow for TableWrapperFlow {
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
+ level: i32,
stacking_context_position: &Point2D<Au>) {
- self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position)
+ self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs
index 513a9d39775..376bec890cc 100644
--- a/components/script/dom/htmlelement.rs
+++ b/components/script/dom/htmlelement.rs
@@ -12,7 +12,7 @@ use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementM
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLFrameSetElementDerived};
use dom::bindings::codegen::InheritTypes::{EventTargetCast, HTMLInputElementCast, NodeCast};
-use dom::bindings::codegen::InheritTypes::{HTMLElementDerived, HTMLBodyElementDerived};
+use dom::bindings::codegen::InheritTypes::{HTMLElementDerived, HTMLBodyElementDerived, HTMLHtmlElementDerived};
use dom::bindings::js::{JS, MutNullableHeap, Root};
use dom::bindings::error::ErrorResult;
use dom::bindings::error::Error::Syntax;
@@ -216,6 +216,63 @@ impl<'a> HTMLElementMethods for &'a HTMLElement {
// If `request_focus` is not called, focus will be set to None.
document.r().commit_focus_transaction(FocusType::Element);
}
+
+ // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface
+ fn GetOffsetParent(self) -> Option<Root<Element>> {
+ if self.is_htmlbodyelement() || self.is_htmlhtmlelement() {
+ return None;
+ }
+
+ let node = NodeCast::from_ref(self);
+ let window = window_from_node(self);
+ let (element, _) = window.offset_parent_query(node.to_trusted_node_address());
+
+ element
+ }
+
+ // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface
+ fn OffsetTop(self) -> i32 {
+ if self.is_htmlbodyelement() {
+ return 0;
+ }
+
+ let node = NodeCast::from_ref(self);
+ let window = window_from_node(self);
+ let (_, rect) = window.offset_parent_query(node.to_trusted_node_address());
+
+ rect.origin.y.to_nearest_px()
+ }
+
+ // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface
+ fn OffsetLeft(self) -> i32 {
+ if self.is_htmlbodyelement() {
+ return 0;
+ }
+
+ let node = NodeCast::from_ref(self);
+ let window = window_from_node(self);
+ let (_, rect) = window.offset_parent_query(node.to_trusted_node_address());
+
+ rect.origin.x.to_nearest_px()
+ }
+
+ // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface
+ fn OffsetWidth(self) -> i32 {
+ let node = NodeCast::from_ref(self);
+ let window = window_from_node(self);
+ let (_, rect) = window.offset_parent_query(node.to_trusted_node_address());
+
+ rect.size.width.to_nearest_px()
+ }
+
+ // https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface
+ fn OffsetHeight(self) -> i32 {
+ let node = NodeCast::from_ref(self);
+ let window = window_from_node(self);
+ let (_, rect) = window.offset_parent_query(node.to_trusted_node_address());
+
+ rect.size.height.to_nearest_px()
+ }
}
// https://html.spec.whatwg.org/#attr-data-*
diff --git a/components/script/dom/webidls/HTMLElement.webidl b/components/script/dom/webidls/HTMLElement.webidl
index f4c0376b618..90b11d85bff 100644
--- a/components/script/dom/webidls/HTMLElement.webidl
+++ b/components/script/dom/webidls/HTMLElement.webidl
@@ -45,5 +45,16 @@ interface HTMLElement : Element {
//readonly attribute boolean? commandDisabled;
//readonly attribute boolean? commandChecked;
};
+
+// http://dev.w3.org/csswg/cssom-view/#extensions-to-the-htmlelement-interface
+partial interface HTMLElement {
+ // CSSOM things are not [Pure] because they can flush
+ readonly attribute Element? offsetParent;
+ readonly attribute long offsetTop;
+ readonly attribute long offsetLeft;
+ readonly attribute long offsetWidth;
+ readonly attribute long offsetHeight;
+};
+
HTMLElement implements GlobalEventHandlers;
HTMLElement implements ElementCSSInlineStyle;
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index d7a9e9f243a..1320ca354b9 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -8,7 +8,7 @@ use dom::bindings::codegen::Bindings::EventHandlerBinding::{OnErrorEventHandlerN
use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::codegen::Bindings::WindowBinding::{self, WindowMethods, FrameRequestCallback};
-use dom::bindings::codegen::InheritTypes::{NodeCast, EventTargetCast};
+use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast, EventTargetCast};
use dom::bindings::global::global_object_for_js_object;
use dom::bindings::error::{report_pending_exception, Fallible};
use dom::bindings::error::Error::InvalidCharacter;
@@ -27,7 +27,7 @@ use dom::eventtarget::{EventTarget, EventTargetHelpers, EventTargetTypeId};
use dom::htmlelement::HTMLElement;
use dom::location::Location;
use dom::navigator::Navigator;
-use dom::node::{window_from_node, TrustedNodeAddress, NodeHelpers};
+use dom::node::{window_from_node, TrustedNodeAddress, NodeHelpers, from_untrusted_node_address};
use dom::performance::Performance;
use dom::screen::Screen;
use dom::storage::Storage;
@@ -578,6 +578,7 @@ pub trait WindowHelpers {
fn client_rect_query(self, node_geometry_request: TrustedNodeAddress) -> Rect<i32>;
fn resolved_style_query(self, element: TrustedNodeAddress,
pseudo: Option<PseudoElement>, property: &Atom) -> Option<String>;
+ fn offset_parent_query(self, node: TrustedNodeAddress) -> (Option<Root<Element>>, Rect<Au>);
fn handle_reflow_complete_msg(self, reflow_id: u32);
fn set_fragment_name(self, fragment: Option<String>);
fn steal_fragment_name(self) -> Option<String>;
@@ -827,6 +828,27 @@ impl<'a> WindowHelpers for &'a Window {
resolved
}
+ fn offset_parent_query(self, node: TrustedNodeAddress) -> (Option<Root<Element>>, Rect<Au>) {
+ self.reflow(ReflowGoal::ForScriptQuery,
+ ReflowQueryType::OffsetParentQuery(node),
+ ReflowReason::Query);
+ let response = self.layout_rpc.offset_parent();
+ let js_runtime = self.js_runtime.borrow();
+ let js_runtime = js_runtime.as_ref().unwrap();
+ let element = match response.node_address {
+ Some(parent_node_address) => {
+ let node = from_untrusted_node_address(js_runtime.rt(),
+ parent_node_address);
+ let element = ElementCast::to_ref(node.r()).unwrap();
+ Some(Root::from_ref(element))
+ }
+ None => {
+ None
+ }
+ };
+ (element, response.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 {
@@ -1135,6 +1157,7 @@ fn debug_reflow_events(goal: &ReflowGoal, query_type: &ReflowQueryType, reason:
ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
+ ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",
});
debug_msg.push_str(match *reason {
diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs
index 8fcb298880c..a6fb9ad8491 100644
--- a/components/script/layout_interface.rs
+++ b/components/script/layout_interface.rs
@@ -104,6 +104,7 @@ pub trait LayoutRPC {
fn mouse_over(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()>;
/// Query layout for the resolved value of a given CSS property
fn resolved_style(&self) -> ResolvedStyleResponse;
+ fn offset_parent(&self) -> OffsetParentResponse;
}
@@ -116,6 +117,21 @@ pub struct HitTestResponse(pub UntrustedNodeAddress);
pub struct MouseOverResponse(pub Vec<UntrustedNodeAddress>);
pub struct ResolvedStyleResponse(pub Option<String>);
+#[derive(Clone)]
+pub struct OffsetParentResponse {
+ pub node_address: Option<UntrustedNodeAddress>,
+ pub rect: Rect<Au>,
+}
+
+impl OffsetParentResponse {
+ pub fn empty() -> OffsetParentResponse {
+ OffsetParentResponse {
+ node_address: None,
+ rect: Rect::zero(),
+ }
+ }
+}
+
/// Why we're doing reflow.
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum ReflowGoal {
@@ -133,6 +149,7 @@ pub enum ReflowQueryType {
ContentBoxesQuery(TrustedNodeAddress),
NodeGeometryQuery(TrustedNodeAddress),
ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, Atom),
+ OffsetParentQuery(TrustedNodeAddress),
}
/// Information needed for a reflow.
diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json
index 18329c0c5f3..8dda0aba215 100644
--- a/tests/wpt/mozilla/meta/MANIFEST.json
+++ b/tests/wpt/mozilla/meta/MANIFEST.json
@@ -473,6 +473,12 @@
"url": "/_mozilla/mozilla/element_matches_empty.html"
}
],
+ "mozilla/element_parentOffset.html": [
+ {
+ "path": "mozilla/element_parentOffset.html",
+ "url": "/_mozilla/mozilla/element_parentOffset.html"
+ }
+ ],
"mozilla/empty_clientrect.html": [
{
"path": "mozilla/empty_clientrect.html",
diff --git a/tests/wpt/mozilla/tests/mozilla/element_parentOffset.html b/tests/wpt/mozilla/tests/mozilla/element_parentOffset.html
new file mode 100644
index 00000000000..d2b390c05cb
--- /dev/null
+++ b/tests/wpt/mozilla/tests/mozilla/element_parentOffset.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <style type="text/css">
+ #outer {
+ position: absolute;
+ margin-left: 50px;
+ width: 400px;
+ height: 400px;
+ background-color: red;
+ }
+ #inner {
+ margin-left: 100px;
+ width: 200px;
+ height: 200px;
+ background-color: green;
+ }
+ #div {
+ margin: 25px;
+ position: absolute;
+ width: 80px;
+ height: 130px;
+ background-color: blue;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="outer">
+ <div id="inner">
+ <div id="div"></div>
+ </div>
+ </div>
+
+ <script>
+ test(function() {
+ var div = document.getElementById("div");
+ var outer = document.getElementById("outer");
+
+ var parent = div.offsetParent;
+ assert_equals(parent, outer);
+
+ assert_equals(div.offsetLeft, 125);
+ assert_equals(div.offsetTop, 25);
+ assert_equals(div.offsetWidth, 80);
+ assert_equals(div.offsetHeight, 130);
+ });
+ </script>
+ </body>
+</html>