aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout/layout_thread.rs13
-rw-r--r--components/layout/query.rs129
-rw-r--r--components/script/dom/element.rs10
-rw-r--r--components/script/dom/node.rs41
-rw-r--r--components/script/dom/webidls/Element.webidl3
-rw-r--r--components/script/dom/window.rs8
-rw-r--r--components/script/layout_interface.rs3
-rw-r--r--components/style/logical_geometry.rs29
-rw-r--r--tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeight.htm.ini17
-rw-r--r--tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeightWhenNotScrollable.htm.ini20
10 files changed, 234 insertions, 39 deletions
diff --git a/components/layout/layout_thread.rs b/components/layout/layout_thread.rs
index a0ee55ece66..5c6afdbcfff 100644
--- a/components/layout/layout_thread.rs
+++ b/components/layout/layout_thread.rs
@@ -43,7 +43,7 @@ use profile_traits::mem::{self, Report, ReportKind, ReportsChan};
use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType};
use profile_traits::time::{self, TimerMetadata, profile};
use query::{LayoutRPCImpl, process_content_box_request, process_content_boxes_request};
-use query::{process_node_geometry_request, process_offset_parent_query};
+use query::{process_node_geometry_request, process_node_scroll_area_request, process_offset_parent_query};
use query::{process_resolved_style_request, process_margin_style_query};
use script::dom::node::OpaqueStyleAndLayoutData;
use script::layout_interface::{LayoutRPC, OffsetParentResponse, MarginStyleResponse};
@@ -117,6 +117,9 @@ pub struct LayoutThreadData {
/// A queued response for the node at a given point
pub hit_test_response: (Option<DisplayItemMetadata>, bool),
+ /// A queued response for the scroll {top, left, width, height} of a node in pixels.
+ pub scroll_area_response: Rect<i32>,
+
/// A queued response for the resolved style property of an element.
pub resolved_style_response: Option<String>,
@@ -462,6 +465,7 @@ impl LayoutThread {
content_boxes_response: Vec::new(),
client_rect_response: Rect::zero(),
hit_test_response: (None, false),
+ scroll_area_response: Rect::zero(),
resolved_style_response: None,
offset_parent_response: OffsetParentResponse::empty(),
margin_style_response: MarginStyleResponse::empty(),
@@ -989,6 +993,9 @@ impl LayoutThread {
ReflowQueryType::NodeGeometryQuery(_) => {
rw_data.client_rect_response = Rect::zero();
},
+ ReflowQueryType::NodeScrollGeometryQuery(_) => {
+ rw_data.scroll_area_response = Rect::zero();
+ },
ReflowQueryType::ResolvedStyleQuery(_, _, _) => {
rw_data.resolved_style_response = None;
},
@@ -1144,6 +1151,10 @@ impl LayoutThread {
let node = unsafe { ServoLayoutNode::new(&node) };
rw_data.client_rect_response = process_node_geometry_request(node, &mut root_flow);
},
+ ReflowQueryType::NodeScrollGeometryQuery(node) => {
+ let node = unsafe { ServoLayoutNode::new(&node) };
+ rw_data.scroll_area_response = process_node_scroll_area_request(node, &mut root_flow);
+ },
ReflowQueryType::ResolvedStyleQuery(node, ref pseudo, ref property) => {
let node = unsafe { ServoLayoutNode::new(&node) };
rw_data.resolved_style_response =
diff --git a/components/layout/query.rs b/components/layout/query.rs
index 039de56f5c4..7c45c5bb13f 100644
--- a/components/layout/query.rs
+++ b/components/layout/query.rs
@@ -8,6 +8,7 @@ use app_units::Au;
use construct::ConstructionResult;
use euclid::point::Point2D;
use euclid::rect::Rect;
+use euclid::size::Size2D;
use flow;
use flow_ref::FlowRef;
use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
@@ -20,11 +21,12 @@ use script::layout_interface::{HitTestResponse, LayoutRPC, OffsetParentResponse}
use script::layout_interface::{ResolvedStyleResponse, ScriptLayoutChan, MarginStyleResponse};
use script_traits::LayoutMsg as ConstellationMsg;
use sequential;
+use std::cmp::{min, max};
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use string_cache::Atom;
use style::computed_values;
-use style::logical_geometry::WritingMode;
+use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
use style::properties::longhands::{display, position};
use style::properties::style_structs;
use style::selector_impl::PseudoElement;
@@ -34,6 +36,18 @@ use wrapper::{LayoutNode, ThreadSafeLayoutNode};
pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
+// https://drafts.csswg.org/cssom-view/#overflow-directions
+fn overflow_direction(writing_mode: &WritingMode) -> OverflowDirection {
+ match (writing_mode.block_flow_direction(), writing_mode.inline_base_direction()) {
+ (BlockFlowDirection::TopToBottom, InlineBaseDirection::LeftToRight) |
+ (BlockFlowDirection::LeftToRight, InlineBaseDirection::LeftToRight) => OverflowDirection::RightAndDown,
+ (BlockFlowDirection::TopToBottom, InlineBaseDirection::RightToLeft) |
+ (BlockFlowDirection::RightToLeft, InlineBaseDirection::LeftToRight) => OverflowDirection::LeftAndDown,
+ (BlockFlowDirection::RightToLeft, InlineBaseDirection::RightToLeft) => OverflowDirection::LeftAndUp,
+ (BlockFlowDirection::LeftToRight, InlineBaseDirection::RightToLeft) => OverflowDirection::RightAndUp
+ }
+}
+
impl LayoutRPC for LayoutRPCImpl {
// The neat thing here is that in order to answer the following two queries we only
@@ -78,6 +92,12 @@ impl LayoutRPC for LayoutRPCImpl {
}
}
+ fn node_scroll_area(&self) -> NodeGeometryResponse {
+ NodeGeometryResponse {
+ client_rect: self.0.lock().unwrap().scroll_area_response
+ }
+ }
+
/// Retrieves the resolved value for a CSS style property.
fn resolved_style(&self) -> ResolvedStyleResponse {
let &LayoutRPCImpl(ref rw_data) = self;
@@ -174,6 +194,14 @@ enum PositionProperty {
Height,
}
+#[derive(Debug)]
+enum OverflowDirection {
+ RightAndDown,
+ LeftAndDown,
+ LeftAndUp,
+ RightAndUp,
+}
+
struct PositionRetrievingFragmentBorderBoxIterator {
node_address: OpaqueNode,
result: Option<Au>,
@@ -291,6 +319,28 @@ impl FragmentLocatingFragmentIterator {
}
}
+struct UnioningFragmentScrollAreaIterator {
+ node_address: OpaqueNode,
+ union_rect: Rect<i32>,
+ origin_rect: Rect<i32>,
+ level: Option<i32>,
+ is_child: bool,
+ overflow_direction: OverflowDirection
+}
+
+impl UnioningFragmentScrollAreaIterator {
+ fn new(node_address: OpaqueNode) -> UnioningFragmentScrollAreaIterator {
+ UnioningFragmentScrollAreaIterator {
+ node_address: node_address,
+ union_rect: Rect::zero(),
+ origin_rect: Rect::zero(),
+ level: None,
+ is_child: false,
+ overflow_direction: OverflowDirection::RightAndDown
+ }
+ }
+}
+
struct ParentBorderBoxInfo {
node_address: OpaqueNode,
border_box: Rect<Au>,
@@ -336,6 +386,53 @@ impl FragmentBorderBoxIterator for FragmentLocatingFragmentIterator {
}
}
+// https://drafts.csswg.org/cssom-view/#scrolling-area
+impl FragmentBorderBoxIterator for UnioningFragmentScrollAreaIterator {
+ fn process(&mut self, fragment: &Fragment, level: i32, border_box: &Rect<Au>) {
+ // In cases in which smaller child elements contain less padding than the parent
+ // the a union of the two elements padding rectangles could result in an unwanted
+ // increase in size. To work around this, we store the original elements padding
+ // rectangle as `origin_rect` and the union of all child elements padding and
+ // margin rectangles as `union_rect`.
+ let style_structs::Border {
+ border_top_width: top_border,
+ border_right_width: right_border,
+ border_bottom_width: bottom_border,
+ border_left_width: left_border,
+ ..
+ } = *fragment.style.get_border();
+ let right_padding = (border_box.size.width - right_border - left_border).to_px();
+ let bottom_padding = (border_box.size.height - bottom_border - top_border).to_px();
+ let top_padding = top_border.to_px();
+ let left_padding = left_border.to_px();
+ match self.level {
+ Some(start_level) if level <= start_level => { self.is_child = false; }
+ Some(_) => {
+ let padding = Rect::new(Point2D::new(left_padding, top_padding),
+ Size2D::new(right_padding, bottom_padding));
+ let top_margin = fragment.margin.top(fragment.style.writing_mode).to_px();
+ let left_margin = fragment.margin.left(fragment.style.writing_mode).to_px();
+ let bottom_margin = fragment.margin.bottom(fragment.style.writing_mode).to_px();
+ let right_margin = fragment.margin.right(fragment.style.writing_mode).to_px();
+ let margin = Rect::new(Point2D::new(left_margin, top_margin),
+ Size2D::new(right_margin, bottom_margin));
+ self.union_rect = self.union_rect.union(&margin).union(&padding);
+ }
+ None => {
+ self.level = Some(level);
+ self.is_child = true;
+ self.overflow_direction = overflow_direction(&fragment.style.writing_mode);
+ self.origin_rect = Rect::new(Point2D::new(top_padding, left_padding),
+ Size2D::new(right_padding, bottom_padding));
+ },
+ };
+ }
+
+ fn should_process(&mut self, fragment: &Fragment) -> bool {
+ fragment.contains_node(self.node_address) || self.is_child
+ }
+}
+
// 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>) {
@@ -400,6 +497,36 @@ pub fn process_node_geometry_request<'ln, N: LayoutNode<'ln>>(requested_node: N,
iterator.client_rect
}
+pub fn process_node_scroll_area_request<'ln, N: LayoutNode<'ln>>(requested_node: N, layout_root: &mut FlowRef)
+ -> Rect<i32> {
+ let mut iterator = UnioningFragmentScrollAreaIterator::new(requested_node.opaque());
+ sequential::iterate_through_flow_tree_fragment_border_boxes(layout_root, &mut iterator);
+ match iterator.overflow_direction {
+ OverflowDirection::RightAndDown => {
+ let right = max(iterator.union_rect.size.width, iterator.origin_rect.size.width);
+ let bottom = max(iterator.union_rect.size.height, iterator.origin_rect.size.height);
+ Rect::new(iterator.origin_rect.origin, Size2D::new(right, bottom))
+ },
+ OverflowDirection::LeftAndDown => {
+ let bottom = max(iterator.union_rect.size.height, iterator.origin_rect.size.height);
+ let left = max(iterator.union_rect.origin.x, iterator.origin_rect.origin.x);
+ Rect::new(Point2D::new(left, iterator.origin_rect.origin.y),
+ Size2D::new(iterator.origin_rect.size.width, bottom))
+ },
+ OverflowDirection::LeftAndUp => {
+ let top = min(iterator.union_rect.origin.y, iterator.origin_rect.origin.y);
+ let left = min(iterator.union_rect.origin.x, iterator.origin_rect.origin.x);
+ Rect::new(Point2D::new(left, top), iterator.origin_rect.size)
+ },
+ OverflowDirection::RightAndUp => {
+ let top = min(iterator.union_rect.origin.y, iterator.origin_rect.origin.y);
+ let right = max(iterator.union_rect.size.width, iterator.origin_rect.size.width);
+ Rect::new(Point2D::new(iterator.origin_rect.origin.x, top),
+ Size2D::new(right, iterator.origin_rect.size.height))
+ }
+ }
+}
+
/// Return the resolved value of property for a given (pseudo)element.
/// https://drafts.csswg.org/cssom/#resolved-value
pub fn process_resolved_style_request<'ln, N: LayoutNode<'ln>>(
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs
index 3eec2575bc3..25d9f0d45b8 100644
--- a/components/script/dom/element.rs
+++ b/components/script/dom/element.rs
@@ -1421,6 +1421,16 @@ impl ElementMethods for Element {
rect.size.height.to_f64_px())
}
+ // https://drafts.csswg.org/cssom-view/#dom-element-scrollwidth
+ fn ScrollWidth(&self) -> i32 {
+ self.upcast::<Node>().get_scroll_area().size.width
+ }
+
+ // https://drafts.csswg.org/cssom-view/#dom-element-scrollheight
+ fn ScrollHeight(&self) -> i32 {
+ self.upcast::<Node>().get_scroll_area().size.height
+ }
+
// https://drafts.csswg.org/cssom-view/#dom-element-clienttop
fn ClientTop(&self) -> i32 {
self.upcast::<Node>().get_client_rect().origin.y
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index 717be0331d5..fed47bb7b24 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -18,6 +18,7 @@ use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods;
use dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::codegen::UnionTypes::NodeOrString;
use dom::bindings::conversions::{self, DerivedFrom};
use dom::bindings::error::{Error, ErrorResult, Fallible};
@@ -37,6 +38,7 @@ use dom::documentfragment::DocumentFragment;
use dom::documenttype::DocumentType;
use dom::element::{Element, ElementCreator};
use dom::eventtarget::EventTarget;
+use dom::htmlbodyelement::HTMLBodyElement;
use dom::htmlcollection::HTMLCollection;
use dom::htmlelement::HTMLElement;
use dom::nodelist::NodeList;
@@ -45,8 +47,11 @@ use dom::range::WeakRangeVec;
use dom::text::Text;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use dom::window::Window;
+use euclid::point::Point2D;
use euclid::rect::Rect;
+use euclid::size::Size2D;
use heapsize::{HeapSizeOf, heap_size_of};
+use html5ever::tree_builder::QuirksMode;
use js::jsapi::{JSContext, JSObject, JSRuntime};
use layout_interface::{LayoutChan, Msg};
use libc::{self, c_void, uintptr_t};
@@ -577,6 +582,42 @@ impl Node {
window_from_node(self).client_rect_query(self.to_trusted_node_address())
}
+ // https://drafts.csswg.org/cssom-view/#dom-element-scrollwidth
+ // https://drafts.csswg.org/cssom-view/#dom-element-scrollheight
+ // https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
+ // https://drafts.csswg.org/cssom-view/#dom-element-scrollleft
+ pub fn get_scroll_area(&self) -> Rect<i32> {
+ // Step 1
+ let document = self.owner_doc();
+ // Step 3
+ let window = document.window();
+
+ let html_element = document.GetDocumentElement();
+
+ let is_body_element = html_element.r().and_then(|root| {
+ let node = root.upcast::<Node>();
+ node.children().find(|child| { child.is::<HTMLBodyElement>() }).map(|node| {
+ *node.r() == *self
+ })
+ }).unwrap_or(false);
+
+ let scroll_area = window.scroll_area_query(self.to_trusted_node_address());
+
+ match (document != window.Document(), is_body_element, document.quirks_mode(),
+ html_element.r() == self.downcast::<Element>()) {
+ // Step 2 && Step 5
+ (true, _, _, _) | (_, false, QuirksMode::Quirks, true) => Rect::zero(),
+ // Step 6 && Step 7
+ (false, false, _, true) | (false, true, QuirksMode::Quirks, _) => {
+ Rect::new(Point2D::new(window.ScrollX(), window.ScrollY()),
+ Size2D::new(max(window.InnerWidth(), scroll_area.size.width),
+ max(window.InnerHeight(), scroll_area.size.height)))
+ },
+ // Step 9
+ _ => scroll_area
+ }
+ }
+
// https://dom.spec.whatwg.org/#dom-childnode-before
pub fn before(&self, nodes: Vec<NodeOrString>) -> ErrorResult {
// Step 1.
diff --git a/components/script/dom/webidls/Element.webidl b/components/script/dom/webidls/Element.webidl
index 900c2eb24dc..c5c395536ac 100644
--- a/components/script/dom/webidls/Element.webidl
+++ b/components/script/dom/webidls/Element.webidl
@@ -77,6 +77,9 @@ partial interface Element {
DOMRectList getClientRects();
DOMRect getBoundingClientRect();
+ readonly attribute long scrollWidth;
+ readonly attribute long scrollHeight;
+
readonly attribute long clientTop;
readonly attribute long clientLeft;
readonly attribute long clientWidth;
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index e81efa4a77c..ea29485ad3e 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -1106,6 +1106,13 @@ impl Window {
self.layout_rpc.hit_test().node_address
}
+ pub fn scroll_area_query(&self, node: TrustedNodeAddress) -> Rect<i32> {
+ self.reflow(ReflowGoal::ForScriptQuery,
+ ReflowQueryType::NodeScrollGeometryQuery(node),
+ ReflowReason::Query);
+ self.layout_rpc.node_scroll_area().client_rect
+ }
+
pub fn resolved_style_query(&self,
element: TrustedNodeAddress,
pseudo: Option<PseudoElement>,
@@ -1459,6 +1466,7 @@ fn debug_reflow_events(id: PipelineId, goal: &ReflowGoal, query_type: &ReflowQue
ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
ReflowQueryType::HitTestQuery(_n, _o) => "\tHitTestQuery",
ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
+ ReflowQueryType::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery",
ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",
ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery",
diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs
index 9a8cddf97e1..0d063f47bc1 100644
--- a/components/script/layout_interface.rs
+++ b/components/script/layout_interface.rs
@@ -104,6 +104,8 @@ pub trait LayoutRPC {
fn content_boxes(&self) -> ContentBoxesResponse;
/// Requests the geometry of this node. Used by APIs such as `clientTop`.
fn node_geometry(&self) -> NodeGeometryResponse;
+ /// Requests the scroll geometry of this node. Used by APIs such as `scrollTop`.
+ fn node_scroll_area(&self) -> NodeGeometryResponse;
/// Requests the node containing the point of interest
fn hit_test(&self) -> HitTestResponse;
/// Query layout for the resolved value of a given CSS property
@@ -165,6 +167,7 @@ pub enum ReflowQueryType {
ContentBoxesQuery(TrustedNodeAddress),
HitTestQuery(Point2D<f32>, bool),
NodeGeometryQuery(TrustedNodeAddress),
+ NodeScrollGeometryQuery(TrustedNodeAddress),
ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, Atom),
OffsetParentQuery(TrustedNodeAddress),
MarginStyleQuery(TrustedNodeAddress),
diff --git a/components/style/logical_geometry.rs b/components/style/logical_geometry.rs
index 661ef0c6a65..3f3bf1e4509 100644
--- a/components/style/logical_geometry.rs
+++ b/components/style/logical_geometry.rs
@@ -10,6 +10,17 @@ use std::cmp::{max, min};
use std::fmt::{self, Debug, Error, Formatter};
use std::ops::{Add, Sub};
+pub enum BlockFlowDirection {
+ TopToBottom,
+ RightToLeft,
+ LeftToRight
+}
+
+pub enum InlineBaseDirection {
+ LeftToRight,
+ RightToLeft
+}
+
bitflags!(
#[derive(HeapSizeOf, RustcEncodable)]
flags WritingMode: u8 {
@@ -87,6 +98,24 @@ impl WritingMode {
}
#[inline]
+ pub fn block_flow_direction(&self) -> BlockFlowDirection {
+ match (self.is_vertical(), self.is_vertical_lr()) {
+ (false, _) => BlockFlowDirection::TopToBottom,
+ (true, true) => BlockFlowDirection::LeftToRight,
+ (true, false) => BlockFlowDirection::RightToLeft,
+ }
+ }
+
+ #[inline]
+ pub fn inline_base_direction(&self) -> InlineBaseDirection {
+ if self.intersects(FLAG_RTL) {
+ InlineBaseDirection::RightToLeft
+ } else {
+ InlineBaseDirection::LeftToRight
+ }
+ }
+
+ #[inline]
/// The default bidirectional embedding level for this writing mode.
///
/// Returns 0 if the mode is LTR, or 1 otherwise.
diff --git a/tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeight.htm.ini b/tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeight.htm.ini
deleted file mode 100644
index 80549d98995..00000000000
--- a/tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeight.htm.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[scrollWidthHeight.htm]
- type: testharness
- [elemSimple.scrollHeight is its clientHeight]
- expected: FAIL
-
- [elemSimple.scrollWidth is its clientWidth]
- expected: FAIL
-
- [elemOverflow.scrollHeight is the width of its scrolled contents (ignoring padding, since we overflowed)]
- expected: FAIL
-
- [elemNestedOverflow.scrollHeight is the height of its scrolled contents (ignoring padding, since we overflowed)]
- expected: FAIL
-
- [elemNestedOverflow.scrollWidth is the width of its scrolled contents (ignoring padding, since we overflowed)]
- expected: FAIL
-
diff --git a/tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeightWhenNotScrollable.htm.ini b/tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeightWhenNotScrollable.htm.ini
deleted file mode 100644
index 3ec2eb62e46..00000000000
--- a/tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeightWhenNotScrollable.htm.ini
+++ /dev/null
@@ -1,20 +0,0 @@
-[scrollWidthHeightWhenNotScrollable.htm]
- type: testharness
- [elemSimple.scrollHeight is its clientHeight]
- expected: FAIL
-
- [elemSimple.scrollWidth is its clientWidth]
- expected: FAIL
-
- [elemOverflow.scrollHeight is the height of its scrolled contents (ignoring padding, since we overflowed)]
- expected: FAIL
-
- [elemOverflow.scrollHeight is the width of its scrolled contents (ignoring padding, since we overflowed)]
- expected: FAIL
-
- [elemNestedOverflow.scrollHeight is the height of its scrolled contents (ignoring padding, since we overflowed)]
- expected: FAIL
-
- [elemNestedOverflow.scrollWidth is the width of its scrolled contents (ignoring padding, since we overflowed)]
- expected: FAIL
-