// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/events/blink/input_scroll_elasticity_controller.h" #include "cc/input/input_handler.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/public/web/WebInputEvent.h" namespace ui { namespace { enum Phase { PhaseNone = blink::WebMouseWheelEvent::PhaseNone, PhaseBegan = blink::WebMouseWheelEvent::PhaseBegan, PhaseStationary = blink::WebMouseWheelEvent::PhaseStationary, PhaseChanged = blink::WebMouseWheelEvent::PhaseChanged, PhaseEnded = blink::WebMouseWheelEvent::PhaseEnded, PhaseCancelled = blink::WebMouseWheelEvent::PhaseCancelled, PhaseMayBegin = blink::WebMouseWheelEvent::PhaseMayBegin, }; class MockScrollElasticityHelper : public cc::ScrollElasticityHelper { public: MockScrollElasticityHelper() : is_user_scrollable_(true), set_stretch_amount_count_(0), request_begin_frame_count_(0) {} ~MockScrollElasticityHelper() override {} // cc::ScrollElasticityHelper implementation: bool IsUserScrollable() const override { return is_user_scrollable_; } gfx::Vector2dF StretchAmount() const override { return stretch_amount_; } void SetStretchAmount(const gfx::Vector2dF& stretch_amount) override { set_stretch_amount_count_ += 1; stretch_amount_ = stretch_amount; } gfx::ScrollOffset ScrollOffset() const override { return scroll_offset_; } gfx::ScrollOffset MaxScrollOffset() const override { return max_scroll_offset_; } void ScrollBy(const gfx::Vector2dF& delta) override { scroll_offset_ += gfx::ScrollOffset(delta); } void RequestOneBeginFrame() override { request_begin_frame_count_ += 1; } // Counters for number of times functions were called. int request_begin_frame_count() const { return request_begin_frame_count_; } int set_stretch_amount_count() const { return set_stretch_amount_count_; } void SetScrollOffsetAndMaxScrollOffset( const gfx::ScrollOffset& scroll_offset, const gfx::ScrollOffset& max_scroll_offset) { scroll_offset_ = scroll_offset; max_scroll_offset_ = max_scroll_offset; } void SetUserScrollable(bool is_user_scrollable) { is_user_scrollable_ = is_user_scrollable; } private: bool is_user_scrollable_; gfx::Vector2dF stretch_amount_; int set_stretch_amount_count_; int request_begin_frame_count_; gfx::ScrollOffset scroll_offset_; gfx::ScrollOffset max_scroll_offset_; }; class ScrollElasticityControllerTest : public testing::Test { public: ScrollElasticityControllerTest() : controller_(&helper_), input_event_count_(0), current_time_(base::TimeTicks::FromInternalValue(100000000ull)) {} ~ScrollElasticityControllerTest() override {} void SendMouseWheelEvent( Phase phase, Phase momentum_phase, const gfx::Vector2dF& event_delta = gfx::Vector2dF(), const gfx::Vector2dF& overscroll_delta = gfx::Vector2dF()) { blink::WebMouseWheelEvent event; event.phase = static_cast(phase); event.momentumPhase = static_cast(momentum_phase); event.deltaX = -event_delta.x(); event.deltaY = -event_delta.y(); TickCurrentTime(); event.timeStampSeconds = (current_time_ - base::TimeTicks()).InSecondsF(); cc::InputHandlerScrollResult scroll_result; scroll_result.did_overscroll_root = !overscroll_delta.IsZero(); scroll_result.unused_scroll_delta = overscroll_delta; controller_.ObserveWheelEventAndResult(event, scroll_result); input_event_count_ += 1; } void SendGestureScrollBegin(bool inertial) { blink::WebGestureEvent event; event.sourceDevice = blink::WebGestureDeviceTouchpad; event.type = blink::WebInputEvent::GestureScrollBegin; event.data.scrollBegin.inertial = inertial; TickCurrentTime(); event.timeStampSeconds = (current_time_ - base::TimeTicks()).InSecondsF(); controller_.ObserveGestureEventAndResult(event, cc::InputHandlerScrollResult()); input_event_count_ += 1; } void SendGestureScrollUpdate( bool inertial, const gfx::Vector2dF& event_delta = gfx::Vector2dF(), const gfx::Vector2dF& overscroll_delta = gfx::Vector2dF()) { blink::WebGestureEvent event; event.sourceDevice = blink::WebGestureDeviceTouchpad; event.type = blink::WebInputEvent::GestureScrollUpdate; event.data.scrollUpdate.inertial = inertial; event.data.scrollUpdate.deltaX = -event_delta.x(); event.data.scrollUpdate.deltaY = -event_delta.y(); TickCurrentTime(); event.timeStampSeconds = (current_time_ - base::TimeTicks()).InSecondsF(); cc::InputHandlerScrollResult scroll_result; scroll_result.did_overscroll_root = !overscroll_delta.IsZero(); scroll_result.unused_scroll_delta = overscroll_delta; controller_.ObserveGestureEventAndResult(event, scroll_result); input_event_count_ += 1; } void SendGestureScrollEnd() { blink::WebGestureEvent event; event.sourceDevice = blink::WebGestureDeviceTouchpad; event.type = blink::WebInputEvent::GestureScrollEnd; TickCurrentTime(); event.timeStampSeconds = (current_time_ - base::TimeTicks()).InSecondsF(); controller_.ObserveGestureEventAndResult(event, cc::InputHandlerScrollResult()); input_event_count_ += 1; } const base::TimeTicks& TickCurrentTime() { current_time_ += base::TimeDelta::FromSecondsD(1 / 60.f); return current_time_; } void TickCurrentTimeAndAnimate() { TickCurrentTime(); controller_.Animate(current_time_); } MockScrollElasticityHelper helper_; InputScrollElasticityController controller_; int input_event_count_; base::TimeTicks current_time_; }; // Verify that stretching only occurs in one axis at a time, and that it // is biased to the Y axis. TEST_F(ScrollElasticityControllerTest, Axis) { helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(0, 0), gfx::ScrollOffset(0, 0)); // If we push equally in the X and Y directions, we should see a stretch only // in the Y direction. SendMouseWheelEvent(PhaseBegan, PhaseNone); SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(10, 10), gfx::Vector2dF(10, 10)); EXPECT_EQ(1, helper_.set_stretch_amount_count()); EXPECT_EQ(0.f, helper_.StretchAmount().x()); EXPECT_LT(0.f, helper_.StretchAmount().y()); helper_.SetStretchAmount(gfx::Vector2dF()); EXPECT_EQ(2, helper_.set_stretch_amount_count()); SendMouseWheelEvent(PhaseEnded, PhaseNone); EXPECT_EQ(0, helper_.request_begin_frame_count()); // If we push more in the X direction than the Y direction, we should see a // stretch only in the X direction. This decision should be based on the // input delta, not the actual overscroll delta. SendMouseWheelEvent(PhaseBegan, PhaseNone); SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(-25, 10), gfx::Vector2dF(-25, 40)); EXPECT_EQ(3, helper_.set_stretch_amount_count()); EXPECT_GT(0.f, helper_.StretchAmount().x()); EXPECT_EQ(0.f, helper_.StretchAmount().y()); helper_.SetStretchAmount(gfx::Vector2dF()); EXPECT_EQ(4, helper_.set_stretch_amount_count()); SendMouseWheelEvent(PhaseEnded, PhaseNone); EXPECT_EQ(0, helper_.request_begin_frame_count()); } // Verify that stretching only occurs in one axis at a time, and that it // is biased to the Y axis. TEST_F(ScrollElasticityControllerTest, GestureBased_Axis) { helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(0, 0), gfx::ScrollOffset(0, 0)); // If we push equally in the X and Y directions, we should see a stretch only // in the Y direction. SendGestureScrollBegin(false); SendGestureScrollUpdate(false, gfx::Vector2dF(10, 10), gfx::Vector2dF(10, 10)); EXPECT_EQ(1, helper_.set_stretch_amount_count()); EXPECT_EQ(0.f, helper_.StretchAmount().x()); EXPECT_LT(0.f, helper_.StretchAmount().y()); helper_.SetStretchAmount(gfx::Vector2dF()); EXPECT_EQ(2, helper_.set_stretch_amount_count()); SendGestureScrollEnd(); EXPECT_EQ(0, helper_.request_begin_frame_count()); // If we push more in the X direction than the Y direction, we should see a // stretch only in the X direction. This decision should be based on the // input delta, not the actual overscroll delta. SendGestureScrollBegin(false); SendGestureScrollUpdate(false, gfx::Vector2dF(-25, 10), gfx::Vector2dF(-25, 40)); EXPECT_EQ(3, helper_.set_stretch_amount_count()); EXPECT_GT(0.f, helper_.StretchAmount().x()); EXPECT_EQ(0.f, helper_.StretchAmount().y()); helper_.SetStretchAmount(gfx::Vector2dF()); EXPECT_EQ(4, helper_.set_stretch_amount_count()); SendGestureScrollEnd(); EXPECT_EQ(0, helper_.request_begin_frame_count()); } // Verify that we need a total overscroll delta of at least 10 in a pinned // direction before we start stretching. TEST_F(ScrollElasticityControllerTest, MinimumDeltaBeforeStretch) { // We should not start stretching while we are not pinned in the direction // of the scroll (even if there is an overscroll delta). We have to wait for // the regular scroll to eat all of the events. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5), gfx::ScrollOffset(10, 10)); SendMouseWheelEvent(PhaseMayBegin, PhaseNone); SendMouseWheelEvent(PhaseBegan, PhaseNone); SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 10)); SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 10)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Now pin the -X and +Y direction. The first event will not generate a // stretch // because it is below the delta threshold of 10. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(0, 10), gfx::ScrollOffset(10, 10)); SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 8)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Make the next scroll be in the -X direction more than the +Y direction, // which will erase the memory of the previous unused delta of 8. SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(-10, 5), gfx::Vector2dF(-8, 10)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Now push against the pinned +Y direction again by 8. We reset the // previous delta, so this will not generate a stretch. SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 8)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Push against +Y by another 8. This gets us above the delta threshold of // 10, so we should now have had the stretch set, and it should be in the // +Y direction. The scroll in the -X direction should have been forgotten. SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 8)); EXPECT_EQ(1, helper_.set_stretch_amount_count()); EXPECT_EQ(0.f, helper_.StretchAmount().x()); EXPECT_LT(0.f, helper_.StretchAmount().y()); // End the gesture. Because there is a non-zero stretch, we should be in the // animated state, and should have had a frame requested. EXPECT_EQ(0, helper_.request_begin_frame_count()); SendMouseWheelEvent(PhaseEnded, PhaseNone); EXPECT_EQ(1, helper_.request_begin_frame_count()); } // Verify that we need a total overscroll delta of at least 10 in a pinned // direction before we start stretching. TEST_F(ScrollElasticityControllerTest, GestureBased_MinimumDeltaBeforeStretch) { // We should not start stretching while we are not pinned in the direction // of the scroll (even if there is an overscroll delta). We have to wait for // the regular scroll to eat all of the events. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5), gfx::ScrollOffset(10, 10)); SendGestureScrollBegin(false); SendGestureScrollUpdate(false, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 10)); SendGestureScrollUpdate(false, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 10)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Now pin the -X and +Y direction. The first event will not generate a // stretch // because it is below the delta threshold of 10. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(0, 10), gfx::ScrollOffset(10, 10)); SendGestureScrollUpdate(false, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 8)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Make the next scroll be in the -X direction more than the +Y direction, // which will erase the memory of the previous unused delta of 8. SendGestureScrollUpdate(false, gfx::Vector2dF(-10, 5), gfx::Vector2dF(-8, 10)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Now push against the pinned +Y direction again by 8. We reset the // previous delta, so this will not generate a stretch. SendGestureScrollUpdate(false, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 8)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Push against +Y by another 8. This gets us above the delta threshold of // 10, so we should now have had the stretch set, and it should be in the // +Y direction. The scroll in the -X direction should have been forgotten. SendGestureScrollUpdate(false, gfx::Vector2dF(0, 10), gfx::Vector2dF(0, 8)); EXPECT_EQ(1, helper_.set_stretch_amount_count()); EXPECT_EQ(0.f, helper_.StretchAmount().x()); EXPECT_LT(0.f, helper_.StretchAmount().y()); // End the gesture. Because there is a non-zero stretch, we should be in the // animated state, and should have had a frame requested. EXPECT_EQ(0, helper_.request_begin_frame_count()); SendGestureScrollEnd(); EXPECT_EQ(1, helper_.request_begin_frame_count()); } // Verify that a stretch caused by a momentum scroll will switch to the // animating mode, where input events are ignored, and the stretch is updated // while animating. TEST_F(ScrollElasticityControllerTest, MomentumAnimate) { // Do an active scroll, then switch to the momentum phase and scroll for a // bit. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5), gfx::ScrollOffset(10, 10)); SendMouseWheelEvent(PhaseBegan, PhaseNone); SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendMouseWheelEvent(PhaseChanged, PhaseNone, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendMouseWheelEvent(PhaseEnded, PhaseNone); SendMouseWheelEvent(PhaseNone, PhaseBegan); SendMouseWheelEvent(PhaseNone, PhaseChanged, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendMouseWheelEvent(PhaseNone, PhaseChanged, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendMouseWheelEvent(PhaseNone, PhaseChanged, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Hit the -Y edge and overscroll slightly, but not enough to go over the // threshold to cause a stretch. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 0), gfx::ScrollOffset(10, 10)); SendMouseWheelEvent(PhaseNone, PhaseChanged, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -8)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); EXPECT_EQ(0, helper_.request_begin_frame_count()); // Take another step, this time going over the threshold. This should update // the stretch amount, and then switch to the animating mode. SendMouseWheelEvent(PhaseNone, PhaseChanged, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -80)); EXPECT_EQ(1, helper_.set_stretch_amount_count()); EXPECT_EQ(1, helper_.request_begin_frame_count()); EXPECT_GT(-1.f, helper_.StretchAmount().y()); // Subsequent momentum events should do nothing. SendMouseWheelEvent(PhaseNone, PhaseChanged, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -80)); SendMouseWheelEvent(PhaseNone, PhaseChanged, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -80)); SendMouseWheelEvent(PhaseNone, PhaseEnded, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -80)); EXPECT_EQ(1, helper_.set_stretch_amount_count()); EXPECT_EQ(1, helper_.request_begin_frame_count()); // Subsequent animate events should update the stretch amount and request // another frame. TickCurrentTimeAndAnimate(); EXPECT_EQ(2, helper_.set_stretch_amount_count()); EXPECT_EQ(2, helper_.request_begin_frame_count()); EXPECT_GT(-1.f, helper_.StretchAmount().y()); // Touching the trackpad (a PhaseMayBegin event) should disable animation. SendMouseWheelEvent(PhaseMayBegin, PhaseNone); TickCurrentTimeAndAnimate(); EXPECT_EQ(2, helper_.set_stretch_amount_count()); EXPECT_EQ(2, helper_.request_begin_frame_count()); // Releasing the trackpad should re-enable animation. SendMouseWheelEvent(PhaseCancelled, PhaseNone); EXPECT_EQ(2, helper_.set_stretch_amount_count()); EXPECT_EQ(3, helper_.request_begin_frame_count()); TickCurrentTimeAndAnimate(); EXPECT_EQ(3, helper_.set_stretch_amount_count()); EXPECT_EQ(4, helper_.request_begin_frame_count()); // Keep animating frames until the stretch returns to rest. int stretch_count = 3; int begin_frame_count = 4; while (1) { TickCurrentTimeAndAnimate(); if (helper_.StretchAmount().IsZero()) { stretch_count += 1; EXPECT_EQ(stretch_count, helper_.set_stretch_amount_count()); EXPECT_EQ(begin_frame_count, helper_.request_begin_frame_count()); break; } stretch_count += 1; begin_frame_count += 1; EXPECT_EQ(stretch_count, helper_.set_stretch_amount_count()); EXPECT_EQ(begin_frame_count, helper_.request_begin_frame_count()); } // After coming to rest, no subsequent animate calls change anything. TickCurrentTimeAndAnimate(); EXPECT_EQ(stretch_count, helper_.set_stretch_amount_count()); EXPECT_EQ(begin_frame_count, helper_.request_begin_frame_count()); } // Verify that a stretch caused by a momentum scroll will switch to the // animating mode, where input events are ignored, and the stretch is updated // while animating. TEST_F(ScrollElasticityControllerTest, GestureBased_MomentumAnimate) { // Do an active scroll, then switch to the momentum phase and scroll for a // bit. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5), gfx::ScrollOffset(10, 10)); SendGestureScrollBegin(false); SendGestureScrollUpdate(false, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendGestureScrollUpdate(false, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendGestureScrollUpdate(false, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendGestureScrollEnd(); SendGestureScrollBegin(true); SendGestureScrollUpdate(true, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendGestureScrollUpdate(true, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); SendGestureScrollUpdate(true, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, 0)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Hit the -Y edge and overscroll slightly, but not enough to go over the // threshold to cause a stretch. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 0), gfx::ScrollOffset(10, 10)); SendGestureScrollUpdate(true, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -8)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); EXPECT_EQ(0, helper_.request_begin_frame_count()); // Take another step, this time going over the threshold. This should update // the stretch amount, and then switch to the animating mode. SendGestureScrollUpdate(true, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -80)); EXPECT_EQ(1, helper_.set_stretch_amount_count()); EXPECT_EQ(1, helper_.request_begin_frame_count()); EXPECT_GT(-1.f, helper_.StretchAmount().y()); // Subsequent momentum events should do nothing. SendGestureScrollUpdate(true, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -80)); SendGestureScrollUpdate(true, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -80)); SendGestureScrollUpdate(true, gfx::Vector2dF(0, -80), gfx::Vector2dF(0, -80)); SendGestureScrollEnd(); EXPECT_EQ(1, helper_.set_stretch_amount_count()); EXPECT_EQ(1, helper_.request_begin_frame_count()); // Subsequent animate events should update the stretch amount and request // another frame. TickCurrentTimeAndAnimate(); EXPECT_EQ(2, helper_.set_stretch_amount_count()); EXPECT_EQ(2, helper_.request_begin_frame_count()); EXPECT_GT(-1.f, helper_.StretchAmount().y()); // Touching the trackpad (a PhaseMayBegin event) should disable animation. SendGestureScrollBegin(false); TickCurrentTimeAndAnimate(); EXPECT_EQ(2, helper_.set_stretch_amount_count()); EXPECT_EQ(2, helper_.request_begin_frame_count()); // Releasing the trackpad should re-enable animation. SendGestureScrollEnd(); EXPECT_EQ(2, helper_.set_stretch_amount_count()); EXPECT_EQ(3, helper_.request_begin_frame_count()); TickCurrentTimeAndAnimate(); EXPECT_EQ(3, helper_.set_stretch_amount_count()); EXPECT_EQ(4, helper_.request_begin_frame_count()); // Keep animating frames until the stretch returns to rest. int stretch_count = 3; int begin_frame_count = 4; while (1) { TickCurrentTimeAndAnimate(); if (helper_.StretchAmount().IsZero()) { stretch_count += 1; EXPECT_EQ(stretch_count, helper_.set_stretch_amount_count()); EXPECT_EQ(begin_frame_count, helper_.request_begin_frame_count()); break; } stretch_count += 1; begin_frame_count += 1; EXPECT_EQ(stretch_count, helper_.set_stretch_amount_count()); EXPECT_EQ(begin_frame_count, helper_.request_begin_frame_count()); } // After coming to rest, no subsequent animate calls change anything. TickCurrentTimeAndAnimate(); EXPECT_EQ(stretch_count, helper_.set_stretch_amount_count()); EXPECT_EQ(begin_frame_count, helper_.request_begin_frame_count()); } // Verify that a stretch opposing a scroll is correctly resolved. TEST_F(ScrollElasticityControllerTest, ReconcileStretchAndScroll) { SendMouseWheelEvent(PhaseBegan, PhaseNone); // Verify completely knocking out the scroll in the -Y direction. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5), gfx::ScrollOffset(10, 10)); helper_.SetStretchAmount(gfx::Vector2dF(0, -10)); controller_.ReconcileStretchAndScroll(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, -5)); EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(5, 0)); // Verify partially knocking out the scroll in the -Y direction. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 8), gfx::ScrollOffset(10, 10)); helper_.SetStretchAmount(gfx::Vector2dF(0, -5)); controller_.ReconcileStretchAndScroll(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(5, 3)); // Verify completely knocking out the scroll in the +X direction. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5), gfx::ScrollOffset(10, 10)); helper_.SetStretchAmount(gfx::Vector2dF(10, 0)); controller_.ReconcileStretchAndScroll(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(5, 0)); EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(10, 5)); // Verify partially knocking out the scroll in the +X and +Y directions. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(2, 3), gfx::ScrollOffset(10, 10)); helper_.SetStretchAmount(gfx::Vector2dF(5, 5)); controller_.ReconcileStretchAndScroll(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(7, 8)); } // Verify that a stretch opposing a scroll is correctly resolved. TEST_F(ScrollElasticityControllerTest, GestureBased_ReconcileStretchAndScroll) { SendGestureScrollBegin(false); // Verify completely knocking out the scroll in the -Y direction. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5), gfx::ScrollOffset(10, 10)); helper_.SetStretchAmount(gfx::Vector2dF(0, -10)); controller_.ReconcileStretchAndScroll(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, -5)); EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(5, 0)); // Verify partially knocking out the scroll in the -Y direction. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 8), gfx::ScrollOffset(10, 10)); helper_.SetStretchAmount(gfx::Vector2dF(0, -5)); controller_.ReconcileStretchAndScroll(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(5, 3)); // Verify completely knocking out the scroll in the +X direction. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5), gfx::ScrollOffset(10, 10)); helper_.SetStretchAmount(gfx::Vector2dF(10, 0)); controller_.ReconcileStretchAndScroll(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(5, 0)); EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(10, 5)); // Verify partially knocking out the scroll in the +X and +Y directions. helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(2, 3), gfx::ScrollOffset(10, 10)); helper_.SetStretchAmount(gfx::Vector2dF(5, 5)); controller_.ReconcileStretchAndScroll(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(7, 8)); } // Verify that stretching only happens when the area is user scrollable. TEST_F(ScrollElasticityControllerTest, UserScrollableRequiredForStretch) { helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(0, 0), gfx::ScrollOffset(10, 10)); gfx::Vector2dF delta(0, -15); // Do an active scroll, and ensure that the stretch amount doesn't change. helper_.SetUserScrollable(false); SendMouseWheelEvent(PhaseBegan, PhaseNone); SendMouseWheelEvent(PhaseChanged, PhaseNone, delta, delta); SendMouseWheelEvent(PhaseChanged, PhaseNone, delta, delta); SendMouseWheelEvent(PhaseEnded, PhaseNone); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); SendMouseWheelEvent(PhaseNone, PhaseBegan); SendMouseWheelEvent(PhaseNone, PhaseChanged, delta, delta); SendMouseWheelEvent(PhaseNone, PhaseChanged, delta, delta); SendMouseWheelEvent(PhaseNone, PhaseEnded); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Re-enable user scrolling and ensure that stretching is re-enabled. helper_.SetUserScrollable(true); SendMouseWheelEvent(PhaseBegan, PhaseNone); SendMouseWheelEvent(PhaseChanged, PhaseNone, delta, delta); SendMouseWheelEvent(PhaseChanged, PhaseNone, delta, delta); SendMouseWheelEvent(PhaseEnded, PhaseNone); EXPECT_NE(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_GT(helper_.set_stretch_amount_count(), 0); SendMouseWheelEvent(PhaseNone, PhaseBegan); SendMouseWheelEvent(PhaseNone, PhaseChanged, delta, delta); SendMouseWheelEvent(PhaseNone, PhaseChanged, delta, delta); SendMouseWheelEvent(PhaseNone, PhaseEnded); EXPECT_NE(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_GT(helper_.set_stretch_amount_count(), 0); // Disable user scrolling and tick the timer until the stretch goes back // to zero. Ensure that the return to zero doesn't happen immediately. helper_.SetUserScrollable(false); int ticks_to_zero = 0; while (1) { TickCurrentTimeAndAnimate(); if (helper_.StretchAmount().IsZero()) break; ticks_to_zero += 1; } EXPECT_GT(ticks_to_zero, 3); } // Verify that stretching only happens when the area is user scrollable. TEST_F(ScrollElasticityControllerTest, GestureBased_UserScrollableRequiredForStretch) { helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(0, 0), gfx::ScrollOffset(10, 10)); gfx::Vector2dF delta(0, -15); // Do an active scroll, and ensure that the stretch amount doesn't change. helper_.SetUserScrollable(false); SendGestureScrollBegin(false); SendGestureScrollUpdate(false, delta, delta); SendGestureScrollUpdate(false, delta, delta); SendGestureScrollEnd(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); SendGestureScrollBegin(true); SendGestureScrollUpdate(true, delta, delta); SendGestureScrollUpdate(true, delta, delta); SendGestureScrollEnd(); EXPECT_EQ(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_EQ(0, helper_.set_stretch_amount_count()); // Re-enable user scrolling and ensure that stretching is re-enabled. helper_.SetUserScrollable(true); SendGestureScrollBegin(false); SendGestureScrollUpdate(false, delta, delta); SendGestureScrollUpdate(false, delta, delta); SendGestureScrollEnd(); EXPECT_NE(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_GT(helper_.set_stretch_amount_count(), 0); SendGestureScrollBegin(true); SendGestureScrollUpdate(true, delta, delta); SendGestureScrollUpdate(true, delta, delta); SendGestureScrollEnd(); EXPECT_NE(helper_.StretchAmount(), gfx::Vector2dF(0, 0)); EXPECT_GT(helper_.set_stretch_amount_count(), 0); // Disable user scrolling and tick the timer until the stretch goes back // to zero. Ensure that the return to zero doesn't happen immediately. helper_.SetUserScrollable(false); int ticks_to_zero = 0; while (1) { TickCurrentTimeAndAnimate(); if (helper_.StretchAmount().IsZero()) break; ticks_to_zero += 1; } EXPECT_GT(ticks_to_zero, 3); } } // namespace } // namespace ui