diff options
-rw-r--r-- | components/layout/layout_thread.rs | 13 | ||||
-rw-r--r-- | components/layout/query.rs | 129 | ||||
-rw-r--r-- | components/script/dom/element.rs | 10 | ||||
-rw-r--r-- | components/script/dom/node.rs | 41 | ||||
-rw-r--r-- | components/script/dom/webidls/Element.webidl | 3 | ||||
-rw-r--r-- | components/script/dom/window.rs | 8 | ||||
-rw-r--r-- | components/script/layout_interface.rs | 3 | ||||
-rw-r--r-- | components/style/logical_geometry.rs | 29 | ||||
-rw-r--r-- | tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeight.htm.ini | 17 | ||||
-rw-r--r-- | tests/wpt/metadata-css/cssom-view-1_dev/html/scrollWidthHeightWhenNotScrollable.htm.ini | 20 |
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 - |