blob: e7a6e23084839e176bbe1c939dbe3815c8f0c1e5 [file] [log] [blame]
// 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 "chromeos/ui/frame/default_frame_header.h"
#include <memory>
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/desks/desks_util.h"
#include "base/i18n/rtl.h"
#include "base/test/icu_test_util.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/frame/caption_buttons/frame_back_button.h"
#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
#include "chromeos/ui/frame/frame_header.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/animation/animation_test_api.h"
#include "ui/gfx/color_utils.h"
#include "ui/views/test/test_views.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/non_client_view.h"
#include "ui/wm/core/window_util.h"
using chromeos::DefaultFrameHeader;
using chromeos::FrameBackButton;
using chromeos::FrameCaptionButtonContainerView;
using chromeos::FrameHeader;
using chromeos::kFrameActiveColorKey;
using chromeos::kFrameInactiveColorKey;
using views::NonClientFrameView;
using views::Widget;
namespace ash {
using DefaultFrameHeaderTest = AshTestBase;
// Ensure the title text is vertically aligned with the window icon.
TEST_F(DefaultFrameHeaderTest, TitleIconAlignment) {
std::unique_ptr<Widget> widget = CreateTestWidget(
nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect(1, 2, 3, 4));
FrameCaptionButtonContainerView container(widget.get());
views::StaticSizedView window_icon(gfx::Size(16, 16));
window_icon.SetBounds(0, 0, 16, 16);
widget->SetBounds(gfx::Rect(0, 0, 500, 500));
widget->Show();
DefaultFrameHeader frame_header(
widget.get(), widget->non_client_view()->frame_view(), &container);
frame_header.SetLeftHeaderView(&window_icon);
frame_header.LayoutHeader();
gfx::Rect title_bounds = frame_header.GetTitleBounds();
EXPECT_EQ(window_icon.bounds().CenterPoint().y(),
title_bounds.CenterPoint().y());
}
TEST_F(DefaultFrameHeaderTest, BackButtonAlignment) {
std::unique_ptr<Widget> widget = CreateTestWidget(
nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect(1, 2, 3, 4));
FrameCaptionButtonContainerView container(widget.get());
FrameBackButton back;
DefaultFrameHeader frame_header(
widget.get(), widget->non_client_view()->frame_view(), &container);
frame_header.SetBackButton(&back);
frame_header.LayoutHeader();
gfx::Rect title_bounds = frame_header.GetTitleBounds();
// The back button should be positioned at the left edge, and
// vertically centered.
EXPECT_EQ(back.bounds().CenterPoint().y(), title_bounds.CenterPoint().y());
EXPECT_EQ(0, back.bounds().x());
}
TEST_F(DefaultFrameHeaderTest, MinimumHeaderWidthRTL) {
base::test::ScopedRestoreICUDefaultLocale restore_locale;
std::unique_ptr<Widget> widget = CreateTestWidget(
nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect(1, 2, 3, 4));
FrameCaptionButtonContainerView container(widget.get());
DefaultFrameHeader frame_header(
widget.get(), widget->non_client_view()->frame_view(), &container);
frame_header.LayoutHeader();
int ltr_minimum_width = frame_header.GetMinimumHeaderWidth();
base::i18n::SetRTLForTesting(true);
frame_header.LayoutHeader();
int rtl_minimum_width = frame_header.GetMinimumHeaderWidth();
EXPECT_EQ(ltr_minimum_width, rtl_minimum_width);
}
// Ensure the right frame colors are used.
TEST_F(DefaultFrameHeaderTest, FrameColors) {
const auto win0_bounds = gfx::Rect{1, 2, 3, 4};
auto win0 = CreateAppWindow(win0_bounds, AppType::BROWSER);
Widget* widget = Widget::GetWidgetForNativeWindow(win0.get());
DefaultFrameHeader* frame_header =
static_cast<DefaultFrameHeader*>(FrameHeader::Get(widget));
// Check frame color is sensitive to mode.
SkColor active = SkColorSetRGB(70, 70, 70);
SkColor inactive = SkColorSetRGB(200, 200, 200);
win0->SetProperty(kFrameActiveColorKey, active);
win0->SetProperty(kFrameInactiveColorKey, inactive);
frame_header->UpdateFrameColors();
frame_header->mode_ = FrameHeader::MODE_ACTIVE;
EXPECT_EQ(active, frame_header->GetCurrentFrameColor());
frame_header->mode_ = FrameHeader::MODE_INACTIVE;
EXPECT_EQ(inactive, frame_header->GetCurrentFrameColor());
EXPECT_EQ(active, frame_header->GetActiveFrameColorForPaintForTest());
// Update to the new value which has no blue, which should animate.
frame_header->mode_ = FrameHeader::MODE_ACTIVE;
SkColor new_active = SkColorSetRGB(70, 70, 0);
win0->SetProperty(kFrameActiveColorKey, new_active);
frame_header->UpdateFrameColors();
// Now update to the new value which is full blue.
SkColor new_new_active = SkColorSetRGB(70, 70, 255);
win0->SetProperty(kFrameActiveColorKey, new_new_active);
frame_header->UpdateFrameColors();
// Again, GetCurrentFrameColor should return the target color.
EXPECT_EQ(new_new_active, frame_header->GetCurrentFrameColor());
}
namespace {
class LayerDestroyedChecker : public ui::LayerObserver {
public:
explicit LayerDestroyedChecker(ui::Layer* layer) { layer->AddObserver(this); }
LayerDestroyedChecker(const LayerDestroyedChecker&) = delete;
LayerDestroyedChecker& operator=(const LayerDestroyedChecker&) = delete;
~LayerDestroyedChecker() override = default;
void LayerDestroyed(ui::Layer* layer) override {
layer->RemoveObserver(this);
destroyed_ = true;
}
bool destroyed() const { return destroyed_; }
private:
bool destroyed_ = false;
};
} // namespace
// A class to wait until hthe frame header is painted.
class FramePaintWaiter : public ui::CompositorObserver {
public:
explicit FramePaintWaiter(aura::Window* window)
: frame_header_(
FrameHeader::Get(Widget::GetWidgetForNativeWindow(window))) {
frame_header_->view()->GetWidget()->GetCompositor()->AddObserver(this);
}
FramePaintWaiter(const FramePaintWaiter&) = delete;
FramePaintWaiter& operator=(FramePaintWaiter&) = delete;
~FramePaintWaiter() override {
frame_header_->view()->GetWidget()->GetCompositor()->RemoveObserver(this);
}
// ui::CompositorObserver:
void OnCompositingDidCommit(ui::Compositor* compositor) override {
if (frame_header_->painted_)
run_loop_.Quit();
}
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
FrameHeader* frame_header_ = nullptr;
};
TEST_F(DefaultFrameHeaderTest, DeleteDuringAnimation) {
const auto bounds = gfx::Rect(100, 100);
auto win0 = CreateAppWindow(bounds, AppType::BROWSER);
auto win1 = CreateAppWindow(bounds, AppType::BROWSER);
Widget* widget = Widget::GetWidgetForNativeWindow(win0.get());
EXPECT_TRUE(FrameHeader::Get(widget));
EXPECT_TRUE(wm::IsActiveWindow(win1.get()));
// A frame will not animate until it is painted first.
FramePaintWaiter(win0.get()).Wait();
FramePaintWaiter(win1.get()).Wait();
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
wm::ActivateWindow(win0.get());
auto* frame_view = NonClientFrameViewAsh::Get(win0.get());
auto* animating_layer_holding_view = frame_view->children()[0];
EXPECT_TRUE(views::IsViewClass<chromeos::FrameHeader::FrameAnimatorView>(
animating_layer_holding_view));
ASSERT_TRUE(animating_layer_holding_view->layer());
ASSERT_GT(animating_layer_holding_view->layer()->parent()->children().size(),
2u);
auto* animating_layer =
animating_layer_holding_view->layer()->parent()->children()[0];
EXPECT_EQ(ui::LAYER_TEXTURED, animating_layer->type());
EXPECT_NE(std::string::npos, animating_layer->name().find(":Old", 0));
EXPECT_TRUE(animating_layer->GetAnimator()->is_animating());
LayerDestroyedChecker checker(animating_layer);
win0.reset();
EXPECT_TRUE(checker.destroyed());
}
// Make sure that the animation is canceled when resized.
TEST_F(DefaultFrameHeaderTest, ResizeAndReorderDuringAnimation) {
const auto bounds = gfx::Rect(100, 100);
auto win_0 = CreateAppWindow(bounds, AppType::BROWSER);
auto win_1 = CreateAppWindow(bounds, AppType::BROWSER);
EXPECT_TRUE(wm::IsActiveWindow(win_1.get()));
// A frame will not animate until it is painted first.
FramePaintWaiter(win_0.get()).Wait();
FramePaintWaiter(win_1.get()).Wait();
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
auto* frame_view_0 = NonClientFrameViewAsh::Get(win_0.get());
auto* animating_layer_holding_view_0 = frame_view_0->children()[0];
EXPECT_TRUE(views::IsViewClass<chromeos::FrameHeader::FrameAnimatorView>(
animating_layer_holding_view_0));
size_t original_layers_count_0 =
animating_layer_holding_view_0->layer()->parent()->children().size();
auto* frame_view_1 = NonClientFrameViewAsh::Get(win_1.get());
auto* extra_view_1 =
frame_view_1->AddChildView(std::make_unique<views::View>());
auto* animating_layer_holding_view_1 = frame_view_1->children()[0];
EXPECT_TRUE(views::IsViewClass<chromeos::FrameHeader::FrameAnimatorView>(
animating_layer_holding_view_1));
size_t original_layers_count_1 =
animating_layer_holding_view_1->layer()->parent()->children().size();
wm::ActivateWindow(win_0.get());
{
// Resize during animation
EXPECT_EQ(
animating_layer_holding_view_0->layer()->parent()->children().size(),
original_layers_count_0 + 1);
auto* animating_layer =
animating_layer_holding_view_0->layer()->parent()->children()[0];
EXPECT_TRUE(animating_layer->GetAnimator()->is_animating());
LayerDestroyedChecker checker(animating_layer);
win_0->SetBounds(gfx::Rect(200, 200));
// Animating layer shuld have been removed.
EXPECT_EQ(
animating_layer_holding_view_0->layer()->parent()->children().size(),
original_layers_count_0);
EXPECT_TRUE(checker.destroyed());
}
{
// win_1 should still be animating.
EXPECT_EQ(
animating_layer_holding_view_1->layer()->parent()->children().size(),
original_layers_count_1 + 1);
auto* animating_layer =
animating_layer_holding_view_1->layer()->parent()->children()[0];
EXPECT_TRUE(animating_layer->GetAnimator()->is_animating());
LayerDestroyedChecker checker(animating_layer);
// Change the view's stacking order should stop the animation.
ASSERT_EQ(3u, frame_view_1->children().size());
frame_view_1->ReorderChildView(extra_view_1, 0);
EXPECT_EQ(
animating_layer_holding_view_1->layer()->parent()->children().size(),
original_layers_count_1);
EXPECT_TRUE(checker.destroyed());
}
}
// Make sure that the animation request while animating will not
// create another animation.
TEST_F(DefaultFrameHeaderTest, AnimateDuringAnimation) {
const auto bounds = gfx::Rect(100, 100);
auto win_0 = CreateAppWindow(bounds, AppType::BROWSER);
// A frame will not animate until it is painted first.
FramePaintWaiter(win_0.get()).Wait();
auto* widget = Widget::GetWidgetForNativeWindow(win_0.get());
auto lock = widget->LockPaintAsActive();
auto win_1 = CreateAppWindow(bounds, AppType::BROWSER);
FramePaintWaiter(win_1.get()).Wait();
EXPECT_TRUE(wm::IsActiveWindow(win_1.get()));
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
auto layer_bounds = win_0->layer()->bounds();
lock.reset();
win_1.reset();
EXPECT_TRUE(wm::IsActiveWindow(win_0.get()));
// Makes sure that the layer has full damaged bounds.
EXPECT_TRUE(win_0->layer()->damaged_region().Contains(layer_bounds));
}
} // namespace ash