// 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 #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/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, shelf_id.Serialize()); window->SetProperty(kShelfItemTypeKey, static_cast(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(); ASSERT_EQ(0, model->item_count()); // Windows with valid ShelfItemType and ShelfID properties get shelf items. std::unique_ptr widget1 = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); CreateShelfItem(widget1->GetNativeWindow()); EXPECT_EQ(1, model->item_count()); std::unique_ptr widget2 = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); CreateShelfItem(widget2->GetNativeWindow()); EXPECT_EQ(2, model->item_count()); // Each ShelfItem is removed when the associated window is destroyed. widget1.reset(); EXPECT_EQ(1, model->item_count()); widget2.reset(); EXPECT_EQ(0, model->item_count()); } TEST_F(ShelfWindowWatcherTest, CreateAndRemoveShelfItemProperties) { ShelfModel* model = ShelfModel::Get(); // Creating windows without a valid ShelfItemType does not add items. std::unique_ptr widget1 = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); std::unique_ptr widget2 = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); EXPECT_EQ(0, model->item_count()); // Create a ShelfItem for the first window. ShelfID id_w1 = CreateShelfItem(widget1->GetNativeWindow()); EXPECT_EQ(1, 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(2, 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(TYPE_UNDEFINED)); EXPECT_EQ(1, model->item_count()); widget2->GetNativeWindow()->SetProperty(kShelfItemTypeKey, static_cast(TYPE_UNDEFINED)); EXPECT_EQ(0, model->item_count()); // Clearing twice doesn't do anything. widget2->GetNativeWindow()->SetProperty(kShelfItemTypeKey, static_cast(TYPE_UNDEFINED)); EXPECT_EQ(0, model->item_count()); // Closing the windows once the properties are cleared doesn't do anything. widget1->CloseNow(); EXPECT_EQ(0, model->item_count()); widget2->CloseNow(); EXPECT_EQ(0, model->item_count()); } TEST_F(ShelfWindowWatcherTest, UpdateWindowProperty) { ShelfModel* model = ShelfModel::Get(); // Create a ShelfItem for a new window. std::unique_ptr widget = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); ShelfID id = CreateShelfItem(widget->GetNativeWindow()); EXPECT_EQ(1, 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(1, 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 widget = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); ShelfID id = CreateShelfItem(widget->GetNativeWindow()); EXPECT_EQ(1, model->item_count()); int index = model->ItemIndexByID(id); EXPECT_EQ(STATUS_RUNNING, model->items()[index].status); // Maximize the window. WindowState* window_state = WindowState::Get(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(1, 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(1, 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 widget = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); ShelfID id = CreateShelfItem(widget->GetNativeWindow()); EXPECT_EQ(1, 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 resizer( CreateWindowResizer(widget->GetNativeWindow(), gfx::PointF(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_MOUSE)); ASSERT_TRUE(resizer.get()); resizer->Drag(gfx::PointF(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 dialog_widget = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); aura::Window* dialog = dialog_widget->GetNativeWindow(); dialog->SetProperty(kShelfIDKey, ShelfID("a").Serialize()); dialog->SetProperty(kShelfItemTypeKey, static_cast(TYPE_DIALOG)); EXPECT_EQ(1, model->item_count()); // An item is not created for an app window. std::unique_ptr app_widget = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); aura::Window* app = app_widget->GetNativeWindow(); app->SetProperty(kShelfIDKey, ShelfID("c").Serialize()); app->SetProperty(kShelfItemTypeKey, static_cast(TYPE_APP)); EXPECT_EQ(1, model->item_count()); app_widget.reset(); // Each ShelfItem is removed when the associated window is destroyed. dialog_widget.reset(); EXPECT_EQ(0, 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 widget = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); aura::Window* window = widget->GetNativeWindow(); ShelfID id = CreateShelfItem(window); EXPECT_EQ(1, model->item_count()); ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); gfx::Image default_image = rb.GetImageNamed(IDR_DEFAULT_FAVICON_32); EXPECT_TRUE(model->items()[0].image.BackedBySameObjectAs( default_image.AsImageSkia())); // Setting a window icon should update the item icon. gfx::ImageSkia red = CreateImageSkiaIcon(SK_ColorRED); window->SetProperty(aura::client::kWindowIconKey, std::move(red)); EXPECT_EQ(SK_ColorRED, model->items()[0].image.bitmap()->getColor(0, 0)); // Setting an app icon should override the window icon. gfx::ImageSkia blue = CreateImageSkiaIcon(SK_ColorBLUE); window->SetProperty(aura::client::kAppIconKey, std::move(blue)); EXPECT_EQ(SK_ColorBLUE, model->items()[0].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()[0].image.bitmap()->getColor(0, 0)); } TEST_F(ShelfWindowWatcherTest, DontCreateShelfEntriesForChildWindows) { ShelfModel* model = ShelfModel::Get(); std::unique_ptr window = std::make_unique(nullptr, aura::client::WINDOW_TYPE_NORMAL); window->Init(ui::LAYER_NOT_DRAWN); window->SetProperty(kShelfIDKey, ShelfID("a").Serialize()); window->SetProperty(kShelfItemTypeKey, static_cast(TYPE_DIALOG)); Shell::GetPrimaryRootWindow() ->GetChildById(desks_util::GetActiveDeskContainerId()) ->AddChild(window.get()); window->Show(); EXPECT_EQ(1, model->item_count()); std::unique_ptr child = std::make_unique(nullptr, aura::client::WINDOW_TYPE_NORMAL); child->Init(ui::LAYER_NOT_DRAWN); child->SetProperty(kShelfIDKey, ShelfID("b").Serialize()); child->SetProperty(kShelfItemTypeKey, static_cast(TYPE_DIALOG)); window->AddChild(child.get()); child->Show(); // There should not be a new shelf item for |child|. EXPECT_EQ(1, model->item_count()); child.reset(); EXPECT_EQ(1, model->item_count()); window.reset(); EXPECT_EQ(0, model->item_count()); } TEST_F(ShelfWindowWatcherTest, CreateShelfEntriesForTransientWindows) { ShelfModel* model = ShelfModel::Get(); std::unique_ptr window = std::make_unique(nullptr, aura::client::WINDOW_TYPE_NORMAL); window->Init(ui::LAYER_NOT_DRAWN); window->SetProperty(kShelfIDKey, ShelfID("a").Serialize()); window->SetProperty(kShelfItemTypeKey, static_cast(TYPE_DIALOG)); Shell::GetPrimaryRootWindow() ->GetChildById(desks_util::GetActiveDeskContainerId()) ->AddChild(window.get()); window->Show(); EXPECT_EQ(1, model->item_count()); std::unique_ptr transient = std::make_unique(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(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(2, model->item_count()); transient.reset(); EXPECT_EQ(1, model->item_count()); window.reset(); EXPECT_EQ(0, 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()); EXPECT_EQ(0, model->item_count()); // Construct a window that should get a shelf item once the session starts. std::unique_ptr widget = CreateTestWidget( nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect()); CreateShelfItem(widget->GetNativeWindow()); EXPECT_EQ(0, model->item_count()); // Start the test user session; ShelfWindowWatcher will find the open window. CreateUserSessions(1); EXPECT_EQ(1, model->item_count()); } } // namespace } // namespace ash