diff options
author | mfomitchev@chromium.org <mfomitchev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-27 02:43:17 +0000 |
---|---|---|
committer | mfomitchev@chromium.org <mfomitchev@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-27 02:43:17 +0000 |
commit | cac326b5df02531fd2f37c83a2f0872166f12195 (patch) | |
tree | fb3cc0cfe06595b314b9a06730cb2d480835ee3c /content/browser/web_contents/aura | |
parent | acf00a782c6f413382c758cb6b835c3286388971 (diff) | |
download | chromium_src-cac326b5df02531fd2f37c83a2f0872166f12195.zip chromium_src-cac326b5df02531fd2f37c83a2f0872166f12195.tar.gz chromium_src-cac326b5df02531fd2f37c83a2f0872166f12195.tar.bz2 |
Fixing race conditions in ui::content::WindowSlider which could cause the overscroll overlay to never go away.
The race condition occurred because new naviagtion could happen before the callback from the animation associated with the old navigation was executed. This could get the OverscrollNavigationOverlay into a bad state.
The fix is to instantly complete the animation associated with a previous user action when new interaction occurs. Also, we now perform page navigation at the beginning of the transition animation, not in the end, which saves us 200ms in load time.
BUG=348717, 305448
Review URL: https://codereview.chromium.org/202183003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@259766 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser/web_contents/aura')
6 files changed, 412 insertions, 130 deletions
diff --git a/content/browser/web_contents/aura/overscroll_navigation_overlay.cc b/content/browser/web_contents/aura/overscroll_navigation_overlay.cc index 8c47a7b..ac7b20c 100644 --- a/content/browser/web_contents/aura/overscroll_navigation_overlay.cc +++ b/content/browser/web_contents/aura/overscroll_navigation_overlay.cc @@ -9,6 +9,7 @@ #include "content/browser/web_contents/aura/image_window_delegate.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/common/view_messages.h" +#include "content/public/browser/render_widget_host_view.h" #include "ui/aura/window.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_animation_observer.h" @@ -236,29 +237,18 @@ ui::Layer* OverscrollNavigationOverlay::CreateFrontLayer() { return CreateSlideLayer(1); } -void OverscrollNavigationOverlay::OnWindowSlideComplete() { - if (slide_direction_ == SLIDE_UNKNOWN) { - window_slider_.reset(); - StopObservingIfDone(); +void OverscrollNavigationOverlay::OnWindowSlideCompleting() { + if (slide_direction_ == SLIDE_UNKNOWN) return; - } - - // Change the image used for the overlay window. - image_delegate_->SetImage(layer_delegate_->image()); - window_->layer()->SetTransform(gfx::Transform()); - window_->SchedulePaintInRect(gfx::Rect(window_->bounds().size())); - - SlideDirection direction = slide_direction_; - slide_direction_ = SLIDE_UNKNOWN; // Reset state and wait for the new navigation page to complete // loading/painting. StartObserving(); // Perform the navigation. - if (direction == SLIDE_BACK) + if (slide_direction_ == SLIDE_BACK) web_contents_->GetController().GoBack(); - else if (direction == SLIDE_FRONT) + else if (slide_direction_ == SLIDE_FRONT) web_contents_->GetController().GoForward(); else NOTREACHED(); @@ -271,6 +261,34 @@ void OverscrollNavigationOverlay::OnWindowSlideComplete() { pending_entry_id_ = pending_entry ? pending_entry->GetUniqueID() : 0; } +void OverscrollNavigationOverlay::OnWindowSlideCompleted() { + if (slide_direction_ == SLIDE_UNKNOWN) { + window_slider_.reset(); + StopObservingIfDone(); + return; + } + + // Change the image used for the overlay window. + image_delegate_->SetImage(layer_delegate_->image()); + window_->layer()->SetTransform(gfx::Transform()); + window_->SchedulePaintInRect(gfx::Rect(window_->bounds().size())); + slide_direction_ = SLIDE_UNKNOWN; + + // Make sure the overlay layer is repainted before we dismiss it, otherwise + // OverlayDismissAnimator may end up showing the wrong screenshot during the + // fadeout animation. + if (received_paint_update_ && need_paint_update_) { + received_paint_update_ = false; + RenderWidgetHost* host = + web_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost(); + RenderViewHostImpl* view_host = + static_cast<RenderViewHostImpl*> (RenderViewHost::From(host)); + view_host->ScheduleComposite(); + } else if (!need_paint_update_) { + StopObservingIfDone(); + } +} + void OverscrollNavigationOverlay::OnWindowSlideAborted() { StopObservingIfDone(); } diff --git a/content/browser/web_contents/aura/overscroll_navigation_overlay.h b/content/browser/web_contents/aura/overscroll_navigation_overlay.h index 68a60a2..32b8819 100644 --- a/content/browser/web_contents/aura/overscroll_navigation_overlay.h +++ b/content/browser/web_contents/aura/overscroll_navigation_overlay.h @@ -83,7 +83,8 @@ class CONTENT_EXPORT OverscrollNavigationOverlay // Overridden from WindowSlider::Delegate: virtual ui::Layer* CreateBackLayer() OVERRIDE; virtual ui::Layer* CreateFrontLayer() OVERRIDE; - virtual void OnWindowSlideComplete() OVERRIDE; + virtual void OnWindowSlideCompleting() OVERRIDE; + virtual void OnWindowSlideCompleted() OVERRIDE; virtual void OnWindowSlideAborted() OVERRIDE; virtual void OnWindowSliderDestroyed() OVERRIDE; diff --git a/content/browser/web_contents/aura/overscroll_navigation_overlay_unittest.cc b/content/browser/web_contents/aura/overscroll_navigation_overlay_unittest.cc index f19d61e..463c795 100644 --- a/content/browser/web_contents/aura/overscroll_navigation_overlay_unittest.cc +++ b/content/browser/web_contents/aura/overscroll_navigation_overlay_unittest.cc @@ -53,7 +53,8 @@ class OverscrollNavigationOverlayTest : public RenderViewHostImplTestHarness { delete GetOverlay()->CreateBackLayer(); // Performs BACK navigation, sets image from layer_delegate_ on // image_delegate_. - GetOverlay()->OnWindowSlideComplete(); + GetOverlay()->OnWindowSlideCompleting(); + GetOverlay()->OnWindowSlideCompleted(); } protected: diff --git a/content/browser/web_contents/aura/window_slider.cc b/content/browser/web_contents/aura/window_slider.cc index 6646500..61146e3 100644 --- a/content/browser/web_contents/aura/window_slider.cc +++ b/content/browser/web_contents/aura/window_slider.cc @@ -19,12 +19,6 @@ namespace content { namespace { -void DeleteLayerAndShadow(ui::Layer* layer, - ShadowLayerDelegate* shadow) { - delete shadow; - delete layer; -} - // An animation observer that runs a callback at the end of the animation, and // destroys itself. class CallbackAnimationObserver : public ui::ImplicitAnimationObserver { @@ -56,6 +50,7 @@ WindowSlider::WindowSlider(Delegate* delegate, : delegate_(delegate), event_window_(event_window), owner_(owner), + active_animator_(NULL), delta_x_(0.f), weak_factory_(this), active_start_threshold_(0.f), @@ -93,9 +88,7 @@ void WindowSlider::ChangeOwner(aura::Window* new_owner) { bool WindowSlider::IsSlideInProgress() const { // if active_start_threshold_ is 0, it means that sliding hasn't been started - return active_start_threshold_ != 0 && - (fabs(delta_x_) >= active_start_threshold_ || slider_.get() || - weak_factory_.HasWeakPtrs()); + return active_start_threshold_ != 0 && (slider_.get() || active_animator_); } void WindowSlider::SetupSliderLayer() { @@ -110,6 +103,16 @@ void WindowSlider::SetupSliderLayer() { } void WindowSlider::UpdateForScroll(float x_offset, float y_offset) { + if (active_animator_) { + // If there is an active animation, complete it before processing the scroll + // so that the callbacks that are invoked on the Delegate are consistent. + // Completing the animation may destroy WindowSlider through the animation + // callback, so we can't continue processing the scroll event here. + delta_x_ += x_offset; + CompleteActiveAnimations(); + return; + } + float old_delta = delta_x_; delta_x_ += x_offset; if (fabs(delta_x_) < active_start_threshold_ && !slider_.get()) @@ -153,100 +156,96 @@ void WindowSlider::UpdateForScroll(float x_offset, float y_offset) { translate_layer->SetTransform(transform); } -void WindowSlider::UpdateForFling(float x_velocity, float y_velocity) { +void WindowSlider::CompleteOrResetSlide() { if (!slider_.get()) return; int width = owner_->bounds().width(); float ratio = (fabs(delta_x_) - active_start_threshold_) / width; if (ratio < complete_threshold_) { - ResetScroll(); + ResetSlide(); return; } ui::Layer* sliding = delta_x_ < 0 ? slider_.get() : owner_->layer(); - ui::ScopedLayerAnimationSettings settings(sliding->GetAnimator()); + active_animator_ = sliding->GetAnimator(); + + ui::ScopedLayerAnimationSettings settings(active_animator_); settings.SetPreemptionStrategy( ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); settings.SetTweenType(gfx::Tween::EASE_OUT); settings.AddObserver(new CallbackAnimationObserver( - base::Bind(&WindowSlider::CompleteWindowSlideAfterAnimation, - weak_factory_.GetWeakPtr()))); + base::Bind(&WindowSlider::SlideAnimationCompleted, + weak_factory_.GetWeakPtr(), + base::Passed(&slider_), + base::Passed(&shadow_)))); gfx::Transform transform; transform.Translate(delta_x_ < 0 ? 0 : width, 0); + delta_x_ = 0; + delegate_->OnWindowSlideCompleting(); sliding->SetTransform(transform); } -void WindowSlider::ResetScroll() { +void WindowSlider::CompleteActiveAnimations() { + if (active_animator_) + active_animator_->StopAnimating(); +} + +void WindowSlider::ResetSlide() { if (!slider_.get()) return; - // Do not trigger any callbacks if this animation replaces any in-progress - // animation. - weak_factory_.InvalidateWeakPtrs(); - // Reset the state of the sliding layer. if (slider_.get()) { - ui::Layer* layer = slider_.release(); - ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); - settings.SetPreemptionStrategy( - ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); - settings.SetTweenType(gfx::Tween::EASE_OUT); - - // Delete the layer and the shadow at the end of the animation. - settings.AddObserver(new CallbackAnimationObserver( - base::Bind(&DeleteLayerAndShadow, - base::Unretained(layer), - base::Unretained(shadow_.release())))); - + ui::Layer* translate_layer; gfx::Transform transform; - transform.Translate(delta_x_ < 0 ? layer->bounds().width() : 0, 0); - layer->SetTransform(transform); - } - - // Reset the state of the main layer. - { - ui::ScopedLayerAnimationSettings settings(owner_->layer()->GetAnimator()); + if (delta_x_ < 0) { + translate_layer = slider_.get(); + transform.Translate(translate_layer->bounds().width(), 0); + } else { + translate_layer = owner_->layer(); + } + + active_animator_ = translate_layer->GetAnimator(); + ui::ScopedLayerAnimationSettings settings(active_animator_); settings.SetPreemptionStrategy( ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); settings.SetTweenType(gfx::Tween::EASE_OUT); settings.AddObserver(new CallbackAnimationObserver( - base::Bind(&WindowSlider::AbortWindowSlideAfterAnimation, - weak_factory_.GetWeakPtr()))); - owner_->layer()->SetTransform(gfx::Transform()); - owner_->layer()->SetLayerBrightness(0.f); + base::Bind(&WindowSlider::ResetSlideAnimationCompleted, + weak_factory_.GetWeakPtr(), + base::Passed(&slider_), + base::Passed(&shadow_)))); + translate_layer->SetTransform(transform); } delta_x_ = 0.f; } -void WindowSlider::CancelScroll() { - ResetScroll(); +void WindowSlider::SlideAnimationCompleted( + scoped_ptr<ui::Layer> layer, scoped_ptr<ShadowLayerDelegate> shadow) { + active_animator_ = NULL; + shadow.reset(); + layer.reset(); + delegate_->OnWindowSlideCompleted(); } -void WindowSlider::CompleteWindowSlideAfterAnimation() { - weak_factory_.InvalidateWeakPtrs(); - shadow_.reset(); - slider_.reset(); - delta_x_ = 0.f; - - delegate_->OnWindowSlideComplete(); -} - -void WindowSlider::AbortWindowSlideAfterAnimation() { - weak_factory_.InvalidateWeakPtrs(); - +void WindowSlider::ResetSlideAnimationCompleted( + scoped_ptr<ui::Layer> layer, scoped_ptr<ShadowLayerDelegate> shadow) { + active_animator_ = NULL; + shadow.reset(); + layer.reset(); delegate_->OnWindowSlideAborted(); } void WindowSlider::OnKeyEvent(ui::KeyEvent* event) { - CancelScroll(); + ResetSlide(); } void WindowSlider::OnMouseEvent(ui::MouseEvent* event) { if (!(event->flags() & ui::EF_IS_SYNTHESIZED)) - CancelScroll(); + ResetSlide(); } void WindowSlider::OnScrollEvent(ui::ScrollEvent* event) { @@ -254,9 +253,9 @@ void WindowSlider::OnScrollEvent(ui::ScrollEvent* event) { if (event->type() == ui::ET_SCROLL) UpdateForScroll(event->x_offset_ordinal(), event->y_offset_ordinal()); else if (event->type() == ui::ET_SCROLL_FLING_START) - UpdateForFling(event->x_offset_ordinal(), event->y_offset_ordinal()); + CompleteOrResetSlide(); else - CancelScroll(); + ResetSlide(); event->SetHandled(); } @@ -265,7 +264,7 @@ void WindowSlider::OnGestureEvent(ui::GestureEvent* event) { const ui::GestureEventDetails& details = event->details(); switch (event->type()) { case ui::ET_GESTURE_SCROLL_BEGIN: - ResetScroll(); + CompleteActiveAnimations(); break; case ui::ET_GESTURE_SCROLL_UPDATE: @@ -273,17 +272,17 @@ void WindowSlider::OnGestureEvent(ui::GestureEvent* event) { break; case ui::ET_GESTURE_SCROLL_END: - UpdateForFling(0.f, 0.f); + CompleteOrResetSlide(); break; case ui::ET_SCROLL_FLING_START: - UpdateForFling(details.velocity_x(), details.velocity_y()); + CompleteOrResetSlide(); break; case ui::ET_GESTURE_PINCH_BEGIN: case ui::ET_GESTURE_PINCH_UPDATE: case ui::ET_GESTURE_PINCH_END: - CancelScroll(); + ResetSlide(); break; default: diff --git a/content/browser/web_contents/aura/window_slider.h b/content/browser/web_contents/aura/window_slider.h index af97afa..6445883 100644 --- a/content/browser/web_contents/aura/window_slider.h +++ b/content/browser/web_contents/aura/window_slider.h @@ -10,6 +10,7 @@ #include "base/memory/weak_ptr.h" #include "content/common/content_export.h" #include "ui/aura/window_observer.h" +#include "ui/compositor/layer_animator.h" #include "ui/events/event_handler.h" namespace ui { @@ -28,25 +29,33 @@ class CONTENT_EXPORT WindowSlider : public ui::EventHandler, public: virtual ~Delegate() {} - // Creates a layer to show in the background, as the window-layer slides - // with the scroll gesture. + // Creates a layer to show behind the window-layer. Called when the + // window-layer starts sliding out to reveal the layer underneath. // The WindowSlider takes ownership of the created layer. virtual ui::Layer* CreateBackLayer() = 0; - // Creates a layer to slide on top of the window-layer with the scroll - // gesture. + // Creates a layer to show on top of the window-layer. Called when the new + // layer needs to start sliding in on top of the window-layer. // The WindowSlider takes ownership of the created layer. virtual ui::Layer* CreateFrontLayer() = 0; - // Called when the slide is complete. Note that at the end of a completed - // slide, the window-layer may have been transformed. The callback here - // should reset the transform if necessary. - virtual void OnWindowSlideComplete() = 0; - // Called when the slide is aborted. Note that when the slide is aborted, // the WindowSlider resets any transform it applied on the window-layer. virtual void OnWindowSlideAborted() = 0; + // Called when the slide is about to be complete. The delegate can take + // action with the assumption that slide will complete soon (within the + // duration of the final transition animation effect). + // This callback is always preceeded by CreateBackLayerAndSetAsTarget() or + // by CreateFrontLayerAndSetAsTarget() callback, and is guaranteed to be + // followed by the OnWindowSlideCompleted() callback. + virtual void OnWindowSlideCompleting() = 0; + + // Called when the window slide completes. Note that at the end the + // window-layer may have been transformed. The callback here should reset + // the transform if necessary. + virtual void OnWindowSlideCompleted() = 0; + // Called when the slider is destroyed. virtual void OnWindowSliderDestroyed() = 0; }; @@ -74,18 +83,25 @@ class CONTENT_EXPORT WindowSlider : public ui::EventHandler, void UpdateForScroll(float x_offset, float y_offset); - void UpdateForFling(float x_velocity, float y_velocity); + // Completes or resets the slide depending on whether the sliding layer + // passed the "complete slide threshold". + void CompleteOrResetSlide(); - // Resets any in-progress slide. - void ResetScroll(); + // Stops all slider-owned animations, progressing them to their end-points. + // Note that depending on the sate of the Delegate and the WindowSlider, this + // may destroy the WindowSlider through animation callbacks. + void CompleteActiveAnimations(); - // Cancels any scroll/animation in progress. - void CancelScroll(); + // Resets in-progress slide if any, and starts the animation of the slidden + // window to its original position. + void ResetSlide(); // The following callbacks are triggered after an animation. - void CompleteWindowSlideAfterAnimation(); + void SlideAnimationCompleted(scoped_ptr<ui::Layer> layer, + scoped_ptr<ShadowLayerDelegate> shadow); - void AbortWindowSlideAfterAnimation(); + void ResetSlideAnimationCompleted(scoped_ptr<ui::Layer> layer, + scoped_ptr<ShadowLayerDelegate> shadow); // Overridden from ui::EventHandler: virtual void OnKeyEvent(ui::KeyEvent* event) OVERRIDE; @@ -110,6 +126,10 @@ class CONTENT_EXPORT WindowSlider : public ui::EventHandler, // destroy |owner_|. aura::Window* owner_; + // Set to the Animator of the currently active animation. If no animation is + // active, this is set to NULL. + ui::LayerAnimator* active_animator_; + // The accumulated amount of horizontal scroll. float delta_x_; diff --git a/content/browser/web_contents/aura/window_slider_unittest.cc b/content/browser/web_contents/aura/window_slider_unittest.cc index ed41347..21a8d17 100644 --- a/content/browser/web_contents/aura/window_slider_unittest.cc +++ b/content/browser/web_contents/aura/window_slider_unittest.cc @@ -5,13 +5,19 @@ #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/event_generator.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/gfx/frame_time.h" namespace content { @@ -39,6 +45,28 @@ void ChangeSliderOwnerDuringScrollCallback(scoped_ptr<aura::Window>* 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: @@ -59,16 +87,21 @@ class WindowSliderDelegateTest : public WindowSlider::Delegate { : 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() {} + 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; @@ -80,6 +113,7 @@ class WindowSliderDelegateTest : public WindowSlider::Delegate { 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_; } @@ -107,10 +141,14 @@ class WindowSliderDelegateTest : public WindowSlider::Delegate { return CreateLayerForTest(); } - virtual void OnWindowSlideComplete() OVERRIDE { + virtual void OnWindowSlideCompleted() OVERRIDE { slide_completed_ = true; } + virtual void OnWindowSlideCompleting() OVERRIDE { + slide_completing_ = true; + } + virtual void OnWindowSlideAborted() OVERRIDE { slide_aborted_ = true; } @@ -123,6 +161,7 @@ class WindowSliderDelegateTest : public WindowSlider::Delegate { bool can_create_layer_; bool created_back_layer_; bool created_front_layer_; + bool slide_completing_; bool slide_completed_; bool slide_aborted_; bool slider_destroyed_; @@ -159,8 +198,8 @@ class WindowSliderDeleteOwnerOnComplete : public WindowSliderDelegateTest { private: // Overridden from WindowSlider::Delegate: - virtual void OnWindowSlideComplete() OVERRIDE { - WindowSliderDelegateTest::OnWindowSlideComplete(); + virtual void OnWindowSlideCompleted() OVERRIDE { + WindowSliderDelegateTest::OnWindowSlideCompleted(); delete owner_; } @@ -180,11 +219,14 @@ TEST_F(WindowSliderTest, WindowSlideUsingGesture) { // Generate a horizontal overscroll. WindowSlider* slider = new WindowSlider(&slider_delegate, root_window(), window.get()); - generator.GestureScrollSequence(gfx::Point(10, 10), - gfx::Point(180, 10), - base::TimeDelta::FromMilliseconds(10), - 10); + 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()); @@ -193,12 +235,15 @@ TEST_F(WindowSliderTest, WindowSlideUsingGesture) { slider_delegate.Reset(); window->SetTransform(gfx::Transform()); - // Generat a horizontal overscroll in the reverse direction. - generator.GestureScrollSequence(gfx::Point(180, 10), - gfx::Point(10, 10), - base::TimeDelta::FromMilliseconds(10), - 10); + // 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()); @@ -207,11 +252,14 @@ TEST_F(WindowSliderTest, WindowSlideUsingGesture) { slider_delegate.Reset(); // Generate a vertical overscroll. - generator.GestureScrollSequence(gfx::Point(10, 10), - gfx::Point(10, 80), - base::TimeDelta::FromMilliseconds(10), - 10); + 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()); @@ -220,13 +268,16 @@ TEST_F(WindowSliderTest, WindowSlideUsingGesture) { // Generate a horizontal scroll that starts overscroll, but doesn't scroll // enough to complete it. - generator.GestureScrollSequence(gfx::Point(10, 10), - gfx::Point(80, 10), - base::TimeDelta::FromMilliseconds(10), - 10); + 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()); @@ -237,10 +288,11 @@ TEST_F(WindowSliderTest, WindowSlideUsingGesture) { EXPECT_TRUE(slider_delegate.slider_destroyed()); } -// Tests that the window slide is cancelled when a different type of event +// Tests that the window slide is interrupted when a different type of event // happens. TEST_F(WindowSliderTest, WindowSlideIsCancelledOnEvent) { scoped_ptr<aura::Window> window(CreateNormalWindow(0, root_window(), NULL)); + window->SetBounds(gfx::Rect(0, 0, 400, 400)); WindowSliderDelegateTest slider_delegate; ui::Event* events[] = { @@ -270,6 +322,7 @@ TEST_F(WindowSliderTest, WindowSlideIsCancelledOnEvent) { 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(); @@ -278,6 +331,112 @@ TEST_F(WindowSliderTest, WindowSlideIsCancelledOnEvent) { 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<aura::Window> 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(); + gfx::AnimationContainerElement* element = animator; + 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); + + aura::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(); + element->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) { @@ -308,28 +467,37 @@ TEST_F(WindowSliderTest, OwnerWindowChangesDuringWindowSlide) { 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<aura::Window> 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()); aura::test::EventGenerator generator(root_window()); - // Generate a horizontal overscroll. - scoped_ptr<WindowSlider> slider( - new WindowSlider(&slider_delegate, root_window(), window.get())); - generator.GestureScrollSequence(gfx::Point(10, 10), - gfx::Point(160, 10), - base::TimeDelta::FromMilliseconds(10), - 10); + // 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()); @@ -337,15 +505,21 @@ TEST_F(WindowSliderTest, NoSlideWhenLayerCantBeCreated) { window->SetTransform(gfx::Transform()); slider_delegate.SetCanCreateLayer(true); - generator.GestureScrollSequence(gfx::Point(10, 10), - gfx::Point(160, 10), - base::TimeDelta::FromMilliseconds(10), - 10); + 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()| @@ -367,6 +541,7 @@ TEST_F(WindowSliderTest, OwnerIsDestroyedOnSliderDestroy) { 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()); @@ -396,6 +571,7 @@ TEST_F(WindowSliderTest, OwnerIsDestroyedOnSlideComplete) { 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()); @@ -406,4 +582,71 @@ TEST_F(WindowSliderTest, OwnerIsDestroyedOnSlideComplete) { 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<aura::Window> 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()); + + ui::ScopedAnimationDurationScaleMode normal_duration_( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + ui::LayerAnimator* animator = window->layer()->GetAnimator(); + gfx::AnimationContainerElement* element = animator; + animator->set_disable_timer_for_test(true); + ui::LayerAnimatorTestController test_controller(animator); + + aura::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(); + + element->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(); + element->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 |