Auto merge of #9968 - izgzhen:scroll, r=asajeffrey

Implement scroll, scrollLeft, scrollTop and friends, addressing issue #9650

This is a work in progress to solve https://github.com/servo/servo/issues/9650. Thanks a lot for helping the review.

- [x] scroll
- [x] scrollTo
- [x] scrollBy
- [x] scrollTop (setter and getter)
- [x] scrollLeft (setter and getter)

The setters will be implemented in another PR after this is merged.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/9968)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-04-20 18:40:26 +05:30
commit 8d988f20c1
15 changed files with 569 additions and 12 deletions

View file

@ -19,6 +19,8 @@ use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions};
use dom::bindings::codegen::UnionTypes::NodeOrString;
use dom::bindings::error::{Error, ErrorResult, Fallible};
use dom::bindings::global::GlobalRef;
@ -39,6 +41,7 @@ use dom::htmlanchorelement::HTMLAnchorElement;
use dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers};
use dom::htmlbuttonelement::HTMLButtonElement;
use dom::htmlcollection::HTMLCollection;
use dom::htmlelement::HTMLElement;
use dom::htmlfieldsetelement::HTMLFieldSetElement;
use dom::htmlfontelement::{HTMLFontElement, HTMLFontElementLayoutHelpers};
use dom::htmlhrelement::{HTMLHRElement, HTMLHRLayoutHelpers};
@ -86,7 +89,7 @@ use string_cache::{Atom, Namespace, QualName};
use style::element_state::*;
use style::error_reporting::ParseErrorReporter;
use style::properties::DeclaredValue;
use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size};
use style::properties::longhands::{self, background_image, border_spacing, font_family, overflow_x, font_size};
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, parse_style_attribute};
use style::selector_impl::{NonTSPseudoClass, ServoSelectorImpl};
use style::values::CSSFloat;
@ -164,6 +167,35 @@ impl Element {
document,
ElementBinding::Wrap)
}
// https://drafts.csswg.org/cssom-view/#css-layout-box
// Elements that have a computed value of the display property
// that is table-column or table-column-group
// FIXME: Currently, it is assumed to be true always
fn has_css_layout_box(&self) -> bool {
true
}
// https://drafts.csswg.org/cssom-view/#potentially-scrollable
fn potentially_scrollable(&self) -> bool {
self.has_css_layout_box() &&
!self.overflow_x_is_visible() &&
!self.overflow_y_is_visible()
}
// used value of overflow-x is "visible"
fn overflow_x_is_visible(&self) -> bool {
let window = window_from_node(self);
let overflow_pair = window.overflow_query(self.upcast::<Node>().to_trusted_node_address());
overflow_pair.x == overflow_x::computed_value::T::visible
}
// used value of overflow-y is "visible"
fn overflow_y_is_visible(&self) -> bool {
let window = window_from_node(self);
let overflow_pair = window.overflow_query(self.upcast::<Node>().to_trusted_node_address());
overflow_pair.y != overflow_x::computed_value::T::visible
}
}
#[allow(unsafe_code)]
@ -1196,6 +1228,49 @@ impl Element {
_ => Err(Error::Syntax)
}
}
// https://drafts.csswg.org/cssom-view/#dom-element-scroll
pub fn scroll(&self, x_: f64, y_: f64, behavior: ScrollBehavior) {
// Step 1.2 or 2.3
let x = if x_.is_finite() { x_ } else { 0.0f64 };
let y = if y_.is_finite() { y_ } else { 0.0f64 };
let node = self.upcast::<Node>();
// Step 3
let doc = node.owner_doc();
// Step 4
if !doc.is_fully_active() {
return;
}
// Step 5
let win = doc.DefaultView();
// Step 7
if *self.root_element() == *self {
if doc.quirks_mode() != Quirks {
win.scroll(x, y, behavior);
}
return;
}
// Step 9
if doc.GetBody().r() == self.downcast::<HTMLElement>() &&
doc.quirks_mode() == Quirks &&
!self.potentially_scrollable() {
win.scroll(x, y, behavior);
return;
}
// Step 10 (TODO)
// Step 11
win.scroll_node(node.to_trusted_node_address(), x, y, behavior);
}
}
impl ElementMethods for Element {
@ -1452,6 +1527,220 @@ impl ElementMethods for Element {
rect.size.height.to_f64_px())
}
// https://drafts.csswg.org/cssom-view/#dom-element-scroll
fn Scroll(&self, options: &ScrollToOptions) {
// Step 1
let left = options.left.unwrap_or(self.ScrollLeft());
let top = options.top.unwrap_or(self.ScrollTop());
self.scroll(left, top, options.parent.behavior);
}
// https://drafts.csswg.org/cssom-view/#dom-element-scroll
fn Scroll_(&self, x: f64, y: f64) {
self.scroll(x, y, ScrollBehavior::Auto);
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrollto
fn ScrollTo(&self, options: &ScrollToOptions) {
self.Scroll(options);
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrollto
fn ScrollTo_(&self, x: f64, y: f64) {
self.Scroll_(x, y);
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrollby
fn ScrollBy(&self, options: &ScrollToOptions) {
// Step 2
let delta_left = options.left.unwrap_or(0.0f64);
let delta_top = options.top.unwrap_or(0.0f64);
let left = self.ScrollLeft();
let top = self.ScrollTop();
self.scroll(left + delta_left, top + delta_top,
options.parent.behavior);
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrollby
fn ScrollBy_(&self, x: f64, y: f64) {
let left = self.ScrollLeft();
let top = self.ScrollTop();
self.scroll(left + x, top + y, ScrollBehavior::Auto);
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
fn ScrollTop(&self) -> f64 {
let node = self.upcast::<Node>();
// Step 1
let doc = node.owner_doc();
// Step 2
if !doc.is_fully_active() {
return 0.0;
}
// Step 3
let win = doc.DefaultView();
// Step 5
if *self.root_element() == *self {
if doc.quirks_mode() == Quirks {
return 0.0;
}
// Step 6
return win.ScrollY() as f64;
}
// Step 7
if doc.GetBody().r() == self.downcast::<HTMLElement>() &&
doc.quirks_mode() == Quirks &&
!self.potentially_scrollable() {
return win.ScrollY() as f64;
}
// Step 8
if !self.has_css_layout_box() {
return 0.0;
}
// Step 9
let point = node.scroll_offset();
return point.y.abs() as f64;
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
fn SetScrollTop(&self, y_: f64) {
let behavior = ScrollBehavior::Auto;
// Step 1, 2
let y = if y_.is_finite() { y_ } else { 0.0f64 };
let node = self.upcast::<Node>();
// Step 3
let doc = node.owner_doc();
// Step 4
if !doc.is_fully_active() {
return;
}
// Step 5
let win = doc.DefaultView();
// Step 7
if *self.root_element() == *self {
if doc.quirks_mode() != Quirks {
win.scroll(win.ScrollX() as f64, y, behavior);
}
return;
}
// Step 9
if doc.GetBody().r() == self.downcast::<HTMLElement>() &&
doc.quirks_mode() == Quirks &&
!self.potentially_scrollable() {
win.scroll(win.ScrollX() as f64, y, behavior);
return;
}
// Step 10 (TODO)
// Step 11
win.scroll_node(node.to_trusted_node_address(), self.ScrollLeft(), y, behavior);
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrolltop
fn ScrollLeft(&self) -> f64 {
let node = self.upcast::<Node>();
// Step 1
let doc = node.owner_doc();
// Step 2
if !doc.is_fully_active() {
return 0.0;
}
// Step 3
let win = doc.DefaultView();
// Step 5
if *self.root_element() == *self {
if doc.quirks_mode() != Quirks {
// Step 6
return win.ScrollX() as f64;
}
return 0.0;
}
// Step 7
if doc.GetBody().r() == self.downcast::<HTMLElement>() &&
doc.quirks_mode() == Quirks &&
!self.potentially_scrollable() {
return win.ScrollX() as f64;
}
// Step 8
if !self.has_css_layout_box() {
return 0.0;
}
// Step 9
let point = node.scroll_offset();
return point.x.abs() as f64;
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrollleft
fn SetScrollLeft(&self, x_: f64) {
let behavior = ScrollBehavior::Auto;
// Step 1, 2
let x = if x_.is_finite() { x_ } else { 0.0f64 };
let node = self.upcast::<Node>();
// Step 3
let doc = node.owner_doc();
// Step 4
if !doc.is_fully_active() {
return;
}
// Step 5
let win = doc.DefaultView();
// Step 7
if *self.root_element() == *self {
if doc.quirks_mode() == Quirks {
return;
}
win.scroll(x, win.ScrollY() as f64, behavior);
return;
}
// Step 9
if doc.GetBody().r() == self.downcast::<HTMLElement>() &&
doc.quirks_mode() == Quirks &&
!self.potentially_scrollable() {
win.scroll(x, win.ScrollY() as f64, behavior);
return;
}
// Step 10 (TODO)
// Step 11
win.scroll_node(node.to_trusted_node_address(), x, self.ScrollTop(), behavior);
}
// https://drafts.csswg.org/cssom-view/#dom-element-scrollwidth
fn ScrollWidth(&self) -> i32 {
self.upcast::<Node>().scroll_area().size.width

View file

@ -614,6 +614,12 @@ impl Node {
}
}
pub fn scroll_offset(&self) -> Point2D<f32> {
let document = self.owner_doc();
let window = document.window();
window.scroll_offset_query(self.to_trusted_node_address())
}
// https://dom.spec.whatwg.org/#dom-childnode-before
pub fn before(&self, nodes: Vec<NodeOrString>) -> ErrorResult {
// Step 1.

View file

@ -81,6 +81,15 @@ partial interface Element {
DOMRectList getClientRects();
DOMRect getBoundingClientRect();
void scroll(optional ScrollToOptions options);
void scroll(unrestricted double x, unrestricted double y);
void scrollTo(optional ScrollToOptions options);
void scrollTo(unrestricted double x, unrestricted double y);
void scrollBy(optional ScrollToOptions options);
void scrollBy(unrestricted double x, unrestricted double y);
attribute unrestricted double scrollTop;
attribute unrestricted double scrollLeft;
readonly attribute long scrollWidth;
readonly attribute long scrollHeight;

View file

@ -74,6 +74,7 @@ use std::sync::{Arc, Mutex};
use string_cache::Atom;
use style::context::ReflowGoal;
use style::error_reporting::ParseErrorReporter;
use style::properties::longhands::{overflow_x};
use style::selector_impl::PseudoElement;
use task_source::TaskSource;
use task_source::dom_manipulation::{DOMManipulationTaskSource, DOMManipulationTask};
@ -911,11 +912,13 @@ impl Window {
//TODO Step 11
//let document = self.Document();
// Step 12
self.perform_a_scroll(x.to_f32().unwrap_or(0.0f32), y.to_f32().unwrap_or(0.0f32), behavior, None);
self.perform_a_scroll(x.to_f32().unwrap_or(0.0f32), y.to_f32().unwrap_or(0.0f32),
LayerId::null(), behavior, None);
}
/// https://drafts.csswg.org/cssom-view/#perform-a-scroll
pub fn perform_a_scroll(&self, x: f32, y: f32, behavior: ScrollBehavior, element: Option<&Element>) {
pub fn perform_a_scroll(&self, x: f32, y: f32, layer_id: LayerId,
behavior: ScrollBehavior, element: Option<&Element>) {
//TODO Step 1
let point = Point2D::new(x, y);
let smooth = match behavior {
@ -934,7 +937,7 @@ impl Window {
self.current_viewport.set(Rect::new(Point2D::new(Au::from_f32_px(x), Au::from_f32_px(y)), size));
self.compositor.send(ScriptToCompositorMsg::ScrollFragmentPoint(
self.pipeline(), LayerId::null(), point, smooth)).unwrap()
self.pipeline(), layer_id, point, smooth)).unwrap()
}
pub fn client_window(&self) -> (Size2D<u32>, Point2D<i32>) {
@ -1122,6 +1125,40 @@ impl Window {
self.layout_rpc.node_scroll_area().client_rect
}
pub fn overflow_query(&self, node: TrustedNodeAddress) -> Point2D<overflow_x::computed_value::T> {
self.reflow(ReflowGoal::ForScriptQuery,
ReflowQueryType::NodeOverflowQuery(node),
ReflowReason::Query);
self.layout_rpc.node_overflow().0.unwrap()
}
pub fn scroll_offset_query(&self, node: TrustedNodeAddress) -> Point2D<f32> {
self.reflow(ReflowGoal::ForScriptQuery,
ReflowQueryType::NodeLayerIdQuery(node),
ReflowReason::Query);
let layer_id = self.layout_rpc.node_layer_id().layer_id;
let pipeline_id = self.id;
let (send, recv) = ipc::channel::<Point2D<f32>>().unwrap();
self.compositor.send(ScriptToCompositorMsg::GetScrollOffset(pipeline_id, layer_id, send)).unwrap();
recv.recv().unwrap_or(Point2D::zero())
}
// https://drafts.csswg.org/cssom-view/#dom-element-scroll
pub fn scroll_node(&self, node: TrustedNodeAddress,
x_: f64, y_: f64, behavior: ScrollBehavior) {
self.reflow(ReflowGoal::ForScriptQuery,
ReflowQueryType::NodeLayerIdQuery(node),
ReflowReason::Query);
let layer_id = self.layout_rpc.node_layer_id().layer_id;
// Step 12
self.perform_a_scroll(x_.to_f32().unwrap_or(0.0f32), y_.to_f32().unwrap_or(0.0f32),
layer_id, behavior, None);
}
pub fn resolved_style_query(&self,
element: TrustedNodeAddress,
pseudo: Option<PseudoElement>,
@ -1476,6 +1513,8 @@ 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::NodeLayerIdQuery(_n) => "\tNodeLayerIdQuery",
ReflowQueryType::NodeOverflowQuery(_n) => "\tNodeOverFlowQuery",
ReflowQueryType::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery",
ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",