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.
5 #include "ash/focus_cycler.h"
9 #include "ash/shelf/shelf.h"
10 #include "ash/shelf/shelf_focus_cycler.h"
11 #include "ash/shelf/shelf_navigation_widget.h"
12 #include "ash/shelf/shelf_widget.h"
13 #include "ash/shell.h"
14 #include "ash/system/status_area_widget.h"
15 #include "ash/system/status_area_widget_delegate.h"
16 #include "ash/test/ash_test_base.h"
17 #include "ash/wm/window_util.h"
18 #include "base/memory/raw_ptr.h"
19 #include "base/ranges/algorithm.h"
20 #include "ui/aura/test/test_windows.h"
21 #include "ui/aura/window.h"
22 #include "ui/aura/window_event_dispatcher.h"
23 #include "ui/events/test/event_generator.h"
24 #include "ui/views/accessible_pane_view.h"
25 #include "ui/views/controls/button/menu_button.h"
26 #include "ui/views/widget/widget.h"
34 StatusAreaWidgetDelegate* GetStatusAreaWidgetDelegate(views::Widget* widget) {
35 return static_cast<StatusAreaWidgetDelegate*>(widget->GetContentsView());
38 class PanedWidgetDelegate : public views::WidgetDelegate {
40 PanedWidgetDelegate(views::Widget* widget) : widget_(widget) {}
42 void SetAccessiblePanes(const std::vector<views::View*>& panes) {
43 accessible_panes_ = panes;
46 // views::WidgetDelegate:
47 void GetAccessiblePanes(std::vector<views::View*>* panes) override {
48 base::ranges::copy(accessible_panes_, std::back_inserter(*panes));
50 views::Widget* GetWidget() override { return widget_; }
51 const views::Widget* GetWidget() const override { return widget_; }
54 raw_ptr<views::Widget, DanglingUntriaged | ExperimentalAsh> widget_;
55 std::vector<views::View*> accessible_panes_;
60 class FocusCyclerTest : public AshTestBase {
62 FocusCyclerTest() = default;
64 FocusCyclerTest(const FocusCyclerTest&) = delete;
65 FocusCyclerTest& operator=(const FocusCyclerTest&) = delete;
67 void SetUp() override {
70 focus_cycler_ = std::make_unique<FocusCycler>();
73 void TearDown() override {
74 GetStatusAreaWidgetDelegate(GetPrimaryStatusAreaWidget())
75 ->SetFocusCyclerForTesting(nullptr);
77 GetPrimaryShelf()->shelf_widget()->SetFocusCycler(nullptr);
79 focus_cycler_.reset();
81 AshTestBase::TearDown();
85 // Setup the system tray focus cycler.
86 void SetUpTrayFocusCycle() {
87 views::Widget* system_tray_widget = GetPrimaryStatusAreaWidget();
88 ASSERT_TRUE(system_tray_widget);
89 focus_cycler_->AddWidget(system_tray_widget);
90 GetStatusAreaWidgetDelegate(system_tray_widget)
91 ->SetFocusCyclerForTesting(focus_cycler());
94 views::Widget* GetPrimaryStatusAreaWidget() {
95 return GetPrimaryShelf()->GetStatusAreaWidget();
98 FocusCycler* focus_cycler() { return focus_cycler_.get(); }
100 void InstallFocusCycleOnShelf() {
102 GetPrimaryShelf()->hotseat_widget()->SetFocusCycler(focus_cycler());
106 std::unique_ptr<FocusCycler> focus_cycler_;
109 TEST_F(FocusCyclerTest, CycleFocusBrowserOnly) {
110 // Create a single test window.
111 std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
112 wm::ActivateWindow(window0.get());
113 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
116 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
117 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
120 TEST_F(FocusCyclerTest, CycleFocusForward) {
121 SetUpTrayFocusCycle();
123 InstallFocusCycleOnShelf();
125 // Create a single test window.
126 std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
127 wm::ActivateWindow(window0.get());
128 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
130 // Cycle focus to the status area.
131 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
132 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
134 // Cycle focus to the shelf.
135 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
136 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
138 // Cycle focus to the browser.
139 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
140 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
143 TEST_F(FocusCyclerTest, CycleFocusBackward) {
144 SetUpTrayFocusCycle();
146 InstallFocusCycleOnShelf();
148 // Create a single test window.
149 std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
150 wm::ActivateWindow(window0.get());
151 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
153 // Cycle focus to the shelf.
154 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
155 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
157 // Cycle focus to the status area.
158 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
159 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
161 // Cycle focus to the browser.
162 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
163 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
166 TEST_F(FocusCyclerTest, CycleFocusForwardBackward) {
167 SetUpTrayFocusCycle();
169 InstallFocusCycleOnShelf();
171 // Create a single test window.
172 std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
173 wm::ActivateWindow(window0.get());
174 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
176 // Cycle focus to the shelf.
177 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
178 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
180 // Cycle focus to the status area.
181 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
182 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
184 // Cycle focus to the browser.
185 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
186 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
188 // Cycle focus to the status area.
189 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
190 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
192 // Cycle focus to the shelf.
193 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
194 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
196 // Cycle focus to the browser.
197 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
198 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
201 TEST_F(FocusCyclerTest, CycleFocusNoBrowser) {
202 SetUpTrayFocusCycle();
204 InstallFocusCycleOnShelf();
206 // Add the shelf and focus it.
207 focus_cycler()->FocusWidget(GetPrimaryShelf()->hotseat_widget());
209 // Cycle focus to the status area.
210 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
211 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
213 // Cycle focus to the shelf.
214 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
215 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
217 // Cycle focus to the status area.
218 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
219 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
221 // Cycle focus to the shelf.
222 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
223 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
225 // Cycle focus to the status area.
226 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
227 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
230 // Tests that focus cycles from the active browser to the status area and back.
231 TEST_F(FocusCyclerTest, Shelf_CycleFocusForward) {
232 SetUpTrayFocusCycle();
233 InstallFocusCycleOnShelf();
234 GetPrimaryShelf()->hotseat_widget()->Hide();
236 // Create two test windows.
237 std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
238 std::unique_ptr<Window> window1(CreateTestWindowInShellWithId(1));
239 wm::ActivateWindow(window1.get());
240 wm::ActivateWindow(window0.get());
241 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
243 // Cycle focus to the status area.
244 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
245 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
247 // Cycle focus to the browser.
248 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
249 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
251 // Cycle focus to the status area.
252 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
253 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
256 TEST_F(FocusCyclerTest, Shelf_CycleFocusBackwardInvisible) {
257 SetUpTrayFocusCycle();
258 InstallFocusCycleOnShelf();
259 GetPrimaryShelf()->hotseat_widget()->Hide();
261 // Create a single test window.
262 std::unique_ptr<Window> window0(CreateTestWindowInShellWithId(0));
263 wm::ActivateWindow(window0.get());
264 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
266 // Cycle focus to the status area.
267 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
268 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
270 // Cycle focus to the browser.
271 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
272 EXPECT_TRUE(wm::IsActiveWindow(window0.get()));
275 TEST_F(FocusCyclerTest, CycleFocusThroughWindowWithPanes) {
276 SetUpTrayFocusCycle();
278 InstallFocusCycleOnShelf();
280 std::unique_ptr<PanedWidgetDelegate> test_widget_delegate;
281 std::unique_ptr<views::Widget> browser_widget(new views::Widget);
282 test_widget_delegate =
283 std::make_unique<PanedWidgetDelegate>(browser_widget.get());
284 views::Widget::InitParams widget_params(
285 views::Widget::InitParams::TYPE_WINDOW);
286 widget_params.delegate = test_widget_delegate.get();
287 widget_params.ownership =
288 views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
289 widget_params.context = GetContext();
290 browser_widget->Init(std::move(widget_params));
291 browser_widget->Show();
293 aura::Window* browser_window = browser_widget->GetNativeView();
295 views::View* root_view = browser_widget->GetRootView();
297 // pane1 contains view1 and view2, pane2 contains view3 and view4.
298 views::AccessiblePaneView* pane1 = new views::AccessiblePaneView();
299 root_view->AddChildView(pane1);
301 views::View* view1 = new views::View;
302 view1->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
303 pane1->AddChildView(view1);
305 views::View* view2 = new views::View;
306 view2->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
307 pane1->AddChildView(view2);
309 views::AccessiblePaneView* pane2 = new views::AccessiblePaneView();
310 root_view->AddChildView(pane2);
312 views::View* view3 = new views::View;
313 view3->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
314 pane2->AddChildView(view3);
316 views::View* view4 = new views::View;
317 view4->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
318 pane2->AddChildView(view4);
320 std::vector<views::View*> panes;
321 panes.push_back(pane1);
322 panes.push_back(pane2);
324 test_widget_delegate->SetAccessiblePanes(panes);
326 views::FocusManager* focus_manager = browser_widget->GetFocusManager();
328 // Cycle focus to the status area.
329 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
330 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
332 // Cycle focus to the shelf.
333 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
334 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
336 // Cycle focus to the first pane in the browser.
337 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
338 EXPECT_TRUE(wm::IsActiveWindow(browser_window));
339 EXPECT_EQ(focus_manager->GetFocusedView(), view1);
341 // Cycle focus to the second pane in the browser.
342 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
343 EXPECT_TRUE(wm::IsActiveWindow(browser_window));
344 EXPECT_EQ(focus_manager->GetFocusedView(), view3);
346 // Cycle focus back to the status area.
347 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
348 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
350 // Reverse direction - back to the second pane in the browser.
351 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
352 EXPECT_TRUE(wm::IsActiveWindow(browser_window));
353 EXPECT_EQ(focus_manager->GetFocusedView(), view3);
355 // Back to the first pane in the browser.
356 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
357 EXPECT_TRUE(wm::IsActiveWindow(browser_window));
358 EXPECT_EQ(focus_manager->GetFocusedView(), view1);
360 // Back to the shelf.
361 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
362 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
364 // Back to the status area.
365 focus_cycler()->RotateFocus(FocusCycler::BACKWARD);
366 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
368 // Pressing "Escape" while on the status area should
369 // deactivate it, and activate the browser window.
370 PressAndReleaseKey(ui::VKEY_ESCAPE);
371 EXPECT_TRUE(wm::IsActiveWindow(browser_window));
372 EXPECT_EQ(focus_manager->GetFocusedView(), view1);
374 // Similarly, pressing "Escape" while on the shelf should do the same thing.
375 // Focus the navigation widget directly because the shelf has no apps here.
376 GetPrimaryShelf()->shelf_focus_cycler()->FocusNavigation(false /* last */);
377 EXPECT_TRUE(GetPrimaryShelf()->navigation_widget()->IsActive());
378 PressAndReleaseKey(ui::VKEY_ESCAPE);
379 EXPECT_TRUE(wm::IsActiveWindow(browser_window));
380 EXPECT_EQ(focus_manager->GetFocusedView(), view1);
383 TEST_F(FocusCyclerTest, CycleFocusThroughWindowWithPanes_MoveOntoNext) {
384 SetUpTrayFocusCycle();
386 InstallFocusCycleOnShelf();
388 std::unique_ptr<views::Widget> browser_widget =
389 std::make_unique<views::Widget>();
390 std::unique_ptr<PanedWidgetDelegate> test_widget_delegate =
391 std::make_unique<PanedWidgetDelegate>(browser_widget.get());
392 views::Widget::InitParams widget_params(
393 views::Widget::InitParams::TYPE_WINDOW);
394 widget_params.delegate = test_widget_delegate.get();
395 widget_params.ownership =
396 views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
398 widget_params.context = GetContext();
399 browser_widget->Init(std::move(widget_params));
400 browser_widget->Show();
402 aura::Window* browser_window = browser_widget->GetNativeView();
404 views::View* root_view = browser_widget->GetRootView();
406 // pane1 contains view1 and view2, pane2 contains view3 and view4.
407 views::AccessiblePaneView* pane1 = new views::AccessiblePaneView();
408 root_view->AddChildView(pane1);
410 views::View* view1 = new views::View();
411 view1->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
412 pane1->AddChildView(view1);
414 views::AccessiblePaneView* pane2 = new views::AccessiblePaneView();
415 root_view->AddChildView(pane2);
417 views::View* view2 = new views::View();
418 view2->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
419 pane2->AddChildView(view2);
421 test_widget_delegate->SetAccessiblePanes({pane1, pane2});
423 views::FocusManager* focus_manager = browser_widget->GetFocusManager();
425 // Cycle focus to the status area.
426 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
427 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
429 // Cycle focus to the shelf.
430 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
431 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
433 // Cycle focus to the first pane in the browser.
434 focus_cycler()->RotateFocus(FocusCycler::FORWARD);
435 EXPECT_TRUE(wm::IsActiveWindow(browser_window));
436 EXPECT_EQ(focus_manager->GetFocusedView(), view1);
438 // Cycle focus back to the status area by asking the focus_cycler to move
439 // onto the next widget. This should skip the next accessible pane.
440 focus_cycler()->RotateFocus(FocusCycler::FORWARD, true);
441 EXPECT_FALSE(wm::IsActiveWindow(browser_window));
442 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
444 // Manually deallocate to ensure delegate outlives widget.
445 browser_widget.release();
446 test_widget_delegate.release();
449 // Test that when the shelf widget & status area widget are removed, they should
450 // also be removed from focus cycler.
451 TEST_F(FocusCyclerTest, RemoveWidgetOnDisplayRemoved) {
452 // Two displays are added, so two shelf widgets and two status area widgets
453 // are added to focus cycler.
454 UpdateDisplay("800x700, 600x500");
455 // Remove one display. Its shelf widget and status area widget should also be
456 // removed from focus cycler.
457 UpdateDisplay("800x700");
459 // Create a single test window.
460 std::unique_ptr<Window> window(CreateTestWindowInShellWithId(0));
461 wm::ActivateWindow(window.get());
462 EXPECT_TRUE(wm::IsActiveWindow(window.get()));
464 // Cycle focus to the navigation widget.
465 Shell::Get()->focus_cycler()->RotateFocus(FocusCycler::FORWARD);
466 EXPECT_TRUE(GetPrimaryShelf()->navigation_widget()->IsActive());
468 // Cycle focus to the hotseat widget.
469 Shell::Get()->focus_cycler()->RotateFocus(FocusCycler::FORWARD);
470 EXPECT_TRUE(GetPrimaryShelf()->hotseat_widget()->IsActive());
472 // Cycle focus to the status area.
473 Shell::Get()->focus_cycler()->RotateFocus(FocusCycler::FORWARD);
474 EXPECT_TRUE(GetPrimaryStatusAreaWidget()->IsActive());
476 // Cycle focus should go back to the browser.
477 Shell::Get()->focus_cycler()->RotateFocus(FocusCycler::FORWARD);
478 EXPECT_TRUE(wm::IsActiveWindow(window.get()));