0

DeskButton: Update toast logic for new desk bar

This CL ensures that the toast displayed when closing a desk with its
windows is closed in chromevox as soon as the user performs a
non-traversal action (i.e. presses anything that is not a tab or arrow
keys) or traverses past the undo button. Previously in this case we
would only close the toast as soon as the user exits overview. However,
the new desk bar appears outside of overview, so this is our new way of
determining that the user has moved on from their closed desk. We also
add a readout that notifies the user that they can also use Ctrl+Z to
undo desk removal. We also add a unit test to verify that this works.

Bug: b/292269270
Change-Id: Id60c36384cfd3575248735d723374cad6c00c648
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4851130
Reviewed-by: Daniel Andersson <dandersson@chromium.org>
Reviewed-by: Yongshun Liu <yongshun@chromium.org>
Commit-Queue: Ben Becker <benbecker@chromium.org>
Reviewed-by: Ahmed Fakhry <afakhry@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1199293}
This commit is contained in:
Ben Becker
2023-09-20 22:57:14 +00:00
committed by Chromium LUCI CQ
parent adcf41568c
commit 94061d6a12
12 changed files with 311 additions and 75 deletions

@ -5703,6 +5703,9 @@ Here are some things you can try to get started.
<message name="IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_REMOVED" desc="A desk is removed in virtual desks.">
Desk <ph name="REMOVED_DESK">$1</ph> removed and merged with Desk <ph name="RECEIVE_DESK">$2</ph>
</message>
<message name="IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_CLOSED_WITH_WINDOWS" desc="The accessibility text read by screen readers when a desk is closed with its windows.">
Desk and windows have been removed. Press Control + Z to undo action.
</message>
<message name="IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_ACTIVATED" desc="A desk in virtual desks is activated.">
Desk <ph name="DESK_TITILE">$1<ex>1</ex></ph> activated
</message>

@ -0,0 +1 @@
e57fd710020b9a9f4c04870514e88b889a10f662

