| // Copyright 2018 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 "ash/wm/window_preview_view.h" |
| |
| #include "ash/public/cpp/app_types.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/wm/window_preview_view_test_api.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace ash { |
| namespace { |
| |
| using WindowPreviewViewTest = AshTestBase; |
| |
| // Creates and returns a widget whose type is the given |type|, which is added |
| // as a transient child of the given |parent_widget|. |
| std::unique_ptr<views::Widget> CreateTransientChild( |
| views::Widget* parent_widget, |
| views::Widget::InitParams::Type type) { |
| auto widget = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params{type}; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.bounds = gfx::Rect{40, 50}; |
| params.context = params.parent = parent_widget->GetNativeWindow(); |
| params.init_properties_container.SetProperty( |
| aura::client::kAppType, static_cast<int>(ash::AppType::ARC_APP)); |
| widget->Init(std::move(params)); |
| widget->Show(); |
| return widget; |
| } |
| |
| // Test that if we have two widgets whos windows are linked together by |
| // transience, WindowPreviewView's internal collection will contain both those |
| // two windows. |
| TEST_F(WindowPreviewViewTest, Basic) { |
| auto widget1 = CreateTestWidget(); |
| auto widget2 = CreateTestWidget(); |
| |
| ::wm::AddTransientChild(widget1->GetNativeWindow(), |
| widget2->GetNativeWindow()); |
| auto preview_view = std::make_unique<WindowPreviewView>( |
| widget1->GetNativeWindow(), /*trilinear_filtering_on_init=*/false); |
| WindowPreviewViewTestApi test_api(preview_view.get()); |
| EXPECT_EQ(2u, test_api.GetMirrorViews().size()); |
| EXPECT_TRUE(test_api.GetMirrorViews().contains(widget1->GetNativeWindow())); |
| EXPECT_TRUE(test_api.GetMirrorViews().contains(widget2->GetNativeWindow())); |
| } |
| |
| // Tests that the preview view preferred size is the same aspect ratio as the |
| // window's non client view. |
| TEST_F(WindowPreviewViewTest, AspectRatio) { |
| // Default frame header is 32dp, so we expect a window of size 300, 300 to |
| // have a preview of 1:1 ratio. |
| auto window = CreateAppWindow(gfx::Rect(300, 332)); |
| auto preview_view = std::make_unique<WindowPreviewView>( |
| window.get(), /*trilinear_filtering_on_init=*/false); |
| |
| const gfx::SizeF preferred_size(preview_view->GetPreferredSize()); |
| EXPECT_EQ(1.f, preferred_size.width() / preferred_size.height()); |
| } |
| |
| // Tests that WindowPreviewView behaves as expected when we add or remove |
| // transient children. |
| TEST_F(WindowPreviewViewTest, TransientChildAddedAndRemoved) { |
| auto widget1 = CreateTestWidget(); |
| auto widget2 = CreateTestWidget(); |
| auto widget3 = CreateTestWidget(); |
| |
| ::wm::AddTransientChild(widget1->GetNativeWindow(), |
| widget2->GetNativeWindow()); |
| auto preview_view = std::make_unique<WindowPreviewView>( |
| widget1->GetNativeWindow(), /*trilinear_filtering_on_init=*/false); |
| WindowPreviewViewTestApi test_api(preview_view.get()); |
| ASSERT_EQ(2u, test_api.GetMirrorViews().size()); |
| |
| ::wm::AddTransientChild(widget1->GetNativeWindow(), |
| widget3->GetNativeWindow()); |
| EXPECT_EQ(3u, test_api.GetMirrorViews().size()); |
| |
| ::wm::RemoveTransientChild(widget1->GetNativeWindow(), |
| widget3->GetNativeWindow()); |
| EXPECT_EQ(2u, test_api.GetMirrorViews().size()); |
| } |
| |
| // Tests that init'ing a Widget with a native window as a transient child before |
| // it is parented to a parent window doesn't cause a crash while the |
| // WindowPreviewView is observing transient windows additions. |
| // https://crbug.com/1003544. |
| TEST_F(WindowPreviewViewTest, NoCrashWithTransientChildWithNoWindowState) { |
| auto widget1 = CreateTestWidget(); |
| |
| auto transient_child1 = CreateTransientChild( |
| widget1.get(), views::Widget::InitParams::TYPE_WINDOW); |
| |
| EXPECT_EQ(widget1->GetNativeWindow(), |
| wm::GetTransientParent(transient_child1->GetNativeWindow())); |
| |
| auto preview_view = std::make_unique<WindowPreviewView>( |
| widget1->GetNativeWindow(), /*trilinear_filtering_on_init=*/false); |
| WindowPreviewViewTestApi test_api(preview_view.get()); |
| ASSERT_EQ(2u, test_api.GetMirrorViews().size()); |
| |
| // The popup and bubble transient child should be ignored (they result in a |
| // native window type WINDOW_TYPE_POPUP), while the TYPE_WINDOW one will be |
| // added as a transient child before it is parented to a container. This |
| // should not cause a crash. |
| auto transient_child2 = CreateTransientChild( |
| widget1.get(), views::Widget::InitParams::TYPE_POPUP); |
| auto transient_child3 = CreateTransientChild( |
| widget1.get(), views::Widget::InitParams::TYPE_BUBBLE); |
| auto transient_child4 = CreateTransientChild( |
| widget1.get(), views::Widget::InitParams::TYPE_WINDOW); |
| |
| EXPECT_EQ(widget1->GetNativeWindow(), |
| wm::GetTransientParent(transient_child2->GetNativeWindow())); |
| EXPECT_EQ(widget1->GetNativeWindow(), |
| wm::GetTransientParent(transient_child3->GetNativeWindow())); |
| EXPECT_EQ(widget1->GetNativeWindow(), |
| wm::GetTransientParent(transient_child4->GetNativeWindow())); |
| EXPECT_EQ(3u, test_api.GetMirrorViews().size()); |
| |
| transient_child3.reset(); |
| EXPECT_EQ(3u, test_api.GetMirrorViews().size()); |
| transient_child4.reset(); |
| EXPECT_EQ(2u, test_api.GetMirrorViews().size()); |
| } |
| |
| // Tests that if cycling stops before a transient popup child is destroyed |
| // doesn't introduce a crash. https://crbug.com/1014543. |
| TEST_F(WindowPreviewViewTest, |
| NoCrashWhenWindowCyclingIsCanceledWithATransientPopup) { |
| auto widget1 = CreateTestWidget(); |
| |
| auto preview_view = std::make_unique<WindowPreviewView>( |
| widget1->GetNativeWindow(), /*trilinear_filtering_on_init=*/false); |
| WindowPreviewViewTestApi test_api(preview_view.get()); |
| ASSERT_EQ(1u, test_api.GetMirrorViews().size()); |
| |
| auto transient_popup = CreateTransientChild( |
| widget1.get(), views::Widget::InitParams::TYPE_POPUP); |
| ASSERT_EQ(1u, test_api.GetMirrorViews().size()); |
| |
| // Simulate canceling window cycling now, there should be no crashes. |
| preview_view.reset(); |
| } |
| |
| TEST_F(WindowPreviewViewTest, LayoutChildWithinParentBounds) { |
| UpdateDisplay("1000x1000"); |
| |
| // Create two widgets linked transiently. The child window is within the |
| // bounds of the parent window. |
| auto widget1 = CreateTestWidget(); |
| auto widget2 = CreateTestWidget(); |
| widget1->GetNativeWindow()->SetBounds(gfx::Rect(0, -20, 100, 120)); |
| widget1->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, 20); |
| widget2->GetNativeWindow()->SetBounds(gfx::Rect(20, 20, 50, 50)); |
| widget2->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, 10); |
| ::wm::AddTransientChild(widget1->GetNativeWindow(), |
| widget2->GetNativeWindow()); |
| |
| // The top inset is excluded from GetUnionRect() calculations. |
| auto preview_view = std::make_unique<WindowPreviewView>( |
| widget1->GetNativeWindow(), /*trilinear_filtering_on_init=*/false); |
| WindowPreviewViewTestApi test_api(preview_view.get()); |
| EXPECT_EQ(gfx::RectF(100.f, 100.f), test_api.GetUnionRect()); |
| |
| // Test that the ratio between the two windows is maintained. |
| preview_view->SetBoundsRect(gfx::Rect(500, 500)); |
| EXPECT_EQ(gfx::Rect(500, 500), |
| test_api.GetMirrorViewForWidget(widget1.get())->bounds()); |
| // The original rect (20, 20, 50, 50) should now be (100, 100, 250, 250). |
| // However, the top inset of 10 is now 50, and is excluded from calculations. |
| EXPECT_EQ(gfx::Rect(100, 150, 250, 200), |
| test_api.GetMirrorViewForWidget(widget2.get())->bounds()); |
| } |
| |
| // Test that WindowPreviewView layouts the transient tree correctly when each |
| // transient child is outside the bounds of its transient parent. |
| TEST_F(WindowPreviewViewTest, LayoutChildOutsideParentBounds) { |
| UpdateDisplay("1000x1000"); |
| |
| // Create two widgets linked transiently. The child window is outside of the |
| // bounds of the parent window. |
| auto widget1 = CreateTestWidget(); |
| auto widget2 = CreateTestWidget(); |
| widget1->GetNativeWindow()->SetBounds(gfx::Rect(0, -20, 200, 220)); |
| widget1->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, 20); |
| widget2->GetNativeWindow()->SetBounds(gfx::Rect(300, 300, 100, 100)); |
| widget2->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, 20); |
| ::wm::AddTransientChild(widget1->GetNativeWindow(), |
| widget2->GetNativeWindow()); |
| |
| // Get the union rect of the two windows. The top inset is excluded from |
| // calculations. |
| auto preview_view = std::make_unique<WindowPreviewView>( |
| widget1->GetNativeWindow(), /*trilinear_filtering_on_init=*/false); |
| WindowPreviewViewTestApi test_api(preview_view.get()); |
| EXPECT_EQ(gfx::RectF(400.f, 400.f), test_api.GetUnionRect()); |
| |
| // Test that the ratio between the two windows, relative to the smallest |
| // rectangle which encompasses them both (0, 0, 400, 400) is maintained. The |
| // first window is positioned at the origin, the second window is positioned |
| // such that it is 25% larger and the bottom right corner is aligned with |
| // |preview_view|'s bottom right corner. |
| preview_view->SetBoundsRect(gfx::Rect(500, 500)); |
| EXPECT_EQ(gfx::Rect(250, 250), |
| test_api.GetMirrorViewForWidget(widget1.get())->bounds()); |
| EXPECT_EQ(gfx::Rect(375, 400, 125, 100), |
| test_api.GetMirrorViewForWidget(widget2.get())->bounds()); |
| } |
| |
| } // namespace |
| } // namespace ash |