1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174

ash / wm / overview / cleanup_animation_observer_unittest.cc [blame]

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/wm/overview/cleanup_animation_observer.h"

#include <utility>
#include <vector>

#include "ash/test/ash_test_base.h"
#include "ash/wm/overview/overview_delegate.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/memory/raw_ptr.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"

namespace ash {
namespace {

class TestOverviewDelegate : public OverviewDelegate {
 public:
  TestOverviewDelegate() = default;

  TestOverviewDelegate(const TestOverviewDelegate&) = delete;
  TestOverviewDelegate& operator=(const TestOverviewDelegate&) = delete;

  ~TestOverviewDelegate() override {
    // Destroy widgets that may be still animating if shell shuts down soon
    // after exiting overview mode.
    for (std::unique_ptr<DelayedAnimationObserver>& observer : observers_)
      observer->Shutdown();
  }

  // OverviewDelegate:
  void AddExitAnimationObserver(
      std::unique_ptr<DelayedAnimationObserver> animation_observer) override {
    animation_observer->SetOwner(this);
    observers_.push_back(std::move(animation_observer));
  }
  void RemoveAndDestroyExitAnimationObserver(
      DelayedAnimationObserver* animation_observer) override {
    std::erase_if(observers_, base::MatchesUniquePtr(animation_observer));
  }
  void AddEnterAnimationObserver(
      std::unique_ptr<DelayedAnimationObserver> animation_observer) override {}
  void RemoveAndDestroyEnterAnimationObserver(
      DelayedAnimationObserver* animation_observer) override {}

 private:
  std::vector<std::unique_ptr<DelayedAnimationObserver>> observers_;
};

class CleanupAnimationObserverTest : public AshTestBase,
                                     public views::WidgetObserver {
 public:
  CleanupAnimationObserverTest() = default;

  CleanupAnimationObserverTest(const CleanupAnimationObserverTest&) = delete;
  CleanupAnimationObserverTest& operator=(const CleanupAnimationObserverTest&) =
      delete;

  ~CleanupAnimationObserverTest() override {
    if (widget_)
      widget_->RemoveObserver(this);
  }

  // Creates a Widget containing a Window with the given |bounds|. This should
  // be used when the test requires a Widget. For example any test that will
  // cause a window to be closed via
  // views::Widget::GetWidgetForNativeView(window)->Close().
  std::unique_ptr<views::Widget> CreateWindowWidget(const gfx::Rect& bounds) {
    auto widget = std::make_unique<views::Widget>();
    views::Widget::InitParams params(
        views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
        views::Widget::InitParams::TYPE_WINDOW);
    params.bounds = bounds;
    params.context = GetContext();
    widget->Init(std::move(params));
    widget->Show();
    widget->AddObserver(this);
    widget_ = widget.get();
    return widget;
  }

 protected:
  bool widget_destroyed() { return !widget_; }

 private:
  void OnWidgetDestroyed(views::Widget* widget) override {
    if (widget_ == widget)
      widget_ = nullptr;
  }

  raw_ptr<views::Widget> widget_ = nullptr;
};

}  // namespace

// Tests that basic create-destroy sequence does not crash.
TEST_F(CleanupAnimationObserverTest, CreateDestroy) {
  TestOverviewDelegate delegate;
  std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
  auto observer = std::make_unique<CleanupAnimationObserver>(std::move(widget));
  delegate.AddExitAnimationObserver(std::move(observer));
}

// Tests that completing animation deletes the animation observer and the
// test widget and that deleting the OverviewDelegate instance which
// owns the observer does not crash.
TEST_F(CleanupAnimationObserverTest, CreateAnimateComplete) {
  TestOverviewDelegate delegate;
  std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
  aura::Window* widget_window = widget->GetNativeWindow();
  {
    ui::ScopedLayerAnimationSettings animation_settings(
        widget_window->layer()->GetAnimator());
    animation_settings.SetTransitionDuration(base::Milliseconds(1000));
    animation_settings.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);

    auto observer =
        std::make_unique<CleanupAnimationObserver>(std::move(widget));
    animation_settings.AddObserver(observer.get());
    delegate.AddExitAnimationObserver(std::move(observer));

    widget_window->SetBounds(gfx::Rect(50, 50, 60, 60));
  }
  // The widget should be destroyed when |animation_settings| gets out of scope
  // which in absence of NON_ZERO_DURATION animation duration mode completes
  // the animation and calls OnImplicitAnimationsCompleted() on the cleanup
  // observer and auto-deletes the owned widget.
  EXPECT_TRUE(widget_destroyed());
  // TestOverviewDelegate going out of scope should not crash.
}

// Tests that starting an animation and exiting doesn't crash. If not for
// TestOverviewDelegate calling Shutdown() on a CleanupAnimationObserver
// instance in destructor, this test would have crashed.
TEST_F(CleanupAnimationObserverTest, CreateAnimateShutdown) {
  TestOverviewDelegate delegate;
  std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
  aura::Window* widget_window = widget->GetNativeWindow();
  {
    // Normal animations for tests have ZERO_DURATION, make sure we are actually
    // animating the movement.
    ui::ScopedAnimationDurationScaleMode animation_scale_mode(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
    ui::ScopedLayerAnimationSettings animation_settings(
        widget_window->layer()->GetAnimator());
    animation_settings.SetTransitionDuration(base::Milliseconds(1000));
    animation_settings.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);

    auto observer =
        std::make_unique<CleanupAnimationObserver>(std::move(widget));
    animation_settings.AddObserver(observer.get());
    delegate.AddExitAnimationObserver(std::move(observer));

    widget_window->SetBounds(gfx::Rect(50, 50, 60, 60));
  }
  // The widget still exists.
  EXPECT_FALSE(widget_destroyed());
  // The test widget is auto-deleted when |delegate| that owns it goes out of
  // scope. The animation is still active when this happens which should not
  // crash.
}

}  // namespace ash