| // Copyright 2013 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/shelf/shelf_window_watcher.h" |
| |
| #include <memory> |
| |
| #include "ash/public/cpp/shelf_item.h" |
| #include "ash/public/cpp/shelf_model.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shell.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/window_factory.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "ash/wm/window_resizer.h" |
| #include "ash/wm/window_state.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/resources/grit/ui_resources.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/transient_window_controller.h" |
| |
| namespace ash { |
| namespace { |
| |
| // Create a test 1x1 icon image with a given |color|. |
| gfx::ImageSkia CreateImageSkiaIcon(SkColor color) { |
| SkBitmap bitmap; |
| bitmap.allocN32Pixels(1, 1); |
| bitmap.eraseColor(color); |
| return gfx::ImageSkia::CreateFrom1xBitmap(bitmap); |
| } |
| |
| static ShelfID CreateShelfItem(aura::Window* window) { |
| static int id = 0; |
| ShelfID shelf_id(base::NumberToString(id++)); |
| window->SetProperty(kShelfIDKey, new std::string(shelf_id.Serialize())); |
| window->SetProperty(kShelfItemTypeKey, static_cast<int32_t>(TYPE_DIALOG)); |
| return shelf_id; |
| } |
| |
| using ShelfWindowWatcherTest = AshTestBase; |
| |
| // Ensure shelf items are added and removed as windows are opened and closed. |
| TEST_F(ShelfWindowWatcherTest, OpenAndClose) { |
| ShelfModel* model = ShelfModel::Get(); |
| // ShelfModel creates an app list item and back button. |
| ASSERT_EQ(2, model->item_count()); |
| // Windows with valid ShelfItemType and ShelfID properties get shelf items. |
| std::unique_ptr<views::Widget> widget1 = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| CreateShelfItem(widget1->GetNativeWindow()); |
| EXPECT_EQ(3, model->item_count()); |
| std::unique_ptr<views::Widget> widget2 = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| CreateShelfItem(widget2->GetNativeWindow()); |
| EXPECT_EQ(4, model->item_count()); |
| |
| // Each ShelfItem is removed when the associated window is destroyed. |
| widget1.reset(); |
| EXPECT_EQ(3, model->item_count()); |
| widget2.reset(); |
| EXPECT_EQ(2, model->item_count()); |
| } |
| |
| TEST_F(ShelfWindowWatcherTest, CreateAndRemoveShelfItemProperties) { |
| ShelfModel* model = ShelfModel::Get(); |
| // Creating windows without a valid ShelfItemType does not add items. |
| std::unique_ptr<views::Widget> widget1 = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| std::unique_ptr<views::Widget> widget2 = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| EXPECT_EQ(2, model->item_count()); |
| |
| // Create a ShelfItem for the first window. |
| ShelfID id_w1 = CreateShelfItem(widget1->GetNativeWindow()); |
| EXPECT_EQ(3, model->item_count()); |
| |
| int index_w1 = model->ItemIndexByID(id_w1); |
| EXPECT_EQ(STATUS_RUNNING, model->items()[index_w1].status); |
| |
| // Create a ShelfItem for the second window. |
| ShelfID id_w2 = CreateShelfItem(widget2->GetNativeWindow()); |
| EXPECT_EQ(4, model->item_count()); |
| |
| int index_w2 = model->ItemIndexByID(id_w2); |
| EXPECT_EQ(STATUS_RUNNING, model->items()[index_w2].status); |
| |
| // ShelfItem is removed when the type property is cleared. |
| widget1->GetNativeWindow()->SetProperty(kShelfItemTypeKey, |
| static_cast<int32_t>(TYPE_UNDEFINED)); |
| EXPECT_EQ(3, model->item_count()); |
| widget2->GetNativeWindow()->SetProperty(kShelfItemTypeKey, |
| static_cast<int32_t>(TYPE_UNDEFINED)); |
| EXPECT_EQ(2, model->item_count()); |
| // Clearing twice doesn't do anything. |
| widget2->GetNativeWindow()->SetProperty(kShelfItemTypeKey, |
| static_cast<int32_t>(TYPE_UNDEFINED)); |
| EXPECT_EQ(2, model->item_count()); |
| |
| // Closing the windows once the properties are cleared doesn't do anything. |
| widget1->CloseNow(); |
| EXPECT_EQ(2, model->item_count()); |
| widget2->CloseNow(); |
| EXPECT_EQ(2, model->item_count()); |
| } |
| |
| TEST_F(ShelfWindowWatcherTest, UpdateWindowProperty) { |
| ShelfModel* model = ShelfModel::Get(); |
| // Create a ShelfItem for a new window. |
| std::unique_ptr<views::Widget> widget = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| ShelfID id = CreateShelfItem(widget->GetNativeWindow()); |
| EXPECT_EQ(3, model->item_count()); |
| |
| int index = model->ItemIndexByID(id); |
| EXPECT_EQ(STATUS_RUNNING, model->items()[index].status); |
| |
| // No new item is created after updating a launcher item. |
| EXPECT_EQ(3, model->item_count()); |
| // index and id are not changed after updating a launcher item. |
| EXPECT_EQ(index, model->ItemIndexByID(id)); |
| EXPECT_EQ(id, model->items()[index].id); |
| } |
| |
| TEST_F(ShelfWindowWatcherTest, MaximizeAndRestoreWindow) { |
| ShelfModel* model = ShelfModel::Get(); |
| // Create a ShelfItem for a new window. |
| std::unique_ptr<views::Widget> widget = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| ShelfID id = CreateShelfItem(widget->GetNativeWindow()); |
| EXPECT_EQ(3, model->item_count()); |
| |
| int index = model->ItemIndexByID(id); |
| EXPECT_EQ(STATUS_RUNNING, model->items()[index].status); |
| |
| // Maximize the window. |
| wm::WindowState* window_state = wm::GetWindowState(widget->GetNativeWindow()); |
| EXPECT_FALSE(window_state->IsMaximized()); |
| window_state->Maximize(); |
| EXPECT_TRUE(window_state->IsMaximized()); |
| // No new item is created after maximizing the window. |
| EXPECT_EQ(3, model->item_count()); |
| // index and id are not changed after maximizing the window. |
| EXPECT_EQ(index, model->ItemIndexByID(id)); |
| EXPECT_EQ(id, model->items()[index].id); |
| |
| // Restore the window. |
| window_state->Restore(); |
| EXPECT_FALSE(window_state->IsMaximized()); |
| // No new item is created after restoring the window. |
| EXPECT_EQ(3, model->item_count()); |
| // Index and id are not changed after maximizing the window. |
| EXPECT_EQ(index, model->ItemIndexByID(id)); |
| EXPECT_EQ(id, model->items()[index].id); |
| } |
| |
| // Check |window|'s item is not changed during the dragging. |
| // TODO(simonhong): Add a test for removing a Window during the dragging. |
| TEST_F(ShelfWindowWatcherTest, DragWindow) { |
| ShelfModel* model = ShelfModel::Get(); |
| // Create a ShelfItem for a new window. |
| std::unique_ptr<views::Widget> widget = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| ShelfID id = CreateShelfItem(widget->GetNativeWindow()); |
| EXPECT_EQ(3, model->item_count()); |
| |
| int index = model->ItemIndexByID(id); |
| EXPECT_EQ(STATUS_RUNNING, model->items()[index].status); |
| |
| // Simulate dragging of the window and check its item is not changed. |
| std::unique_ptr<WindowResizer> resizer( |
| CreateWindowResizer(widget->GetNativeWindow(), gfx::Point(), HTCAPTION, |
| ::wm::WINDOW_MOVE_SOURCE_MOUSE)); |
| ASSERT_TRUE(resizer.get()); |
| resizer->Drag(gfx::Point(50, 50), 0); |
| resizer->CompleteDrag(); |
| |
| // Index and id are not changed after dragging the window. |
| EXPECT_EQ(index, model->ItemIndexByID(id)); |
| EXPECT_EQ(id, model->items()[index].id); |
| } |
| |
| // Ensure dialogs get shelf items. |
| TEST_F(ShelfWindowWatcherTest, DialogWindows) { |
| ShelfModel* model = ShelfModel::Get(); |
| // An item is created for a dialog window. |
| std::unique_ptr<views::Widget> dialog_widget = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| aura::Window* dialog = dialog_widget->GetNativeWindow(); |
| dialog->SetProperty(kShelfIDKey, new std::string(ShelfID("a").Serialize())); |
| dialog->SetProperty(kShelfItemTypeKey, static_cast<int32_t>(TYPE_DIALOG)); |
| EXPECT_EQ(3, model->item_count()); |
| |
| // An item is not created for an app window. |
| std::unique_ptr<views::Widget> app_widget = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| aura::Window* app = app_widget->GetNativeWindow(); |
| app->SetProperty(kShelfIDKey, new std::string(ShelfID("c").Serialize())); |
| app->SetProperty(kShelfItemTypeKey, static_cast<int32_t>(TYPE_APP)); |
| EXPECT_EQ(3, model->item_count()); |
| app_widget.reset(); |
| |
| // Each ShelfItem is removed when the associated window is destroyed. |
| dialog_widget.reset(); |
| EXPECT_EQ(2, model->item_count()); |
| } |
| |
| // Ensure items use the app icon and window icon aura::Window properties. |
| TEST_F(ShelfWindowWatcherTest, ItemIcon) { |
| ShelfModel* model = ShelfModel::Get(); |
| // Create a ShelfItem for a window; it should have a default icon. |
| std::unique_ptr<views::Widget> widget = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| aura::Window* window = widget->GetNativeWindow(); |
| ShelfID id = CreateShelfItem(window); |
| EXPECT_EQ(3, model->item_count()); |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| gfx::Image default_image = rb.GetImageNamed(IDR_DEFAULT_FAVICON_32); |
| EXPECT_TRUE(model->items()[2].image.BackedBySameObjectAs( |
| default_image.AsImageSkia())); |
| |
| // Setting a window icon should update the item icon. |
| const gfx::ImageSkia red = CreateImageSkiaIcon(SK_ColorRED); |
| window->SetProperty(aura::client::kWindowIconKey, new gfx::ImageSkia(red)); |
| EXPECT_EQ(SK_ColorRED, model->items()[2].image.bitmap()->getColor(0, 0)); |
| |
| // Setting an app icon should override the window icon. |
| const gfx::ImageSkia blue = CreateImageSkiaIcon(SK_ColorBLUE); |
| window->SetProperty(aura::client::kAppIconKey, new gfx::ImageSkia(blue)); |
| EXPECT_EQ(SK_ColorBLUE, model->items()[2].image.bitmap()->getColor(0, 0)); |
| |
| // Clearing the app icon should restore the window icon to the shelf item. |
| window->ClearProperty(aura::client::kAppIconKey); |
| EXPECT_EQ(SK_ColorRED, model->items()[2].image.bitmap()->getColor(0, 0)); |
| } |
| |
| TEST_F(ShelfWindowWatcherTest, DontCreateShelfEntriesForChildWindows) { |
| ShelfModel* model = ShelfModel::Get(); |
| std::unique_ptr<aura::Window> window = |
| window_factory::NewWindow(nullptr, aura::client::WINDOW_TYPE_NORMAL); |
| window->Init(ui::LAYER_NOT_DRAWN); |
| window->SetProperty(kShelfIDKey, new std::string(ShelfID("a").Serialize())); |
| window->SetProperty(kShelfItemTypeKey, static_cast<int32_t>(TYPE_DIALOG)); |
| Shell::GetPrimaryRootWindow() |
| ->GetChildById(desks_util::GetActiveDeskContainerId()) |
| ->AddChild(window.get()); |
| window->Show(); |
| EXPECT_EQ(3, model->item_count()); |
| |
| std::unique_ptr<aura::Window> child = |
| window_factory::NewWindow(nullptr, aura::client::WINDOW_TYPE_NORMAL); |
| child->Init(ui::LAYER_NOT_DRAWN); |
| child->SetProperty(kShelfIDKey, new std::string(ShelfID("b").Serialize())); |
| child->SetProperty(kShelfItemTypeKey, static_cast<int32_t>(TYPE_DIALOG)); |
| window->AddChild(child.get()); |
| child->Show(); |
| // There should not be a new shelf item for |child|. |
| EXPECT_EQ(3, model->item_count()); |
| |
| child.reset(); |
| EXPECT_EQ(3, model->item_count()); |
| window.reset(); |
| EXPECT_EQ(2, model->item_count()); |
| } |
| |
| TEST_F(ShelfWindowWatcherTest, CreateShelfEntriesForTransientWindows) { |
| ShelfModel* model = ShelfModel::Get(); |
| std::unique_ptr<aura::Window> window = |
| window_factory::NewWindow(nullptr, aura::client::WINDOW_TYPE_NORMAL); |
| window->Init(ui::LAYER_NOT_DRAWN); |
| window->SetProperty(kShelfIDKey, new std::string(ShelfID("a").Serialize())); |
| window->SetProperty(kShelfItemTypeKey, static_cast<int32_t>(TYPE_DIALOG)); |
| Shell::GetPrimaryRootWindow() |
| ->GetChildById(desks_util::GetActiveDeskContainerId()) |
| ->AddChild(window.get()); |
| window->Show(); |
| EXPECT_EQ(3, model->item_count()); |
| |
| std::unique_ptr<aura::Window> transient = |
| window_factory::NewWindow(nullptr, aura::client::WINDOW_TYPE_NORMAL); |
| transient->Init(ui::LAYER_NOT_DRAWN); |
| transient->SetProperty(kShelfIDKey, |
| new std::string(ShelfID("b").Serialize())); |
| transient->SetProperty(kShelfItemTypeKey, static_cast<int32_t>(TYPE_DIALOG)); |
| Shell::GetPrimaryRootWindow() |
| ->GetChildById(desks_util::GetActiveDeskContainerId()) |
| ->AddChild(transient.get()); |
| ::wm::TransientWindowController::Get()->AddTransientChild(window.get(), |
| transient.get()); |
| transient->Show(); |
| // There should be a new shelf item for |transient|. |
| EXPECT_EQ(4, model->item_count()); |
| |
| transient.reset(); |
| EXPECT_EQ(3, model->item_count()); |
| window.reset(); |
| EXPECT_EQ(2, model->item_count()); |
| } |
| |
| // Ensures ShelfWindowWatcher supports windows opened prior to session start. |
| using ShelfWindowWatcherSessionStartTest = NoSessionAshTestBase; |
| TEST_F(ShelfWindowWatcherSessionStartTest, PreExistingWindow) { |
| ShelfModel* model = ShelfModel::Get(); |
| ASSERT_FALSE( |
| Shell::Get()->session_controller()->IsActiveUserSessionStarted()); |
| |
| // ShelfModel creates an app list item and back button. |
| EXPECT_EQ(2, model->item_count()); |
| |
| // Construct a window that should get a shelf item once the session starts. |
| std::unique_ptr<views::Widget> widget = CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); |
| CreateShelfItem(widget->GetNativeWindow()); |
| EXPECT_EQ(2, model->item_count()); |
| |
| // Start the test user session; ShelfWindowWatcher will find the open window. |
| CreateUserSessions(1); |
| EXPECT_EQ(3, model->item_count()); |
| } |
| |
| } // namespace |
| } // namespace ash |