@ -35,12 +35,13 @@ class FakeToastManager : public ash::ToastManager {
const std::string& id) override {
return false;
}
bool IsRunning(const std::string& id) const override { return false; }
bool IsRunning(std::string_view id) const override { return false; }
std::unique_ptr<ash::ScopedToastPause> CreateScopedPause() override {
return nullptr;
}
void Pause() override {}
void Resume() override {}
bool IsHighlighted(std::string_view id) const override { return false; }
void ResetState() {
called_show_ = false;

@ -40,11 +40,16 @@ class ASH_PUBLIC_EXPORT ToastManager {
const std::string& id) = 0;
// Tells if the toast with the provided ID is running.
virtual bool IsRunning(const std::string& id) const = 0;
virtual bool IsRunning(std::string_view id) const = 0;
// Creates a `ScopedToastPause`.
virtual std::unique_ptr<ScopedToastPause> CreateScopedPause() = 0;
// Tells if the toast with the provided ID has a dismiss button that is
// currently being highlighted. Returns false if the toast is not running,
// does not have a dismiss button, or the dismiss button is not highlighted.
virtual bool IsHighlighted(std::string_view id) const = 0;
protected:
ToastManager();
virtual ~ToastManager();

@ -160,9 +160,10 @@ void ToastManagerImpl::Cancel(const std::string& id) {
bool ToastManagerImpl::MaybeToggleA11yHighlightOnActiveToastDismissButton(
const std::string& id) {
DCHECK(IsRunning(id));
for (auto& iter : root_window_to_overlay_) {
if (iter.second && iter.second->MaybeToggleA11yHighlightOnDismissButton())
for (auto& [_, overlay] : root_window_to_overlay_) {
if (overlay && overlay->MaybeToggleA11yHighlightOnDismissButton()) {
return true;
}
}
return false;
@ -171,15 +172,16 @@ bool ToastManagerImpl::MaybeToggleA11yHighlightOnActiveToastDismissButton(
bool ToastManagerImpl::MaybeActivateHighlightedDismissButtonOnActiveToast(
const std::string& id) {
DCHECK(IsRunning(id));
for (auto& iter : root_window_to_overlay_) {
if (iter.second && iter.second->MaybeActivateHighlightedDismissButton())
for (auto& [_, overlay] : root_window_to_overlay_) {
if (overlay && overlay->MaybeActivateHighlightedDismissButton()) {
return true;
}
}
return false;
}
bool ToastManagerImpl::IsRunning(const std::string& id) const {
bool ToastManagerImpl::IsRunning(std::string_view id) const {
return HasActiveToasts() && current_toast_data_ &&
current_toast_data_->id == id;
}
@ -188,6 +190,20 @@ std::unique_ptr<ScopedToastPause> ToastManagerImpl::CreateScopedPause() {
return std::make_unique<ScopedToastPause>();
}
bool ToastManagerImpl::IsHighlighted(std::string_view id) const {
if (!IsRunning(id)) {
return false;
}
for (const auto& [_, overlay] : root_window_to_overlay_) {
if (overlay && overlay->IsDismissButtonHighlighted()) {
return true;
}
}
return false;
}
void ToastManagerImpl::OnClosed() {
const base::TimeDelta user_journey_time =
base::TimeTicks::Now() - current_toast_data_->time_start_showing;
@ -299,15 +315,17 @@ void ToastManagerImpl::CreateToastOverlayForRoot(aura::Window* root_window) {
}
void ToastManagerImpl::CloseAllToastsWithAnimation() {
for (auto& iter : root_window_to_overlay_) {
if (iter.second)
iter.second->Show(false);
for (auto& [_, overlay] : root_window_to_overlay_) {
if (overlay) {
overlay->Show(false);
}
}
}
void ToastManagerImpl::CloseAllToastsWithoutAnimation() {
for (auto& iter : root_window_to_overlay_)
iter.second.reset();
for (auto& [_, overlay] : root_window_to_overlay_) {
overlay.reset();
}
// `OnClosed` (the other place where we stop the
// `current_toast_expiration_timer_`) is only called when the toast is being
@ -317,9 +335,10 @@ void ToastManagerImpl::CloseAllToastsWithoutAnimation() {
}
bool ToastManagerImpl::HasActiveToasts() const {
for (auto& iter : root_window_to_overlay_) {
if (iter.second)
for (const auto& [_, overlay] : root_window_to_overlay_) {
if (overlay) {
return true;
}
}
return false;

@ -54,8 +54,9 @@ class ASH_EXPORT ToastManagerImpl : public ToastManager,
const std::string& id) override;
bool MaybeActivateHighlightedDismissButtonOnActiveToast(
const std::string& id) override;
bool IsRunning(const std::string& id) const override;
bool IsRunning(std::string_view id) const override;
std::unique_ptr<ScopedToastPause> CreateScopedPause() override;
bool IsHighlighted(std::string_view id) const override;
// ToastOverlay::Delegate overrides:
void OnClosed() override;

@ -270,6 +270,10 @@ void ToastOverlay::OnSliderBubbleHeightChanged() {
}
}
bool ToastOverlay::IsDismissButtonHighlighted() const {
return overlay_view_->is_dismiss_button_highlighted();
}
gfx::Rect ToastOverlay::CalculateOverlayBounds() {
// If the native window has not been initialized, as in the first call, get
// the default root window. Otherwise get the window for this `overlay_widget`

@ -94,6 +94,10 @@ class ASH_EXPORT ToastOverlay : public ui::ImplicitAnimationObserver,
// UnifiedSystemTray::Observer:
void OnSliderBubbleHeightChanged() override;
// Returns if the dismiss button is highlighted in the toast. If the toast
// does not have a dismiss button, it returns false.
bool IsDismissButtonHighlighted() const;
private:
friend class ToastManagerImplTest;
friend class DesksTestApi;

@ -6,6 +6,7 @@
#include <memory>
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shelf/desk_button_widget.h"
@ -21,16 +22,19 @@
#include "ash/wm/desks/desks_constants.h"
#include "ash/wm/desks/desks_controller.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/work_area_insets.h"
#include "base/auto_reset.h"
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/task/single_thread_task_runner.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/presentation_time_recorder.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
@ -66,30 +70,49 @@ bool ShouldProcessLocatedEvent(const ui::LocatedEvent& event) {
void MoveFocus(const DeskBarController::BarWidgetAndView& desk_bar,
bool reverse) {
auto* focus_manager = desk_bar.bar_widget->GetFocusManager();
// When ChromeVox is not enabled, we do not need to advance focus outside of
// the normal focus order (i.e. to a button on a toast that will undo the desk
// close operation). Therefore, in this case we can just advance focus
// normally.
if (!Shell::Get()->accessibility_controller()->spoken_feedback().enabled()) {
focus_manager->AdvanceFocus(reverse);
return;
}
views::View* focused_view = focus_manager->GetFocusedView();
views::View* first_focusable_view = desk_bar.GetFirstFocusableView();
views::View* last_focusable_view = desk_bar.GetLastFocusableView();
// When a desk is removed and the undo desk removal toast is shown, we return
// focus back to the start of the cycling order.
views::View* next_focusable_view;
DesksController* desks_controller = DesksController::Get();
if (focused_view) {
next_focusable_view = desk_bar.GetNextFocusableView(focused_view, reverse);
} else {
next_focusable_view = reverse ? last_focusable_view : first_focusable_view;
}
// If we are moving over either end of the list of traversible views and there
// is an active toast with an undo button for desk removal that can be
// focused, then we unfocus any traversible views while the dismiss button is
// focused.
if (((next_focusable_view == first_focusable_view && !reverse) ||
(next_focusable_view == last_focusable_view && reverse)) &&
DesksController::Get()
->MaybeToggleA11yHighlightOnUndoDeskRemovalToast()) {
// If we are moving over either end of the list of traversible views and
// there is an active toast with an undo button for desk removal that can be
// focused, then we unfocus any traversible views while the dismiss button
// is focused.
if (((next_focusable_view == first_focusable_view && !reverse) ||
(next_focusable_view == last_focusable_view && reverse)) &&
desks_controller->MaybeToggleA11yHighlightOnUndoDeskRemovalToast()) {
focus_manager->ClearFocus();
return;
}
} else if (reverse &&
desks_controller
->MaybeToggleA11yHighlightOnUndoDeskRemovalToast()) {
focus_manager->ClearFocus();
return;
}
if (desks_controller->IsUndoToastHighlighted()) {
desks_controller->MaybeToggleA11yHighlightOnUndoDeskRemovalToast();
}
focus_manager->AdvanceFocus(reverse);
}
@ -133,6 +156,8 @@ DeskBarController::DeskBarController() {
Shell::Get()->tablet_mode_controller()->AddObserver(this);
DesksController::Get()->AddObserver(this);
Shell::Get()->activation_client()->AddObserver(this);
// TODO(b/301274861): DeskBarController should only be doing pre-target
// handling when the desk bar is visible.
Shell::Get()->AddPreTargetHandler(this);
Shell::Get()->AddShellObserver(this);
}
@ -155,20 +180,67 @@ void DeskBarController::OnMouseEvent(ui::MouseEvent* event) {
if (ShouldProcessLocatedEvent(*event)) {
OnMaybePressOffBar(*event);
}
if (event->type() == ui::ET_MOUSE_PRESSED) {
DesksController::Get()->MaybeDismissPersistentDeskRemovalToast();
}
}
void DeskBarController::OnTouchEvent(ui::TouchEvent* event) {
if (ShouldProcessLocatedEvent(*event)) {
OnMaybePressOffBar(*event);
}
if (event->type() == ui::ET_TOUCH_PRESSED) {
DesksController::Get()->MaybeDismissPersistentDeskRemovalToast();
}
}
void DeskBarController::OnKeyEvent(ui::KeyEvent* event) {
const bool is_key_press = event->type() == ui::ET_KEY_PRESSED;
if (!is_key_press || !IsShowingDeskBar()) {
// We return early if we are in an overview session because the overview desk
// bar has its own predefined key event handling logic. This will handle key
// events when the desk bar is not visible because the undo desk close toast
// can be visible without the desk bar being visible. But we still do not want
// to encroach on the logic that is established for the overview desk bar.
if (!is_key_press || IsInOverviewSession()) {
return;
}
// TODO(b/301274861): Move toast highlighting logic outside of the desk bar
// controller.
// There are two scenarios in which we should handle the close all undo toast
// without the desk bar being open:
// 1) the user presses return. This can occur whether the desk bar is showing
// or not, and in either case it should activate the undo toast.
// 2) the user presses any key other than return when the desk bar is not
// showing. If this is the case, we should try to close the toast.
DesksController* desks_controller = DesksController::Get();
if (event->key_code() == ui::VKEY_RETURN) {
desks_controller->MaybeActivateDeskRemovalUndoButtonOnHighlightedToast();
return;
}
if (!IsShowingDeskBar()) {
if (event->key_code() != ui::VKEY_RETURN) {
desks_controller->MaybeDismissPersistentDeskRemovalToast();
}
return;
}
// If the user is performing any non-traversal action (i.e. they do anything
// other than press tab or an arrow key) and is not trying to undo desk
// removal with Ctrl + Z, we should close the desk removal undo toast.
const ui::KeyboardCode traversal_keys[7] = {
ui::VKEY_TAB, ui::VKEY_UP, ui::VKEY_DOWN, ui::VKEY_LEFT,
ui::VKEY_RIGHT, ui::VKEY_SHIFT, ui::VKEY_CONTROL};
if (!(event->IsControlDown() && event->key_code() == ui::VKEY_Z) &&
!base::Contains(traversal_keys, event->key_code())) {
desks_controller->MaybeDismissPersistentDeskRemovalToast();
}
const bool is_control_down = event->IsControlDown();
for (auto& desk_bar : desk_bars_) {
if (!desk_bar.bar_view->GetVisible()) {
@ -181,7 +253,6 @@ void DeskBarController::OnKeyEvent(ui::KeyEvent* event) {
views::AsViewClass<DeskPreviewView>(focused_view);
DeskNameView* focused_name_view =
views::AsViewClass<DeskNameView>(focused_view);
auto* desks_controller = DesksController::Get();
// TODO(b/290651821): Consolidates arrow key behaviors for the desk bar.
switch (event->key_code()) {
@ -194,8 +265,8 @@ void DeskBarController::OnKeyEvent(ui::KeyEvent* event) {
break;
case ui::VKEY_UP:
case ui::VKEY_DOWN:
focus_manager->AdvanceFocus(/*reverse=*/event->key_code() ==
ui::VKEY_UP);
MoveFocus(desk_bar,
/*reverse=*/event->key_code() == ui::VKEY_UP);
break;
case ui::VKEY_TAB:
// For alt+tab/alt+shift+tab, like other UIs on the shelf, it should
@ -218,8 +289,8 @@ void DeskBarController::OnKeyEvent(ui::KeyEvent* event) {
return;
}
} else {
focus_manager->AdvanceFocus(/*reverse=*/event->key_code() ==
ui::VKEY_LEFT);
MoveFocus(desk_bar,
/*reverse=*/event->key_code() == ui::VKEY_LEFT);
}
break;
case ui::VKEY_W:
@ -245,12 +316,6 @@ void DeskBarController::OnKeyEvent(ui::KeyEvent* event) {
desks_controller->MaybeCancelDeskRemoval();
break;
case ui::VKEY_RETURN:
if (!desks_controller
->MaybeActivateDeskRemovalUndoButtonOnHighlightedToast()) {
return;
}
break;
default:
return;
}

@ -11,6 +11,7 @@
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/notifier_catalogs.h"
#include "ash/public/cpp/accessibility_controller.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shelf_prefs.h"
#include "ash/public/cpp/shelf_types.h"
@ -1666,6 +1667,12 @@ void DesksController::OnAnimationFinished(DeskAnimationBase* animation) {
DCHECK_EQ(animation_.get(), animation);
metrics_helper_->OnAnimationFinished(animation->visible_desk_changes());
animation_.reset();
// If we just switched desks due to removing the active desk, we immediately
// highlight the undo button.
if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled()) {
MaybeToggleA11yHighlightOnUndoDeskRemovalToast();
}
}
bool DesksController::HasDeskWithName(const std::u16string& desk_name) const {
@ -1977,12 +1984,17 @@ void DesksController::RemoveDeskInternal(const Desk* desk,
UMA_HISTOGRAM_ENUMERATION(kRemoveDeskTypeHistogramName, close_type);
// We should only announce desks are being merged if we are combining desks.
// Otherwise, we tell the user that the desk has closed with its windows.
AccessibilityControllerImpl* accessibility_controller =
shell->accessibility_controller();
if (close_type == DeskCloseType::kCombineDesks) {
Shell::Get()
->accessibility_controller()
->TriggerAccessibilityAlertWithMessage(l10n_util::GetStringFUTF8(
IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_REMOVED, removed_desk->name(),
active_desk_->name()));
accessibility_controller->TriggerAccessibilityAlertWithMessage(
l10n_util::GetStringFUTF8(IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_REMOVED,
removed_desk->name(), active_desk_->name()));
} else if (close_type == DeskCloseType::kCloseAllWindowsAndWait) {
accessibility_controller->TriggerAccessibilityAlertWithMessage(
l10n_util::GetStringUTF8(
IDS_ASH_VIRTUAL_DESKS_ALERT_DESK_CLOSED_WITH_WINDOWS));
}
desks_restore_util::UpdatePrimaryUserDeskNamesPrefs();
@ -2180,6 +2192,11 @@ void DesksController::MaybeCommitPendingDeskRemoval(
}
}
bool DesksController::IsUndoToastHighlighted() const {
return temporary_removed_desk_ && ToastManager::Get()->IsHighlighted(
temporary_removed_desk_->toast_id());
}
void DesksController::CleanUpClosedAppWindowsTask(
std::unique_ptr<aura::WindowTracker> closing_window_tracker) {
auto widgetless_windows = std::make_unique<aura::WindowTracker>();

@ -409,6 +409,10 @@ class ASH_EXPORT DesksController : public chromeos::DesksHelper,
void MaybeCommitPendingDeskRemoval(
const std::string& toast_id = std::string());
// Returns true if there is an active toast for undoing desk removal and that
// toast's dismiss button is currently being highlighted.
bool IsUndoToastHighlighted() const;
private:
class DeskTraversalsMetricsHelper;
class RemovedDeskData;

@ -9041,38 +9041,6 @@ TEST_P(DesksCloseAllTest,
EXPECT_TRUE(window.window()->transform().IsIdentity());
}
// Tests that we can undo close-all solely via keyboard navigation (tabbing to
// the undo toast and pressing enter).
TEST_P(DesksCloseAllTest, CanUndoDeskClosureThroughKeyboardNavigation) {
NewDesk();
Shell::Get()->accessibility_controller()->spoken_feedback().SetEnabled(true);
EnterOverview();
OverviewSession* overview_session =
Shell::Get()->overview_controller()->overview_session();
ASSERT_TRUE(overview_session);
// Tab to the first mini view and perform close-all on it.
SendKey(ui::VKEY_TAB);
ASSERT_EQ(GetPrimaryRootDesksBarView()->mini_views()[0]->desk_preview(),
overview_session->focus_cycler()->focused_view());
SendKey(ui::VKEY_W, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
ASSERT_EQ(1u, DesksController::Get()->desks().size());
// Tab to the undo toast.
SendKey(ui::VKEY_TAB);
SendKey(ui::VKEY_TAB);
SendKey(ui::VKEY_TAB);
// If the focus cycler returns a nullptr after tabbing, then the undo toast's
// button should be focused.
ASSERT_FALSE(overview_session->focus_cycler()->focused_view());
// Pressing enter should restore the desk.
SendKey(ui::VKEY_RETURN);
EXPECT_EQ(2u, DesksController::Get()->desks().size());
}
// Tests that we can create the maximum number of desks, remove one, and add one
// before the toast asking if the user would like to undo goes away.
TEST_P(DesksCloseAllTest, CanAddLastDeskWhileUndoToastIsBeingDisplayed) {
@ -10881,6 +10849,150 @@ TEST_P(DeskBarTest, BottomLockedShelf) {
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kBottomLocked);
}
// Tests that we can undo close-all solely via keyboard navigation (tabbing to
// the undo toast and pressing enter).
TEST_P(DeskBarTest, CanUndoDeskClosureThroughKeyboardNavigation) {
// Scenarios in which we can try to undo desk closure. If the active desk is
// removed, we close the desk bar and immediately focus the undo button.
enum class DeskRemovalMethod {
kInactiveDeskRemovedForwardTab,
kInactiveDeskRemovedReverseTab,
kActiveDeskRemoved,
};
struct {
const std::string scope_trace;
const DeskRemovalMethod desk_removal_method;
const int expected_tab_count;
} kTestCases[] = {
{base::StringPrintf("Forward tabbing to the undo button after removing "
"an inactive desk %s",
(bar_type_ == DeskBarViewBase::Type::kDeskButton
? "in desk button desk bar"
: "in overview desk bar")),
DeskRemovalMethod::kInactiveDeskRemovedForwardTab,
bar_type_ == DeskBarViewBase::Type::kDeskButton ? 8 : 4},
{base::StringPrintf("Reverse tabbing to the undo button after removing "
"an inactive desk %s",
(bar_type_ == DeskBarViewBase::Type::kDeskButton
? "in desk button desk bar"
: "in overview desk bar")),
DeskRemovalMethod::kInactiveDeskRemovedReverseTab,
bar_type_ == DeskBarViewBase::Type::kDeskButton ? 1 : 4},
{base::StringPrintf(
"Activating the undo button after removing the active desk %s",
(bar_type_ == DeskBarViewBase::Type::kDeskButton
? "in desk button desk bar"
: "in overview desk bar")),
DeskRemovalMethod::kActiveDeskRemoved,
bar_type_ == DeskBarViewBase::Type::kDeskButton ? 0 : 6},
};
NewDesk();
NewDesk();
Shell::Get()->accessibility_controller()->spoken_feedback().SetEnabled(true);
OpenDeskBar();
auto* desk_bar = GetDeskBarView();
const auto& mini_views = desk_bar->mini_views();
DesksController* desks_controller = DesksController::Get();
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(test_case.scope_trace);
// Tab to the first mini view.
SendKey(ui::VKEY_TAB);
// If we are not closing the active desk, close the next desk instead.
if (test_case.desk_removal_method !=
DeskRemovalMethod::kActiveDeskRemoved) {
SendKey(ui::VKEY_TAB);
SendKey(ui::VKEY_TAB);
// The desk button desk bar adds the close-all button to the tab order, so
// we need to include an extra tab for that.
if (bar_type_ == DeskBarViewBase::Type::kDeskButton) {
SendKey(ui::VKEY_TAB);
ASSERT_EQ(mini_views[1]->desk_preview(),
desk_bar->GetWidget()->GetFocusManager()->GetFocusedView());
} else {
ASSERT_EQ(mini_views[1]->desk_preview(), Shell::Get()
->overview_controller()
->overview_session()
->focus_cycler()
->focused_view());
}
SendKey(ui::VKEY_W, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
} else if (bar_type_ == DeskBarViewBase::Type::kDeskButton) {
if (bar_type_ == DeskBarViewBase::Type::kDeskButton) {
ASSERT_EQ(mini_views[0]->desk_preview(),
desk_bar->GetWidget()->GetFocusManager()->GetFocusedView());
} else {
ASSERT_EQ(mini_views[0]->desk_preview(), Shell::Get()
->overview_controller()
->overview_session()
->focus_cycler()
->focused_view());
}
// In the desk button desk bar, we run the desk switch animation when
// removing the active desk.
DeskSwitchAnimationWaiter waiter;
SendKey(ui::VKEY_W, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
waiter.Wait();
} else {
if (bar_type_ == DeskBarViewBase::Type::kDeskButton) {
ASSERT_EQ(mini_views[0]->desk_preview(),
desk_bar->GetWidget()->GetFocusManager()->GetFocusedView());
} else {
ASSERT_EQ(mini_views[0]->desk_preview(), Shell::Get()
->overview_controller()
->overview_session()
->focus_cycler()
->focused_view());
}
SendKey(ui::VKEY_W, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN);
}
ASSERT_EQ(2u, desks_controller->desks().size());
// Tab to the undo toast.
for (int i = 0; i < test_case.expected_tab_count; i++) {
SendKey(ui::VKEY_TAB,
test_case.desk_removal_method ==
DeskRemovalMethod::kInactiveDeskRemovedReverseTab
? ui::EF_SHIFT_DOWN
: ui::EF_NONE);
}
ASSERT_TRUE(bar_type_ == DeskBarViewBase::Type::kDeskButton
? desks_controller->IsUndoToastHighlighted()
: !Shell::Get()
->overview_controller()
->overview_session()
->focus_cycler()
->focused_view());
// Pressing undo in the desk button desk bar after closing the active desk
// will switch us back to the removed desk.
if (test_case.desk_removal_method ==
DeskRemovalMethod::kActiveDeskRemoved &&
bar_type_ == DeskBarViewBase::Type::kDeskButton) {
DeskSwitchAnimationWaiter waiter;
SendKey(ui::VKEY_RETURN);
waiter.Wait();
} else {
SendKey(ui::VKEY_RETURN);
}
ASSERT_EQ(3u, desks_controller->desks().size());
}
}
namespace {
struct DeskButtonTestParams {