1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
11 #include "ash/display/mouse_cursor_event_filter.h"
12 #include "ash/drag_drop/drag_drop_controller.h"
13 #include "ash/drag_drop/drag_drop_controller_test_api.h"
14 #include "ash/keyboard/ui/keyboard_ui_controller.h"
15 #include "ash/keyboard/ui/keyboard_util.h"
16 #include "ash/public/cpp/ash_prefs.h"
17 #include "ash/public/cpp/keyboard/keyboard_switches.h"
18 #include "ash/public/cpp/shell_window_ids.h"
19 #include "ash/public/cpp/test/shell_test_api.h"
20 #include "ash/root_window_controller.h"
21 #include "ash/session/session_controller_impl.h"
22 #include "ash/session/test_session_controller_client.h"
23 #include "ash/shelf/home_button.h"
24 #include "ash/shelf/shelf.h"
25 #include "ash/shelf/shelf_layout_manager.h"
26 #include "ash/shelf/shelf_navigation_widget.h"
27 #include "ash/shelf/shelf_widget.h"
28 #include "ash/system/status_area_widget.h"
29 #include "ash/test/ash_test_base.h"
30 #include "ash/test/ash_test_helper.h"
31 #include "ash/test/test_widget_builder.h"
32 #include "ash/test_shell_delegate.h"
33 #include "ash/wallpaper/wallpaper_widget_controller.h"
34 #include "ash/wm/desks/desks_util.h"
35 #include "ash/wm/overview/overview_controller.h"
36 #include "base/command_line.h"
37 #include "base/containers/flat_set.h"
38 #include "base/ranges/algorithm.h"
39 #include "base/strings/utf_string_conversions.h"
40 #include "base/threading/thread_task_runner_handle.h"
41 #include "components/account_id/account_id.h"
42 #include "ui/aura/env.h"
43 #include "ui/aura/window.h"
44 #include "ui/aura/window_event_dispatcher.h"
45 #include "ui/base/models/simple_menu_model.h"
46 #include "ui/display/scoped_display_for_new_windows.h"
47 #include "ui/events/test/event_generator.h"
48 #include "ui/events/test/events_test_utils.h"
49 #include "ui/events/test/test_event_handler.h"
50 #include "ui/gfx/geometry/size.h"
51 #include "ui/views/controls/menu/menu_controller.h"
52 #include "ui/views/controls/menu/menu_runner.h"
53 #include "ui/views/widget/widget.h"
54 #include "ui/views/widget/widget_delegate.h"
55 #include "ui/views/window/dialog_delegate.h"
57 using aura::RootWindow;
63 aura::Window* GetActiveDeskContainer() {
64 return Shell::GetContainer(Shell::GetPrimaryRootWindow(),
65 desks_util::GetActiveDeskContainerId());
68 aura::Window* GetAlwaysOnTopContainer() {
69 return Shell::GetContainer(Shell::GetPrimaryRootWindow(),
70 kShellWindowId_AlwaysOnTopContainer);
73 // Expect ALL the containers!
74 void ExpectAllContainers() {
75 aura::Window* root_window = Shell::GetPrimaryRootWindow();
77 // Validate no duplicate container IDs.
78 base::flat_set<int> container_ids;
79 std::queue<aura::Window*> window_queue;
80 window_queue.push(root_window);
81 while (!window_queue.empty()) {
82 aura::Window* current_window = window_queue.front();
84 for (aura::Window* child : current_window->children())
85 window_queue.push(child);
87 const int id = current_window->GetId();
89 // Skip windows with no IDs.
90 if (id == aura::Window::kInitialId)
93 EXPECT_TRUE(container_ids.insert(id).second)
94 << "Found duplicate ID: " << id
95 << " at window: " << current_window->GetName();
99 Shell::GetContainer(root_window, kShellWindowId_WallpaperContainer));
101 for (int desk_id : desks_util::GetDesksContainersIds())
102 EXPECT_TRUE(Shell::GetContainer(root_window, desk_id));
105 Shell::GetContainer(root_window, kShellWindowId_AlwaysOnTopContainer));
106 EXPECT_TRUE(Shell::GetContainer(root_window, kShellWindowId_ShelfContainer));
108 Shell::GetContainer(root_window, kShellWindowId_SystemModalContainer));
109 EXPECT_TRUE(Shell::GetContainer(root_window,
110 kShellWindowId_LockScreenWallpaperContainer));
112 Shell::GetContainer(root_window, kShellWindowId_LockScreenContainer));
113 EXPECT_TRUE(Shell::GetContainer(root_window,
114 kShellWindowId_LockSystemModalContainer));
115 EXPECT_TRUE(Shell::GetContainer(root_window, kShellWindowId_MenuContainer));
116 EXPECT_TRUE(Shell::GetContainer(root_window,
117 kShellWindowId_DragImageAndTooltipContainer));
119 Shell::GetContainer(root_window, kShellWindowId_SettingBubbleContainer));
121 Shell::GetContainer(root_window, kShellWindowId_OverlayContainer));
122 EXPECT_TRUE(Shell::GetContainer(root_window,
123 kShellWindowId_ImeWindowParentContainer));
124 EXPECT_TRUE(Shell::GetContainer(root_window,
125 kShellWindowId_VirtualKeyboardContainer));
127 Shell::GetContainer(root_window, kShellWindowId_MouseCursorContainer));
129 // Phantom window is not a container.
130 EXPECT_EQ(0u, container_ids.count(kShellWindowId_PhantomWindow));
131 EXPECT_FALSE(Shell::GetContainer(root_window, kShellWindowId_PhantomWindow));
134 std::unique_ptr<views::WidgetDelegateView> CreateModalWidgetDelegate() {
135 auto delegate = std::make_unique<views::WidgetDelegateView>();
136 delegate->SetCanResize(true);
137 delegate->SetModalType(ui::MODAL_TYPE_SYSTEM);
138 delegate->SetOwnedByWidget(true);
139 delegate->SetTitle(u"Modal Window");
143 class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
145 SimpleMenuDelegate() = default;
147 SimpleMenuDelegate(const SimpleMenuDelegate&) = delete;
148 SimpleMenuDelegate& operator=(const SimpleMenuDelegate&) = delete;
150 ~SimpleMenuDelegate() override = default;
152 bool IsCommandIdChecked(int command_id) const override { return false; }
154 bool IsCommandIdEnabled(int command_id) const override { return true; }
156 void ExecuteCommand(int command_id, int event_flags) override {}
161 class ShellTest : public AshTestBase {
163 void TestCreateWindow(views::Widget::InitParams::Type type,
165 aura::Window* expected_container) {
166 TestWidgetBuilder builder;
168 builder.SetZOrderLevel(ui::ZOrderLevel::kFloatingWindow);
169 views::Widget* widget =
170 builder.SetWidgetType(type).BuildOwnedByNativeWidget();
173 expected_container->Contains(widget->GetNativeWindow()->parent()))
174 << "TestCreateWindow: type=" << type
175 << ", always_on_top=" << always_on_top;
180 void LockScreenAndVerifyMenuClosed() {
181 // Verify a menu is open before locking.
182 views::MenuController* menu_controller =
183 views::MenuController::GetActiveInstance();
184 DCHECK(menu_controller);
185 EXPECT_EQ(views::MenuController::ExitType::kNone,
186 menu_controller->exit_type());
188 // Create a LockScreen window.
189 views::Widget* lock_widget =
191 .SetWidgetType(views::Widget::InitParams::TYPE_WINDOW)
193 .BuildOwnedByNativeWidget();
194 Shell::GetContainer(Shell::GetPrimaryRootWindow(),
195 kShellWindowId_LockScreenContainer)
196 ->AddChild(lock_widget->GetNativeView());
199 // Simulate real screen locker to change session state to LOCKED
201 GetSessionControllerClient()->LockScreen();
203 SessionControllerImpl* controller = Shell::Get()->session_controller();
204 EXPECT_TRUE(controller->IsScreenLocked());
205 EXPECT_TRUE(lock_widget->GetNativeView()->HasFocus());
207 // Verify menu is closed.
208 EXPECT_EQ(nullptr, views::MenuController::GetActiveInstance());
209 lock_widget->Close();
210 GetSessionControllerClient()->UnlockScreen();
214 TEST_F(ShellTest, CreateWindow) {
215 // Normal window should be created in default container.
216 TestCreateWindow(views::Widget::InitParams::TYPE_WINDOW,
217 false, // always_on_top
218 GetActiveDeskContainer());
219 TestCreateWindow(views::Widget::InitParams::TYPE_POPUP,
220 false, // always_on_top
221 GetActiveDeskContainer());
223 // Always-on-top window and popup are created in always-on-top container.
224 TestCreateWindow(views::Widget::InitParams::TYPE_WINDOW,
225 true, // always_on_top
226 GetAlwaysOnTopContainer());
227 TestCreateWindow(views::Widget::InitParams::TYPE_POPUP,
228 true, // always_on_top
229 GetAlwaysOnTopContainer());
232 // Verifies that a window with a preferred size is created centered on the
233 // default display for new windows. Mojo apps like shortcut_viewer rely on this
235 TEST_F(ShellTest, CreateWindowWithPreferredSize) {
236 UpdateDisplay("1024x768,800x600");
238 aura::Window* secondary_root = Shell::GetAllRootWindows()[1];
239 display::ScopedDisplayForNewWindows scoped_display(secondary_root);
241 views::Widget::InitParams params;
242 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
243 // Don't specify bounds, parent or context.
245 auto delegate = std::make_unique<views::WidgetDelegateView>();
246 delegate->SetPreferredSize(gfx::Size(400, 300));
247 params.delegate = delegate.release();
249 views::Widget widget;
250 params.context = GetContext();
251 widget.Init(std::move(params));
253 // Widget is centered on secondary display.
254 EXPECT_EQ(secondary_root, widget.GetNativeWindow()->GetRootWindow());
255 EXPECT_EQ(GetSecondaryDisplay().work_area().CenterPoint(),
256 widget.GetRestoredBounds().CenterPoint());
259 TEST_F(ShellTest, ChangeZOrderLevel) {
260 // Creates a normal window.
261 views::Widget* widget = TestWidgetBuilder().BuildOwnedByNativeWidget();
263 // It should be in the active desk container.
265 GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
267 // Set the z-order to float.
268 widget->SetZOrderLevel(ui::ZOrderLevel::kFloatingWindow);
269 // And it should in always on top container now.
270 EXPECT_EQ(GetAlwaysOnTopContainer(), widget->GetNativeWindow()->parent());
272 // Put the z-order back to normal.
273 widget->SetZOrderLevel(ui::ZOrderLevel::kNormal);
274 // It should go back to the active desk container.
276 GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
278 // Set the z-order again to the normal value.
279 widget->SetZOrderLevel(ui::ZOrderLevel::kNormal);
280 // Should have no effect and we are still in the the active desk container.
282 GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
287 TEST_F(ShellTest, CreateModalWindow) {
288 // Create a normal window.
289 views::Widget* widget = TestWidgetBuilder().BuildOwnedByNativeWidget();
291 // It should be in the active desk container.
293 GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
295 // Create a modal window.
296 views::Widget* modal_widget = views::Widget::CreateWindowWithParent(
297 CreateModalWidgetDelegate(), widget->GetNativeView());
298 modal_widget->Show();
300 // It should be in modal container.
301 aura::Window* modal_container = Shell::GetContainer(
302 Shell::GetPrimaryRootWindow(), kShellWindowId_SystemModalContainer);
303 EXPECT_EQ(modal_container, modal_widget->GetNativeWindow()->parent());
305 modal_widget->Close();
309 TEST_F(ShellTest, CreateLockScreenModalWindow) {
310 // Create a normal window.
311 views::Widget* widget = TestWidgetBuilder().BuildOwnedByNativeWidget();
312 EXPECT_TRUE(widget->GetNativeView()->HasFocus());
314 // It should be in the active desk container.
316 GetActiveDeskContainer()->Contains(widget->GetNativeWindow()->parent()));
318 GetSessionControllerClient()->LockScreen();
319 // Create a LockScreen window.
320 views::Widget* lock_widget =
321 TestWidgetBuilder().SetShow(false).BuildOwnedByNativeWidget();
322 Shell::GetContainer(Shell::GetPrimaryRootWindow(),
323 kShellWindowId_LockScreenContainer)
324 ->AddChild(lock_widget->GetNativeView());
326 EXPECT_TRUE(lock_widget->GetNativeView()->HasFocus());
328 // It should be in LockScreen container.
329 aura::Window* lock_screen = Shell::GetContainer(
330 Shell::GetPrimaryRootWindow(), kShellWindowId_LockScreenContainer);
331 EXPECT_EQ(lock_screen, lock_widget->GetNativeWindow()->parent());
333 // Create a modal window with a lock window as parent.
334 views::Widget* lock_modal_widget = views::Widget::CreateWindowWithParent(
335 CreateModalWidgetDelegate(), lock_widget->GetNativeView());
336 lock_modal_widget->Show();
337 EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
339 // It should be in LockScreen modal container.
340 aura::Window* lock_modal_container =
341 Shell::GetContainer(Shell::GetPrimaryRootWindow(),
342 kShellWindowId_LockSystemModalContainer);
343 EXPECT_EQ(lock_modal_container,
344 lock_modal_widget->GetNativeWindow()->parent());
346 // Create a modal window with a normal window as parent.
347 views::Widget* modal_widget = views::Widget::CreateWindowWithParent(
348 CreateModalWidgetDelegate(), widget->GetNativeView());
349 modal_widget->Show();
350 // Window on lock screen shouldn't lost focus.
351 EXPECT_FALSE(modal_widget->GetNativeView()->HasFocus());
352 EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
354 // It should be in non-LockScreen modal container.
355 aura::Window* modal_container = Shell::GetContainer(
356 Shell::GetPrimaryRootWindow(), kShellWindowId_SystemModalContainer);
357 EXPECT_EQ(modal_container, modal_widget->GetNativeWindow()->parent());
359 // Modal widget without parent, caused crash see crbug.com/226141
360 views::Widget* modal_dialog = views::DialogDelegate::CreateDialogWidget(
361 CreateModalWidgetDelegate(), GetContext(), nullptr);
363 modal_dialog->Show();
364 EXPECT_FALSE(modal_dialog->GetNativeView()->HasFocus());
365 EXPECT_TRUE(lock_modal_widget->GetNativeView()->HasFocus());
367 modal_dialog->Close();
368 modal_widget->Close();
369 modal_widget->Close();
370 lock_modal_widget->Close();
371 lock_widget->Close();
375 TEST_F(ShellTest, IsScreenLocked) {
376 SessionControllerImpl* controller = Shell::Get()->session_controller();
377 GetSessionControllerClient()->LockScreen();
378 EXPECT_TRUE(controller->IsScreenLocked());
379 GetSessionControllerClient()->UnlockScreen();
380 EXPECT_FALSE(controller->IsScreenLocked());
383 TEST_F(ShellTest, LockScreenClosesActiveMenu) {
384 SimpleMenuDelegate menu_delegate;
385 std::unique_ptr<ui::SimpleMenuModel> menu_model(
386 new ui::SimpleMenuModel(&menu_delegate));
387 menu_model->AddItem(0, u"Menu item");
388 views::Widget* widget = Shell::GetPrimaryRootWindowController()
389 ->wallpaper_widget_controller()
391 std::unique_ptr<views::MenuRunner> menu_runner(
392 new views::MenuRunner(menu_model.get(), views::MenuRunner::CONTEXT_MENU));
394 menu_runner->RunMenuAt(widget, nullptr, gfx::Rect(),
395 views::MenuAnchorPosition::kTopLeft,
396 ui::MENU_SOURCE_MOUSE);
397 LockScreenAndVerifyMenuClosed();
400 TEST_F(ShellTest, ManagedWindowModeBasics) {
401 // We start with the usual window containers.
402 ExpectAllContainers();
404 ShelfWidget* shelf_widget = GetPrimaryShelf()->shelf_widget();
405 EXPECT_TRUE(shelf_widget->IsVisible());
406 // Shelf is at bottom-left of screen.
407 EXPECT_EQ(0, shelf_widget->GetWindowBoundsInScreen().x());
409 Shell::GetPrimaryRootWindow()->GetHost()->GetBoundsInPixels().height(),
410 shelf_widget->GetWindowBoundsInScreen().bottom());
411 // We have a wallpaper but not a bare layer.
412 // TODO (antrim): enable once we find out why it fails component build.
413 // WallpaperWidgetController* wallpaper =
414 // Shell::GetPrimaryRootWindow()->
415 // GetProperty(kWindowDesktopComponent);
416 // EXPECT_TRUE(wallpaper);
417 // EXPECT_TRUE(wallpaper->widget());
418 // EXPECT_FALSE(wallpaper->layer());
420 // Create a normal window. It is not maximized.
421 views::Widget* widget = TestWidgetBuilder()
422 .SetBounds(gfx::Rect(11, 22, 300, 400))
423 .BuildOwnedByNativeWidget();
424 EXPECT_FALSE(widget->IsMaximized());
430 // Tests that the cursor-filter is ahead of the drag-drop controller in the
432 TEST_F(ShellTest, TestPreTargetHandlerOrder) {
433 Shell* shell = Shell::Get();
434 ui::EventTargetTestApi test_api(shell);
435 ShellTestApi shell_test_api;
437 ui::EventHandlerList handlers = test_api.GetPreTargetHandlers();
438 ui::EventHandlerList::const_iterator cursor_filter =
439 base::ranges::find(handlers, shell->mouse_cursor_filter());
440 ui::EventHandlerList::const_iterator drag_drop =
441 base::ranges::find(handlers, shell_test_api.drag_drop_controller());
442 EXPECT_NE(handlers.end(), cursor_filter);
443 EXPECT_NE(handlers.end(), drag_drop);
444 EXPECT_GT(drag_drop, cursor_filter);
447 // Verifies an EventHandler added to Env gets notified from EventGenerator.
448 TEST_F(ShellTest, EnvPreTargetHandler) {
449 ui::test::TestEventHandler event_handler;
450 aura::Env::GetInstance()->AddPreTargetHandler(&event_handler);
451 ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
452 generator.MoveMouseBy(1, 1);
453 EXPECT_NE(0, event_handler.num_mouse_events());
454 aura::Env::GetInstance()->RemovePreTargetHandler(&event_handler);
457 // Verifies that pressing tab on an empty shell (one with no windows visible)
458 // will put focus on the shelf. This enables keyboard only users to get to the
459 // shelf without knowing the more obscure accelerators. Tab should move focus to
460 // the home button, shift + tab to the status widget. From there, normal shelf
461 // tab behaviour takes over, and the shell no longer catches that event.
462 TEST_F(ShellTest, NoWindowTabFocus) {
463 ExpectAllContainers();
465 StatusAreaWidget* status_area_widget =
466 GetPrimaryShelf()->status_area_widget();
467 ShelfNavigationWidget* home_button = GetPrimaryShelf()->navigation_widget();
469 // Create a normal window. It is not maximized.
470 auto widget = CreateTestWidget();
472 // Hit tab with window open, and expect that focus is not on the navigation
473 // widget or status widget.
474 PressAndReleaseKey(ui::VKEY_TAB);
475 EXPECT_FALSE(home_button->GetNativeView()->HasFocus());
476 EXPECT_FALSE(status_area_widget->GetNativeView()->HasFocus());
478 // Minimize the window, hit tab and expect that focus is on the launcher.
480 PressAndReleaseKey(ui::VKEY_TAB);
481 EXPECT_TRUE(home_button->GetNativeView()->HasFocus());
483 // Show (to steal focus back before continuing testing) and close the window.
486 EXPECT_FALSE(home_button->GetNativeView()->HasFocus());
488 // Confirm that pressing tab when overview mode is open does not go to home
489 // button. Tab should be handled by overview mode and not hit the shell event
492 PressAndReleaseKey(ui::VKEY_TAB);
493 EXPECT_FALSE(home_button->GetNativeView()->HasFocus());
496 // Hit shift tab and expect that focus is on status widget.
497 PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
498 EXPECT_TRUE(status_area_widget->GetNativeView()->HasFocus());
501 // This verifies WindowObservers are removed when a window is destroyed after
502 // the Shell is destroyed. This scenario (aura::Windows being deleted after the
503 // Shell) occurs if someone is holding a reference to an unparented Window, as
504 // is the case with a RenderWidgetHostViewAura that isn't on screen. As long as
505 // everything is ok, we won't crash. If there is a bug, window's destructor will
506 // notify some deleted object (say VideoDetector or ActivationController) and
508 class ShellTest2 : public AshTestBase {
510 ShellTest2() = default;
512 ShellTest2(const ShellTest2&) = delete;
513 ShellTest2& operator=(const ShellTest2&) = delete;
515 ~ShellTest2() override = default;
518 std::unique_ptr<aura::Window> window_;
521 TEST_F(ShellTest2, DontCrashWhenWindowDeleted) {
522 window_ = std::make_unique<aura::Window>(nullptr,
523 aura::client::WINDOW_TYPE_UNKNOWN);
524 window_->Init(ui::LAYER_NOT_DRAWN);
527 using ShellLoginTest = NoSessionAshTestBase;
529 TEST_F(ShellLoginTest, DragAndDropDisabledBeforeLogin) {
530 DragDropController* drag_drop_controller =
531 ShellTestApi().drag_drop_controller();
532 DragDropControllerTestApi drag_drop_controller_test_api(drag_drop_controller);
533 EXPECT_FALSE(drag_drop_controller_test_api.enabled());
535 SimulateUserLogin("user1@test.com");
536 EXPECT_TRUE(drag_drop_controller_test_api.enabled());
539 using NoDuplicateShellContainerIdsTest = AshTestBase;
541 TEST_F(NoDuplicateShellContainerIdsTest, ValidateContainersIds) {
542 ExpectAllContainers();