// 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 "cc/layers/layer.h" #include "content/browser/android/overscroll_refresh.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/android/resources/resource_manager.h" namespace content { class OverscrollRefreshTest : public OverscrollRefreshClient, public ui::ResourceManager, public testing::Test { public: OverscrollRefreshTest() : refresh_triggered_(false), still_refreshing_(false) {} // OverscrollRefreshClient implementation. void TriggerRefresh() override { refresh_triggered_ = true; still_refreshing_ = true; } bool IsStillRefreshing() const override { return still_refreshing_; } // ResourceManager implementation. base::android::ScopedJavaLocalRef GetJavaObject() override { return base::android::ScopedJavaLocalRef(); } Resource* GetResource(ui::AndroidResourceType res_type, int res_id) override { return nullptr; } void PreloadResource(ui::AndroidResourceType res_type, int res_id) override {} bool GetAndResetRefreshTriggered() { bool triggered = refresh_triggered_; refresh_triggered_ = false; return triggered; } void PullBeyondActivationThreshold(OverscrollRefresh* effect) { for (int i = 0; i < OverscrollRefresh::kMinPullsToActivate; ++i) EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 100))); } protected: scoped_ptr CreateEffect() { const float kDragTargetPixels = 100; const bool kMirror = false; scoped_ptr effect( new OverscrollRefresh(this, this, kDragTargetPixels, kMirror)); const gfx::SizeF kViewportSize(512, 512); const gfx::Vector2dF kScrollOffset; const bool kOverflowYHidden = false; effect->UpdateDisplay(kViewportSize, kScrollOffset, kOverflowYHidden); return effect.Pass(); } void SignalRefreshCompleted() { still_refreshing_ = false; } private: bool refresh_triggered_; bool still_refreshing_; }; TEST_F(OverscrollRefreshTest, Basic) { scoped_ptr effect = CreateEffect(); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollBegin(); EXPECT_FALSE(effect->IsActive()); EXPECT_TRUE(effect->IsAwaitingScrollUpdateAck()); // The initial scroll should not be consumed, as it should first be offered // to content. gfx::Vector2dF scroll_up(0, 10); EXPECT_FALSE(effect->WillHandleScrollUpdate(scroll_up)); EXPECT_FALSE(effect->IsActive()); EXPECT_TRUE(effect->IsAwaitingScrollUpdateAck()); // The unconsumed, overscrolling scroll will trigger the effect-> effect->OnScrollUpdateAck(false); EXPECT_TRUE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); // Further scrolls will be consumed. EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 50))); EXPECT_TRUE(effect->IsActive()); // Even scrolls in the down direction should be consumed. EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, -50))); EXPECT_TRUE(effect->IsActive()); // Feed enough scrolls to the effect to exceeds the threshold. PullBeyondActivationThreshold(effect.get()); EXPECT_TRUE(effect->IsActive()); // Ending the scroll while beyond the threshold should trigger a refresh. gfx::Vector2dF zero_velocity; EXPECT_FALSE(GetAndResetRefreshTriggered()); effect->OnScrollEnd(zero_velocity); EXPECT_TRUE(effect->IsActive()); EXPECT_TRUE(GetAndResetRefreshTriggered()); SignalRefreshCompleted(); // Ensure animation doesn't explode. base::TimeTicks initial_time = base::TimeTicks::Now(); base::TimeTicks current_time = initial_time; scoped_refptr layer = cc::Layer::Create(); while (effect->Animate(current_time, layer.get())) current_time += base::TimeDelta::FromMilliseconds(16); // The effect should terminate in a timely fashion. EXPECT_GT(current_time.ToInternalValue(), initial_time.ToInternalValue()); EXPECT_LE( current_time.ToInternalValue(), (initial_time + base::TimeDelta::FromSeconds(10)).ToInternalValue()); EXPECT_FALSE(effect->IsActive()); } TEST_F(OverscrollRefreshTest, AnimationTerminatesEvenIfRefreshNeverTerminates) { scoped_ptr effect = CreateEffect(); effect->OnScrollBegin(); ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollUpdateAck(false); ASSERT_TRUE(effect->IsActive()); PullBeyondActivationThreshold(effect.get()); ASSERT_TRUE(effect->IsActive()); effect->OnScrollEnd(gfx::Vector2dF(0, 0)); ASSERT_TRUE(GetAndResetRefreshTriggered()); // Verify that the animation terminates even if the triggered refresh // action never terminates (i.e., |still_refreshing_| is always true). base::TimeTicks initial_time = base::TimeTicks::Now(); base::TimeTicks current_time = initial_time; scoped_refptr layer = cc::Layer::Create(); while (effect->Animate(current_time, layer.get())) current_time += base::TimeDelta::FromMilliseconds(16); EXPECT_GT(current_time.ToInternalValue(), initial_time.ToInternalValue()); EXPECT_LE( current_time.ToInternalValue(), (initial_time + base::TimeDelta::FromSeconds(10)).ToInternalValue()); EXPECT_FALSE(effect->IsActive()); } TEST_F(OverscrollRefreshTest, NotTriggeredIfBelowThreshold) { scoped_ptr effect = CreateEffect(); effect->OnScrollBegin(); ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollUpdateAck(false); ASSERT_TRUE(effect->IsActive()); // Terminating the pull before it exceeds the threshold will prevent refresh. EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); effect->OnScrollEnd(gfx::Vector2dF()); EXPECT_FALSE(GetAndResetRefreshTriggered()); } TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialYOffsetIsNotZero) { scoped_ptr effect = CreateEffect(); // A positive y scroll offset at the start of scroll will prevent activation, // even if the subsequent scroll overscrolls upward. gfx::SizeF viewport_size(512, 512); gfx::Vector2dF nonzero_offset(0, 10); bool overflow_y_hidden = false; effect->UpdateDisplay(viewport_size, nonzero_offset, overflow_y_hidden); effect->OnScrollBegin(); ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollUpdateAck(false); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500))); effect->OnScrollEnd(gfx::Vector2dF()); EXPECT_FALSE(GetAndResetRefreshTriggered()); } TEST_F(OverscrollRefreshTest, NotTriggeredIfOverflowYHidden) { scoped_ptr effect = CreateEffect(); // "overflow-y: hidden" on the root layer will prevent activation, // even if the subsequent scroll overscrolls upward. gfx::SizeF viewport_size(512, 512); gfx::Vector2dF zero_offset; bool overflow_y_hidden = true; effect->UpdateDisplay(viewport_size, zero_offset, overflow_y_hidden); effect->OnScrollBegin(); ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollUpdateAck(false); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500))); effect->OnScrollEnd(gfx::Vector2dF()); EXPECT_FALSE(GetAndResetRefreshTriggered()); } TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollDownward) { scoped_ptr effect = CreateEffect(); effect->OnScrollBegin(); // A downward initial scroll will prevent activation, even if the subsequent // scroll overscrolls upward. ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, -10))); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollUpdateAck(false); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500))); effect->OnScrollEnd(gfx::Vector2dF()); EXPECT_FALSE(GetAndResetRefreshTriggered()); } TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollOrTouchConsumed) { scoped_ptr effect = CreateEffect(); effect->OnScrollBegin(); ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck()); // Consumption of the initial touchmove or scroll should prevent future // activation. effect->OnScrollUpdateAck(true); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500))); effect->OnScrollUpdateAck(false); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(effect->IsAwaitingScrollUpdateAck()); EXPECT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500))); effect->OnScrollEnd(gfx::Vector2dF()); EXPECT_FALSE(GetAndResetRefreshTriggered()); } TEST_F(OverscrollRefreshTest, NotTriggeredIfInitialScrollsJanked) { scoped_ptr effect = CreateEffect(); effect->OnScrollBegin(); ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollUpdateAck(false); ASSERT_TRUE(effect->IsActive()); // It should take more than just one or two large scrolls to trigger, // mitigating likelihood of jank triggering the effect-> EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500))); EXPECT_TRUE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 500))); effect->OnScrollEnd(gfx::Vector2dF()); EXPECT_FALSE(GetAndResetRefreshTriggered()); } TEST_F(OverscrollRefreshTest, NotTriggeredIfFlungDownward) { scoped_ptr effect = CreateEffect(); effect->OnScrollBegin(); ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollUpdateAck(false); ASSERT_TRUE(effect->IsActive()); // Ensure the pull exceeds the necessary threshold. PullBeyondActivationThreshold(effect.get()); ASSERT_TRUE(effect->IsActive()); // Terminating the pull with a down-directed fling should prevent triggering. effect->OnScrollEnd(gfx::Vector2dF(0, -1000)); EXPECT_FALSE(GetAndResetRefreshTriggered()); } TEST_F(OverscrollRefreshTest, NotTriggeredIfReleasedWithoutActivation) { scoped_ptr effect = CreateEffect(); effect->OnScrollBegin(); ASSERT_FALSE(effect->WillHandleScrollUpdate(gfx::Vector2dF(0, 10))); ASSERT_TRUE(effect->IsAwaitingScrollUpdateAck()); effect->OnScrollUpdateAck(false); ASSERT_TRUE(effect->IsActive()); // Ensure the pull exceeds the necessary threshold. PullBeyondActivationThreshold(effect.get()); ASSERT_TRUE(effect->IsActive()); // An early release should prevent the refresh action from firing. effect->ReleaseWithoutActivation(); effect->OnScrollEnd(gfx::Vector2dF()); EXPECT_FALSE(GetAndResetRefreshTriggered()); // The early release should trigger a dismissal animation. EXPECT_TRUE(effect->IsActive()); base::TimeTicks initial_time = base::TimeTicks::Now(); base::TimeTicks current_time = initial_time; scoped_refptr layer = cc::Layer::Create(); while (effect->Animate(current_time, layer.get())) current_time += base::TimeDelta::FromMilliseconds(16); EXPECT_GT(current_time.ToInternalValue(), initial_time.ToInternalValue()); EXPECT_FALSE(effect->IsActive()); EXPECT_FALSE(GetAndResetRefreshTriggered()); } } // namespace content