chromium/ash/wm/overview/overview_test_base.cc

// Copyright 2021 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/overview_test_base.h"

#include <tuple>

#include "ash/public/cpp/test/test_saved_desk_delegate.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/style/close_button.h"
#include "ash/style/rounded_label_widget.h"
#include "ash/style/system_shadow.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_drop_target.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_item_base.h"
#include "ash/wm/overview/overview_item_view.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/scoped_overview_transform_window.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_mini_view_header_view.h"
#include "ash/wm/window_preview_view.h"
#include "ash/wm/window_util.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/presentation_time_recorder.h"
#include "ui/compositor/test/test_utils.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/wm/core/coordinate_conversion.h"

namespace ash {

OverviewTestBase::~OverviewTestBase() = default;

void OverviewTestBase::EnterTabletMode() {
  // To avoid flaky failures due to mouse devices blocking entering tablet mode,
  // we detach all mouse devices.
  TabletModeControllerTestApi test_api;
  test_api.DetachAllMice();
  test_api.EnterTabletMode();
  base::RunLoop().RunUntilIdle();
}

void OverviewTestBase::LeaveTabletMode() {
  TabletModeControllerTestApi().LeaveTabletMode();
}

bool OverviewTestBase::InOverviewSession() const {
  return OverviewController::Get()->InOverviewSession();
}

bool OverviewTestBase::WindowsOverlapping(aura::Window* window1,
                                          aura::Window* window2) const {
  const gfx::Rect window1_bounds = GetTransformedTargetBounds(window1);
  const gfx::Rect window2_bounds = GetTransformedTargetBounds(window2);
  return window1_bounds.Intersects(window2_bounds);
}

std::unique_ptr<aura::Window> OverviewTestBase::CreateUnsnappableWindow(
    const gfx::Rect& bounds) {
  std::unique_ptr<aura::Window> window = CreateTestWindow(bounds);
  window->SetProperty(aura::client::kResizeBehaviorKey,
                      aura::client::kResizeBehaviorNone);
  return window;
}

void OverviewTestBase::ClickWindow(aura::Window* window) {
  ui::test::EventGenerator event_generator(window->GetRootWindow(), window);
  event_generator.ClickLeftButton();
}

OverviewController* OverviewTestBase::GetOverviewController() {
  return OverviewController::Get();
}

OverviewSession* OverviewTestBase::GetOverviewSession() {
  return GetOverviewController()->overview_session();
}

SplitViewController* OverviewTestBase::GetSplitViewController() {
  return SplitViewController::Get(Shell::GetPrimaryRootWindow());
}

gfx::Rect OverviewTestBase::GetTransformedBounds(aura::Window* window) const {
  gfx::RectF bounds(window->layer()->bounds());
  wm::TranslateRectToScreen(window->parent(), &bounds);
  const gfx::Transform transform =
      gfx::TransformAboutPivot(bounds.origin(), window->layer()->transform());
  return ToStableSizeRoundedRect(transform.MapRect(bounds));
}

gfx::Rect OverviewTestBase::GetTransformedTargetBounds(
    aura::Window* window) const {
  gfx::RectF bounds(window->layer()->GetTargetBounds());
  wm::TranslateRectToScreen(window->parent(), &bounds);
  const gfx::Transform transform = gfx::TransformAboutPivot(
      bounds.origin(), window->layer()->GetTargetTransform());
  return ToStableSizeRoundedRect(transform.MapRect(bounds));
}

gfx::Rect OverviewTestBase::GetTransformedBoundsInRootWindow(
    aura::Window* window) const {
  aura::Window* root = window->GetRootWindow();
  CHECK(window->layer());
  CHECK(root->layer());
  gfx::Transform transform;
  if (!window->layer()->GetTargetTransformRelativeTo(root->layer(),
                                                     &transform)) {
    return gfx::Rect();
  }
  return transform.MapRect(gfx::Rect(window->bounds().size()));
}

const OverviewItemBase* OverviewTestBase::GetDropTarget(int grid_index) const {
  return OverviewController::Get()
      ->overview_session()
      ->grid_list_[grid_index]
      ->drop_target();
}

CloseButton* OverviewTestBase::GetCloseButton(OverviewItemBase* item) {
  return item->GetLeafItemForWindow(item->GetWindow())
      ->overview_item_view_->close_button();
}

views::Label* OverviewTestBase::GetLabelView(OverviewItemBase* item) {
  return item->GetLeafItemForWindow(item->GetWindow())
      ->overview_item_view_->header_view()
      ->title_label();
}

views::View* OverviewTestBase::GetBackdropView(OverviewItemBase* item) {
  return item->GetBackDropView();
}

WindowPreviewView* OverviewTestBase::GetPreviewView(OverviewItemBase* item) {
  return item->GetLeafItemForWindow(item->GetWindow())
      ->overview_item_view_->preview_view();
}

gfx::Rect OverviewTestBase::GetShadowBounds(
    const OverviewItemBase* item) const {
  SystemShadow* shadow = item->shadow_.get();
  if (!shadow || !shadow->GetLayer()->visible()) {
    return gfx::Rect();
  }

  return shadow->GetContentBounds();
}

views::Widget* OverviewTestBase::GetCannotSnapWidget(OverviewItemBase* item) {
  return item->cannot_snap_widget_.get();
}

void OverviewTestBase::SetAnimatingToClose(OverviewItemBase* item, bool val) {
  item->animating_to_close_ = val;
}

float OverviewTestBase::GetCloseButtonOpacity(OverviewItemBase* item) {
  return GetCloseButton(item)->layer()->opacity();
}

float OverviewTestBase::GetTitlebarOpacity(OverviewItemBase* item) {
  return item->GetLeafItemForWindow(item->GetWindow())
      ->overview_item_view_->header_view()
      ->layer()
      ->opacity();
}

bool OverviewTestBase::HasRoundedCorner(OverviewItemBase* item) {
  aura::Window* window = item->GetWindow();
  const ui::Layer* layer = window_util::IsMinimizedOrTucked(window)
                               ? GetPreviewView(item)->layer()
                               : window->layer();
  return !layer->rounded_corner_radii().IsEmpty();
}

void OverviewTestBase::CheckWindowAndCloseButtonInScreen(
    aura::Window* window,
    OverviewItemBase* window_item) {
  const gfx::Rect screen_bounds =
      window_item->root_window()->GetBoundsInScreen();
  EXPECT_TRUE(window_item->Contains(window));
  EXPECT_TRUE(screen_bounds.Contains(GetTransformedTargetBounds(window)));
  EXPECT_TRUE(
      screen_bounds.Contains(GetCloseButton(window_item)->GetBoundsInScreen()));
}

void OverviewTestBase::SetUp() {
  AshTestBase::SetUp();

  aura::Env::GetInstance()->set_throttle_input_on_resize_for_testing(false);
  shelf_view_test_api_ = std::make_unique<ShelfViewTestAPI>(
      GetPrimaryShelf()->GetShelfViewForTesting());
  shelf_view_test_api_->SetAnimationDuration(base::Milliseconds(1));
  ScopedOverviewTransformWindow::SetImmediateCloseForTests(
      /*immediate=*/true);
  ui::PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest(
      true);
}

void OverviewTestBase::TearDown() {
  ui::PresentationTimeRecorder::SetReportPresentationTimeImmediatelyForTest(
      false);
  trace_names_.clear();
  AshTestBase::TearDown();
}

void OverviewTestBase::CheckForDuplicateTraceName(const std::string& trace) {
  DCHECK(!base::Contains(trace_names_, trace)) << trace;
  trace_names_.push_back(trace);
}

void OverviewTestBase::CheckA11yOverrides(const std::string& trace,
                                          views::Widget* widget,
                                          views::Widget* expected_previous,
                                          views::Widget* expected_next) {
  SCOPED_TRACE(trace);
  views::View* contents_view = widget->GetContentsView();
  views::ViewAccessibility& view_accessibility =
      contents_view->GetViewAccessibility();
  EXPECT_EQ(expected_previous, view_accessibility.GetPreviousWindowFocus());
  EXPECT_EQ(expected_next, view_accessibility.GetNextWindowFocus());
}

void OverviewTestBase::CheckOverviewEnterExitHistogram(
    const std::string& trace,
    const std::vector<int>& enter_counts,
    const std::vector<int>& exit_counts) {
  CheckForDuplicateTraceName(trace);

  // Force frames and wait for all throughput trackers to be gone to allow
  // animation throughput data to be passed from cc to ui.
  ui::Compositor* compositor =
      Shell::GetPrimaryRootWindow()->layer()->GetCompositor();
  while (compositor->has_throughput_trackers_for_testing()) {
    compositor->ScheduleFullRedraw();
    std::ignore =
        ui::WaitForNextFrameToBePresented(compositor, base::Milliseconds(500));
  }

  {
    SCOPED_TRACE(trace + ".Enter");
    CheckOverviewHistogram("Ash.Overview.AnimationSmoothness.Enter",
                           enter_counts);
  }
  {
    SCOPED_TRACE(trace + ".Exit");
    CheckOverviewHistogram("Ash.Overview.AnimationSmoothness.Exit",
                           exit_counts);
  }
}

gfx::Rect OverviewTestBase::GetGridBounds() {
  if (GetOverviewSession())
    return GetOverviewSession()->grid_list_[0]->bounds_;

  return gfx::Rect();
}

void OverviewTestBase::SetGridBounds(OverviewGrid* grid,
                                     const gfx::Rect& bounds) {
  grid->bounds_ = bounds;
}

void OverviewTestBase::CheckOverviewHistogram(const std::string& histogram,
                                              const std::vector<int>& counts) {
  ASSERT_EQ(5u, counts.size());

  histograms_.ExpectTotalCount(histogram + ".ClamshellMode", counts[0]);
  histograms_.ExpectTotalCount(histogram + ".SingleClamshellMode", counts[1]);
  histograms_.ExpectTotalCount(histogram + ".TabletMode", counts[2]);
  histograms_.ExpectTotalCount(histogram + ".MinimizedTabletMode", counts[3]);
  histograms_.ExpectTotalCount(histogram + ".SplitView", counts[4]);
}
}  // namespace ash