// Copyright (c) 2013 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 "content/browser/web_contents/aura/window_slider.h" #include "base/bind.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/test/aura_test_base.h" #include "ui/aura/test/test_window_delegate.h" #include "ui/aura/window.h" #include "ui/base/hit_test.h" #include "ui/compositor/scoped_animation_duration_scale_mode.h" #include "ui/compositor/scoped_layer_animation_settings.h" #include "ui/compositor/test/layer_animator_test_controller.h" #include "ui/events/event_processor.h" #include "ui/events/event_utils.h" #include "ui/events/test/event_generator.h" #include "ui/gfx/frame_time.h" namespace content { void DispatchEventDuringScrollCallback(ui::EventProcessor* dispatcher, ui::Event* event, ui::EventType type, const gfx::Vector2dF& delta) { if (type != ui::ET_GESTURE_SCROLL_UPDATE) return; ui::EventDispatchDetails details = dispatcher->OnEventFromSource(event); CHECK(!details.dispatcher_destroyed); } void ChangeSliderOwnerDuringScrollCallback(scoped_ptr* window, WindowSlider* slider, ui::EventType type, const gfx::Vector2dF& delta) { if (type != ui::ET_GESTURE_SCROLL_UPDATE) return; aura::Window* new_window = new aura::Window(NULL); new_window->Init(aura::WINDOW_LAYER_TEXTURED); new_window->Show(); slider->ChangeOwner(new_window); (*window)->parent()->AddChild(new_window); window->reset(new_window); } void ConfirmSlideDuringScrollCallback(WindowSlider* slider, ui::EventType type, const gfx::Vector2dF& delta) { static float total_delta_x = 0; if (type == ui::ET_GESTURE_SCROLL_BEGIN) total_delta_x = 0; if (type == ui::ET_GESTURE_SCROLL_UPDATE) { total_delta_x += delta.x(); if (total_delta_x >= 70) EXPECT_TRUE(slider->IsSlideInProgress()); } else { EXPECT_FALSE(slider->IsSlideInProgress()); } } void ConfirmNoSlideDuringScrollCallback(WindowSlider* slider, ui::EventType type, const gfx::Vector2dF& delta) { EXPECT_FALSE(slider->IsSlideInProgress()); } // The window delegate does not receive any events. class NoEventWindowDelegate : public aura::test::TestWindowDelegate { public: NoEventWindowDelegate() { } virtual ~NoEventWindowDelegate() {} private: // Overridden from aura::WindowDelegate: virtual bool HasHitTestMask() const override { return true; } DISALLOW_COPY_AND_ASSIGN(NoEventWindowDelegate); }; class WindowSliderDelegateTest : public WindowSlider::Delegate { public: WindowSliderDelegateTest() : can_create_layer_(true), created_back_layer_(false), created_front_layer_(false), slide_completing_(false), slide_completed_(false), slide_aborted_(false), slider_destroyed_(false) { } virtual ~WindowSliderDelegateTest() { // Make sure slide_completed() gets called if slide_completing() was called. CHECK(!slide_completing_ || slide_completed_); } void Reset() { can_create_layer_ = true; created_back_layer_ = false; created_front_layer_ = false; slide_completing_ = false; slide_completed_ = false; slide_aborted_ = false; slider_destroyed_ = false; } void SetCanCreateLayer(bool can_create_layer) { can_create_layer_ = can_create_layer; } bool created_back_layer() const { return created_back_layer_; } bool created_front_layer() const { return created_front_layer_; } bool slide_completing() const { return slide_completing_; } bool slide_completed() const { return slide_completed_; } bool slide_aborted() const { return slide_aborted_; } bool slider_destroyed() const { return slider_destroyed_; } protected: ui::Layer* CreateLayerForTest() { CHECK(can_create_layer_); ui::Layer* layer = new ui::Layer(ui::LAYER_SOLID_COLOR); layer->SetColor(SK_ColorRED); return layer; } // Overridden from WindowSlider::Delegate: virtual ui::Layer* CreateBackLayer() override { if (!can_create_layer_) return NULL; created_back_layer_ = true; return CreateLayerForTest(); } virtual ui::Layer* CreateFrontLayer() override { if (!can_create_layer_) return NULL; created_front_layer_ = true; return CreateLayerForTest(); } virtual void OnWindowSlideCompleted(scoped_ptr layer) override { slide_completed_ = true; } virtual void OnWindowSlideCompleting() override { slide_completing_ = true; } virtual void OnWindowSlideAborted() override { slide_aborted_ = true; } virtual void OnWindowSliderDestroyed() override { slider_destroyed_ = true; } private: bool can_create_layer_; bool created_back_layer_; bool created_front_layer_; bool slide_completing_; bool slide_completed_; bool slide_aborted_; bool slider_destroyed_; DISALLOW_COPY_AND_ASSIGN(WindowSliderDelegateTest); }; // This delegate destroys the owner window when the slider is destroyed. class WindowSliderDeleteOwnerOnDestroy : public WindowSliderDelegateTest { public: explicit WindowSliderDeleteOwnerOnDestroy(aura::Window* owner) : owner_(owner) { } virtual ~WindowSliderDeleteOwnerOnDestroy() {} private: // Overridden from WindowSlider::Delegate: virtual void OnWindowSliderDestroyed() override { WindowSliderDelegateTest::OnWindowSliderDestroyed(); delete owner_; } aura::Window* owner_; DISALLOW_COPY_AND_ASSIGN(WindowSliderDeleteOwnerOnDestroy); }; // This delegate destroyes the owner window when a slide is completed. class WindowSliderDeleteOwnerOnComplete : public WindowSliderDelegateTest { public: explicit WindowSliderDeleteOwnerOnComplete(aura::Window* owner) : owner_(owner) { } virtual ~WindowSliderDeleteOwnerOnComplete() {} private: // Overridden from WindowSlider::Delegate: virtual void OnWindowSlideCompleted(scoped_ptr layer) override { WindowSliderDelegateTest::OnWindowSlideCompleted(layer.Pass()); delete owner_; } aura::Window* owner_; DISALLOW_COPY_AND_ASSIGN(WindowSliderDeleteOwnerOnComplete); }; typedef aura::test::AuraTestBase WindowSliderTest; TEST_F(WindowSliderTest, WindowSlideUsingGesture) { scoped_ptr window(CreateNormalWindow(0, root_window(), NULL)); window->SetBounds(gfx::Rect(0, 0, 400, 400)); WindowSliderDelegateTest slider_delegate; ui::test::EventGenerator generator(root_window()); // Generate a horizontal overscroll. WindowSlider* slider = new WindowSlider(&slider_delegate, root_window(), window.get()); generator.GestureScrollSequenceWithCallback( gfx::Point(10, 10), gfx::Point(180, 10), base::TimeDelta::FromMilliseconds(10), 10, base::Bind(&ConfirmSlideDuringScrollCallback, slider)); EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_TRUE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.slider_destroyed()); EXPECT_FALSE(slider->IsSlideInProgress()); slider_delegate.Reset(); window->SetTransform(gfx::Transform()); // Generate a horizontal overscroll in the reverse direction. generator.GestureScrollSequenceWithCallback( gfx::Point(180, 10), gfx::Point(10, 10), base::TimeDelta::FromMilliseconds(10), 10, base::Bind(&ConfirmSlideDuringScrollCallback, slider)); EXPECT_TRUE(slider_delegate.created_front_layer()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_TRUE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.created_back_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.slider_destroyed()); EXPECT_FALSE(slider->IsSlideInProgress()); slider_delegate.Reset(); // Generate a vertical overscroll. generator.GestureScrollSequenceWithCallback( gfx::Point(10, 10), gfx::Point(10, 80), base::TimeDelta::FromMilliseconds(10), 10, base::Bind(&ConfirmNoSlideDuringScrollCallback, slider)); EXPECT_FALSE(slider_delegate.created_back_layer()); EXPECT_FALSE(slider_delegate.slide_completing()); EXPECT_FALSE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider->IsSlideInProgress()); slider_delegate.Reset(); // Generate a horizontal scroll that starts overscroll, but doesn't scroll // enough to complete it. generator.GestureScrollSequenceWithCallback( gfx::Point(10, 10), gfx::Point(80, 10), base::TimeDelta::FromMilliseconds(10), 10, base::Bind(&ConfirmSlideDuringScrollCallback, slider)); EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_TRUE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_completing()); EXPECT_FALSE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.slider_destroyed()); EXPECT_FALSE(slider->IsSlideInProgress()); slider_delegate.Reset(); // Destroy the window. This should destroy the slider. window.reset(); EXPECT_TRUE(slider_delegate.slider_destroyed()); } // Tests that the window slide is interrupted when a different type of event // happens. TEST_F(WindowSliderTest, WindowSlideIsCancelledOnEvent) { scoped_ptr window(CreateNormalWindow(0, root_window(), NULL)); window->SetBounds(gfx::Rect(0, 0, 400, 400)); WindowSliderDelegateTest slider_delegate; ui::Event* events[] = { new ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(55, 10), gfx::Point(55, 10), 0, 0), new ui::KeyEvent('a', ui::VKEY_A, ui::EF_NONE), NULL }; new WindowSlider(&slider_delegate, root_window(), window.get()); for (int i = 0; events[i]; ++i) { // Generate a horizontal overscroll. ui::test::EventGenerator generator(root_window()); generator.GestureScrollSequenceWithCallback( gfx::Point(10, 10), gfx::Point(80, 10), base::TimeDelta::FromMilliseconds(10), 1, base::Bind(&DispatchEventDuringScrollCallback, root_window()->GetHost()->event_processor(), base::Owned(events[i]))); EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_TRUE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_completing()); EXPECT_FALSE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.slider_destroyed()); slider_delegate.Reset(); } window.reset(); EXPECT_TRUE(slider_delegate.slider_destroyed()); } // Tests that the window slide can continue after it is interrupted by another // event if the user continues scrolling. TEST_F(WindowSliderTest, WindowSlideInterruptedThenContinues) { scoped_ptr window(CreateNormalWindow(0, root_window(), NULL)); window->SetBounds(gfx::Rect(0, 0, 400, 400)); WindowSliderDelegateTest slider_delegate; ui::ScopedAnimationDurationScaleMode normal_duration_( ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); ui::LayerAnimator* animator = window->layer()->GetAnimator(); animator->set_disable_timer_for_test(true); ui::LayerAnimatorTestController test_controller(animator); WindowSlider* slider = new WindowSlider(&slider_delegate, root_window(), window.get()); ui::MouseEvent interrupt_event(ui::ET_MOUSE_MOVED, gfx::Point(55, 10), gfx::Point(55, 10), 0, 0); ui::test::EventGenerator generator(root_window()); // Start the scroll sequence. Scroll forward so that |window|'s layer is the // one animating. const int kTouchId = 5; ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), kTouchId, ui::EventTimeForNow()); generator.Dispatch(&press); // First scroll event of the sequence. ui::TouchEvent move1(ui::ET_TOUCH_MOVED, gfx::Point(100, 10), kTouchId, ui::EventTimeForNow()); generator.Dispatch(&move1); EXPECT_TRUE(slider->IsSlideInProgress()); EXPECT_FALSE(animator->is_animating()); // Dispatch the event after the first scroll and confirm it interrupts the // scroll and starts the "reset slide" animation. generator.Dispatch(&interrupt_event); EXPECT_TRUE(slider->IsSlideInProgress()); EXPECT_TRUE(animator->is_animating()); EXPECT_TRUE(slider_delegate.created_back_layer()); // slide_aborted() should be false because the 'reset slide' animation // hasn't completed yet. EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_completing()); EXPECT_FALSE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.slider_destroyed()); slider_delegate.Reset(); // Second scroll event of the sequence. ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(200, 10), kTouchId, ui::EventTimeForNow()); generator.Dispatch(&move2); // The second scroll should instantly cause the animation to complete. EXPECT_FALSE(animator->is_animating()); EXPECT_FALSE(slider_delegate.created_back_layer()); // The ResetScroll() animation was completed, so now slide_aborted() // should be true. EXPECT_TRUE(slider_delegate.slide_aborted()); // Third scroll event of the sequence. ui::TouchEvent move3(ui::ET_TOUCH_MOVED, gfx::Point(300, 10), kTouchId, ui::EventTimeForNow()); generator.Dispatch(&move3); // The third scroll should re-start the sliding. EXPECT_TRUE(slider->IsSlideInProgress()); EXPECT_TRUE(slider_delegate.created_back_layer()); // Generate the release event, finishing the scroll sequence. ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(300, 10), kTouchId, ui::EventTimeForNow()); generator.Dispatch(&release); // When the scroll gesture ends, the slide animation should start. EXPECT_TRUE(slider->IsSlideInProgress()); EXPECT_TRUE(animator->is_animating()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.slider_destroyed()); // Progress the animator to complete the slide animation. ui::ScopedLayerAnimationSettings settings(animator); base::TimeDelta duration = settings.GetTransitionDuration(); test_controller.StartThreadedAnimationsIfNeeded(); animator->Step(gfx::FrameTime::Now() + duration); EXPECT_TRUE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.slider_destroyed()); window.reset(); EXPECT_TRUE(slider_delegate.slider_destroyed()); } // Tests that the slide works correctly when the owner of the window changes // during the duration of the slide. TEST_F(WindowSliderTest, OwnerWindowChangesDuringWindowSlide) { scoped_ptr parent(CreateNormalWindow(0, root_window(), NULL)); NoEventWindowDelegate window_delegate; window_delegate.set_window_component(HTNOWHERE); scoped_ptr window(CreateNormalWindow(1, parent.get(), &window_delegate)); WindowSliderDelegateTest slider_delegate; scoped_ptr slider( new WindowSlider(&slider_delegate, parent.get(), window.get())); // Generate a horizontal scroll, and change the owner in the middle of the // scroll. ui::test::EventGenerator generator(root_window()); aura::Window* old_window = window.get(); generator.GestureScrollSequenceWithCallback( gfx::Point(10, 10), gfx::Point(80, 10), base::TimeDelta::FromMilliseconds(10), 1, base::Bind(&ChangeSliderOwnerDuringScrollCallback, base::Unretained(&window), slider.get())); aura::Window* new_window = window.get(); EXPECT_NE(old_window, new_window); EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_TRUE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.slider_destroyed()); } // If the delegate doesn't create the layer to show while sliding, WindowSlider // shouldn't start the slide or change delegate's state in any way in response // to user input. TEST_F(WindowSliderTest, NoSlideWhenLayerCantBeCreated) { scoped_ptr window(CreateNormalWindow(0, root_window(), NULL)); window->SetBounds(gfx::Rect(0, 0, 400, 400)); WindowSliderDelegateTest slider_delegate; slider_delegate.SetCanCreateLayer(false); WindowSlider* slider = new WindowSlider(&slider_delegate, root_window(), window.get()); ui::test::EventGenerator generator(root_window()); // No slide in progress should be reported during scroll since the layer // wasn't created. generator.GestureScrollSequenceWithCallback( gfx::Point(10, 10), gfx::Point(180, 10), base::TimeDelta::FromMilliseconds(10), 1, base::Bind(&ConfirmNoSlideDuringScrollCallback, slider)); EXPECT_FALSE(slider_delegate.created_back_layer()); EXPECT_FALSE(slider_delegate.slide_completing()); EXPECT_FALSE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.slider_destroyed()); window->SetTransform(gfx::Transform()); slider_delegate.SetCanCreateLayer(true); generator.GestureScrollSequenceWithCallback( gfx::Point(10, 10), gfx::Point(180, 10), base::TimeDelta::FromMilliseconds(10), 10, base::Bind(&ConfirmSlideDuringScrollCallback, slider)); EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_TRUE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.slider_destroyed()); window.reset(); EXPECT_TRUE(slider_delegate.slider_destroyed()); } // Tests that the owner window can be destroyed from |OnWindowSliderDestroyed()| // delegate callback without causing a crash. TEST_F(WindowSliderTest, OwnerIsDestroyedOnSliderDestroy) { size_t child_windows = root_window()->children().size(); aura::Window* window = CreateNormalWindow(0, root_window(), NULL); window->SetBounds(gfx::Rect(0, 0, 400, 400)); EXPECT_EQ(child_windows + 1, root_window()->children().size()); WindowSliderDeleteOwnerOnDestroy slider_delegate(window); ui::test::EventGenerator generator(root_window()); // Generate a horizontal overscroll. scoped_ptr slider( new WindowSlider(&slider_delegate, root_window(), window)); generator.GestureScrollSequence(gfx::Point(10, 10), gfx::Point(180, 10), base::TimeDelta::FromMilliseconds(10), 10); EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_TRUE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.slider_destroyed()); slider.reset(); // Destroying the slider would have destroyed |window| too. So |window| should // not need to be destroyed here. EXPECT_EQ(child_windows, root_window()->children().size()); } // Tests that the owner window can be destroyed from |OnWindowSlideComplete()| // delegate callback without causing a crash. TEST_F(WindowSliderTest, OwnerIsDestroyedOnSlideComplete) { size_t child_windows = root_window()->children().size(); aura::Window* window = CreateNormalWindow(0, root_window(), NULL); window->SetBounds(gfx::Rect(0, 0, 400, 400)); EXPECT_EQ(child_windows + 1, root_window()->children().size()); WindowSliderDeleteOwnerOnComplete slider_delegate(window); ui::test::EventGenerator generator(root_window()); // Generate a horizontal overscroll. new WindowSlider(&slider_delegate, root_window(), window); generator.GestureScrollSequence(gfx::Point(10, 10), gfx::Point(180, 10), base::TimeDelta::FromMilliseconds(10), 10); EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_TRUE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_TRUE(slider_delegate.slider_destroyed()); // Destroying the slider would have destroyed |window| too. So |window| should // not need to be destroyed here. EXPECT_EQ(child_windows, root_window()->children().size()); } // Test the scenario when two swipe gesture occur quickly one after another so // that the second swipe occurs while the transition animation triggered by the // first swipe is in progress. // The second swipe is supposed to instantly complete the animation caused by // the first swipe, ask the delegate to create a new layer, and animate it. TEST_F(WindowSliderTest, SwipeDuringSwipeAnimation) { scoped_ptr window(CreateNormalWindow(0, root_window(), NULL)); window->SetBounds(gfx::Rect(0, 0, 400, 400)); WindowSliderDelegateTest slider_delegate; new WindowSlider(&slider_delegate, root_window(), window.get()); // This test uses explicit durations so needs a normal duration. ui::ScopedAnimationDurationScaleMode normal_duration( ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); ui::LayerAnimator* animator = window->layer()->GetAnimator(); animator->set_disable_timer_for_test(true); ui::LayerAnimatorTestController test_controller(animator); ui::test::EventGenerator generator(root_window()); // Swipe forward so that |window|'s layer is the one animating. generator.GestureScrollSequence( gfx::Point(10, 10), gfx::Point(180, 10), base::TimeDelta::FromMilliseconds(10), 2); EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_FALSE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.slider_destroyed()); ui::ScopedLayerAnimationSettings settings(animator); base::TimeDelta duration = settings.GetTransitionDuration(); test_controller.StartThreadedAnimationsIfNeeded(); base::TimeTicks start_time1 = gfx::FrameTime::Now(); animator->Step(start_time1 + duration / 2); EXPECT_FALSE(slider_delegate.slide_completed()); slider_delegate.Reset(); // Generate another horizontal swipe while the animation from the previous // swipe is in progress. generator.GestureScrollSequence( gfx::Point(10, 10), gfx::Point(180, 10), base::TimeDelta::FromMilliseconds(10), 2); // Performing the second swipe should instantly complete the slide started // by the first swipe and create a new layer. EXPECT_TRUE(slider_delegate.created_back_layer()); EXPECT_FALSE(slider_delegate.slide_aborted()); EXPECT_FALSE(slider_delegate.created_front_layer()); EXPECT_TRUE(slider_delegate.slide_completing()); EXPECT_TRUE(slider_delegate.slide_completed()); EXPECT_FALSE(slider_delegate.slider_destroyed()); test_controller.StartThreadedAnimationsIfNeeded(); base::TimeTicks start_time2 = gfx::FrameTime::Now(); slider_delegate.Reset(); animator->Step(start_time2 + duration); // The animation for the second slide should now be completed. EXPECT_TRUE(slider_delegate.slide_completed()); slider_delegate.Reset(); window.reset(); EXPECT_TRUE(slider_delegate.slider_destroyed()); } } // namespace content