Commit 8eebdf00 authored by Andrew Xu's avatar Andrew Xu Committed by Commit Bot

[Reland]Fix the issue of accessibility focus traversal with hotseat

The previous CL was reverted because it assumes that the title of the
first shelf icon is "Chromium", which is not true under official
build. This CL replaces the hard-code check with the function to
fetch the icon's title. Meanwhile, this CL merges shelf_widget_test_api
into shelf_test_api.

///////////////////////////////////////////////////////////////////////
Previous CL description:

This CL accomplishes the following things:

(1) Designate the previous/next focusable widget for navigation widget,
hotseat widget and status area widget. It prevents the accessibility
ring from being trapped in one widget.

(2) Ensures that the hotseat is shown when the shelf app icon gets
the accessibility focus.

(3) Implements the test api to facilitate browser tests to access
the shelf widget code.

Bug: 1010219
Change-Id: Ibe522a6396c61f8b9bb7af01f34106f21500fdf6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1892155
Commit-Queue: Andrew Xu <andrewxu@chromium.org>
Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#711490}
parent 5b377f38
......@@ -9,6 +9,10 @@
#include "ash/ash_export.h"
namespace views {
class View;
}
namespace ash {
// All methods operate on the shelf on the primary display.
......@@ -25,6 +29,8 @@ class ASH_EXPORT ShelfTestApi {
// Returns true if the shelf alignment is BOTTOM_LOCKED, which is not exposed
// via prefs.
virtual bool IsAlignmentBottomLocked() = 0;
virtual views::View* GetHomeButton() = 0;
};
} // namespace ash
......
......@@ -8,6 +8,7 @@
#include "ash/public/cpp/shelf_config.h"
#include "ash/screen_util.h"
#include "ash/shelf/shelf_focus_cycler.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_tooltip_manager.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/system/status_area_widget.h"
......@@ -753,6 +754,12 @@ void ScrollableShelfView::OnGestureEvent(ui::GestureEvent* event) {
}
}
void ScrollableShelfView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
GetViewAccessibility().OverrideNextFocus(GetShelf()->GetStatusAreaWidget());
GetViewAccessibility().OverridePreviousFocus(
GetShelf()->shelf_widget()->navigation_widget());
}
const char* ScrollableShelfView::GetClassName() const {
return "ScrollableShelfView";
}
......@@ -778,6 +785,14 @@ void ScrollableShelfView::ButtonPressed(views::Button* sender,
shelf_view_->ButtonPressed(sender, event, ink_drop);
}
void ScrollableShelfView::HandleAccessibleActionScrollToMakeVisible() {
// Only in tablet mode with hotseat enabled, may scrollable shelf be hidden.
if (!IsInTabletMode() || !chromeos::switches::ShouldShowShelfHotseat())
return;
GetShelf()->shelf_widget()->ForceToShowHotseat();
}
void ScrollableShelfView::ShowContextMenuForViewImpl(
views::View* source,
const gfx::Point& point,
......
......@@ -166,6 +166,7 @@ class ASH_EXPORT ScrollableShelfView : public views::AccessiblePaneView,
const char* GetClassName() const override;
void OnMouseEvent(ui::MouseEvent* event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
// ShelfButtonDelegate:
void OnShelfButtonAboutToRequestFocusFromTabTraversal(ShelfButton* button,
......@@ -173,6 +174,7 @@ class ASH_EXPORT ScrollableShelfView : public views::AccessiblePaneView,
void ButtonPressed(views::Button* sender,
const ui::Event& event,
views::InkDrop* ink_drop) override;
void HandleAccessibleActionScrollToMakeVisible() override;
// ContextMenuController:
void ShowContextMenuForViewImpl(views::View* source,
......
......@@ -19,6 +19,7 @@
#include "base/time/time.h"
#include "chromeos/constants/chromeos_switches.h"
#include "skia/ext/image_operations.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
......@@ -739,6 +740,14 @@ std::unique_ptr<views::InkDropMask> ShelfAppButton::CreateInkDropMask() const {
return nullptr;
}
bool ShelfAppButton::HandleAccessibleAction(
const ui::AXActionData& action_data) {
if (action_data.action == ax::mojom::Action::kScrollToMakeVisible)
shelf_button_delegate()->HandleAccessibleActionScrollToMakeVisible();
return views::View::HandleAccessibleAction(action_data);
}
void ShelfAppButton::UpdateState() {
indicator_->SetVisible(!(state_ & STATE_HIDDEN) &&
(state_ & STATE_ATTENTION || state_ & STATE_RUNNING ||
......
......@@ -107,6 +107,9 @@ class ASH_EXPORT ShelfAppButton : public ShelfButton {
class AppNotificationIndicatorView;
class AppStatusIndicatorView;
// views::View:
bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
// Updates the parts of the button to reflect the current |state_| and
// alignment. This may add or remove views, layout and paint.
void UpdateState();
......
......@@ -42,6 +42,10 @@ class ShelfButtonDelegate {
const ui::Event& event,
views::InkDrop* ink_drop) = 0;
// Called when the shelf button handles the accessible action with type of
// kScrollToMakeVisible.
virtual void HandleAccessibleActionScrollToMakeVisible() {}
private:
DISALLOW_COPY_AND_ASSIGN(ShelfButtonDelegate);
};
......
......@@ -1048,6 +1048,9 @@ HotseatState ShelfLayoutManager::CalculateHotseatState(
if (!IsHotseatEnabled() || !shelf_->IsHorizontalAlignment())
return HotseatState::kShown;
if (shelf_widget_->is_hotseat_forced_to_show())
return HotseatState::kExtended;
auto* app_list_controller = Shell::Get()->app_list_controller();
const auto* overview_controller = Shell::Get()->overview_controller();
const bool in_overview =
......
......@@ -168,6 +168,12 @@ void ShelfNavigationWidget::Delegate::GetAccessibleNodeData(
ui::AXNodeData* node_data) {
node_data->role = ax::mojom::Role::kToolbar;
node_data->SetName(l10n_util::GetStringUTF8(IDS_ASH_SHELF_ACCESSIBLE_NAME));
ShelfWidget* shelf_widget =
Shelf::ForWindow(GetWidget()->GetNativeWindow())->shelf_widget();
GetViewAccessibility().OverrideNextFocus(shelf_widget->hotseat_widget());
GetViewAccessibility().OverridePreviousFocus(
shelf_widget->status_area_widget());
}
void ShelfNavigationWidget::Delegate::ReorderChildLayers(
......
......@@ -5,23 +5,43 @@
#include "ash/shelf/shelf_test_api_impl.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
namespace {
ash::Shelf* GetShelf() {
return ash::Shell::Get()->GetPrimaryRootWindowController()->shelf();
}
ash::ShelfWidget* GetShelfWidget() {
return ash::Shell::GetRootWindowControllerWithDisplayId(
display::Screen::GetScreen()->GetPrimaryDisplay().id())
->shelf()
->shelf_widget();
}
} // namespace
namespace ash {
ShelfTestApiImpl::ShelfTestApiImpl() = default;
ShelfTestApiImpl::~ShelfTestApiImpl() = default;
bool ShelfTestApiImpl::IsVisible() {
Shelf* shelf = Shell::Get()->GetPrimaryRootWindowController()->shelf();
return shelf->shelf_layout_manager()->IsVisible();
return GetShelf()->shelf_layout_manager()->IsVisible();
}
bool ShelfTestApiImpl::IsAlignmentBottomLocked() {
Shelf* shelf = Shell::Get()->GetPrimaryRootWindowController()->shelf();
return shelf->alignment() == SHELF_ALIGNMENT_BOTTOM_LOCKED;
return GetShelf()->alignment() == SHELF_ALIGNMENT_BOTTOM_LOCKED;
}
views::View* ShelfTestApiImpl::GetHomeButton() {
return GetShelfWidget()->navigation_widget()->GetHomeButton();
}
// static
......
......@@ -19,6 +19,7 @@ class ShelfTestApiImpl : public ShelfTestApi {
// ShelfTestApi:
bool IsVisible() override;
bool IsAlignmentBottomLocked() override;
views::View* GetHomeButton() override;
private:
DISALLOW_COPY_AND_ASSIGN(ShelfTestApiImpl);
......
......@@ -6,6 +6,7 @@
#include <utility>
#include "ash/accessibility/accessibility_controller_impl.h"
#include "ash/animation/animation_change_type.h"
#include "ash/focus_cycler.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
......@@ -341,6 +342,14 @@ bool ShelfWidget::GetHitTestRects(aura::Window* target,
return true;
}
void ShelfWidget::ForceToShowHotseat() {
if (is_hotseat_forced_to_show_)
return;
is_hotseat_forced_to_show_ = true;
shelf_layout_manager_->UpdateVisibilityState();
}
ShelfWidget::ShelfWidget(Shelf* shelf)
: shelf_(shelf),
background_animator_(shelf_, Shell::Get()->wallpaper_controller()),
......@@ -351,6 +360,8 @@ ShelfWidget::ShelfWidget(Shelf* shelf)
}
ShelfWidget::~ShelfWidget() {
Shell::Get()->accessibility_controller()->RemoveObserver(this);
// Must call Shutdown() before destruction.
DCHECK(!status_area_widget_);
}
......@@ -393,6 +404,8 @@ void ShelfWidget::Initialize(aura::Window* shelf_container) {
// Sets initial session state to make sure the UI is properly shown.
OnSessionStateChanged(Shell::Get()->session_controller()->GetSessionState());
GetFocusManager()->set_arrow_key_traversal_enabled_for_widget(true);
Shell::Get()->accessibility_controller()->AddObserver(this);
}
void ShelfWidget::Shutdown() {
......@@ -640,4 +653,16 @@ void ShelfWidget::OnGestureEvent(ui::GestureEvent* event) {
views::Widget::OnGestureEvent(event);
}
void ShelfWidget::OnAccessibilityStatusChanged() {
// Only handles when the spoken feedback is disabled.
if (Shell::Get()->accessibility_controller()->spoken_feedback_enabled())
return;
if (!is_hotseat_forced_to_show_)
return;
is_hotseat_forced_to_show_ = false;
shelf_layout_manager_->UpdateVisibilityState();
}
} // namespace ash
......@@ -7,6 +7,7 @@
#include <memory>
#include "ash/accessibility/accessibility_observer.h"
#include "ash/ash_export.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/session/session_observer.h"
......@@ -37,7 +38,8 @@ class StatusAreaWidget;
class ASH_EXPORT ShelfWidget : public views::Widget,
public ShelfLayoutManagerObserver,
public ShelfObserver,
public SessionObserver {
public SessionObserver,
public AccessibilityObserver {
public:
explicit ShelfWidget(Shelf* shelf);
~ShelfWidget() override;
......@@ -127,6 +129,10 @@ class ASH_EXPORT ShelfWidget : public views::Widget,
gfx::Rect* hit_test_rect_mouse,
gfx::Rect* hit_test_rect_touch);
void ForceToShowHotseat();
bool is_hotseat_forced_to_show() const { return is_hotseat_forced_to_show_; }
// Internal implementation detail. Do not expose outside of tests.
ShelfView* shelf_view_for_testing() const {
return hotseat_widget()->GetShelfView();
......@@ -140,6 +146,9 @@ class ASH_EXPORT ShelfWidget : public views::Widget,
class DelegateView;
friend class DelegateView;
// AccessibilityObserver:
void OnAccessibilityStatusChanged() override;
// Hides shelf widget if IsVisible() returns true.
void HideIfShown();
......@@ -171,6 +180,8 @@ class ASH_EXPORT ShelfWidget : public views::Widget,
ScopedSessionObserver scoped_session_observer_;
bool is_hotseat_forced_to_show_ = false;
DISALLOW_COPY_AND_ASSIGN(ShelfWidget);
};
......
......@@ -15,6 +15,7 @@
#include "ash/shelf/login_shelf_view.h"
#include "ash/shelf/shelf_focus_cycler.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/style/ash_color_provider.h"
......@@ -285,7 +286,8 @@ void TrayBackgroundView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
Shelf* shelf = Shelf::ForWindow(GetWidget()->GetNativeWindow());
ShelfWidget* shelf_widget = shelf->shelf_widget();
GetViewAccessibility().OverridePreviousFocus(shelf_widget);
GetViewAccessibility().OverridePreviousFocus(shelf_widget->hotseat_widget());
GetViewAccessibility().OverrideNextFocus(shelf_widget->navigation_widget());
}
void TrayBackgroundView::ChildPreferredSizeChanged(views::View* child) {
......
......@@ -12,10 +12,10 @@
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shelf_test_api.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/overflow_button.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_app_button.h"
......@@ -40,6 +40,8 @@
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/launch_service/launch_service.h"
#include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
#include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
#include "chrome/browser/chromeos/accessibility/speech_monitor.h"
#include "chrome/browser/chromeos/login/demo_mode/demo_session.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_browsertest.h"
......@@ -2211,6 +2213,60 @@ IN_PROC_BROWSER_TEST_F(HotseatShelfAppBrowserTest,
controller->shelf()->shelf_layout_manager()->hotseat_state());
}
// Verify that the in-app shelf should be shown when the app icon receives
// the accessibility focus.
IN_PROC_BROWSER_TEST_F(HotseatShelfAppBrowserTest, EnableChromeVox) {
ash::Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
chromeos::SpeechMonitor speech_monitor;
// Enable ChromeVox.
{
ASSERT_FALSE(
chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
chromeos::AccessibilityManager::Get()->EnableSpokenFeedback(true);
EXPECT_TRUE(speech_monitor.SkipChromeVoxEnabledMessage());
// Disable earcons (https://crbug.com/396507).
const std::string script(
"cvox.ChromeVox.earcons.playEarcon = function() {};");
extensions::ExtensionHost* host =
extensions::ProcessManager::Get(browser()->profile())
->GetBackgroundHostForExtension(
extension_misc::kChromeVoxExtensionId);
CHECK(content::ExecuteScript(host->host_contents(), script));
}
ash::RootWindowController* controller =
ash::Shell::GetRootWindowControllerWithDisplayId(
display::Screen::GetScreen()->GetPrimaryDisplay().id());
// Gesture tap at the home button.
views::View* home_button = ash::ShelfTestApi::Create()->GetHomeButton();
ui::test::EventGenerator event_generator(controller->GetRootWindow());
event_generator.GestureTapAt(home_button->GetBoundsInScreen().CenterPoint());
ASSERT_EQ("Launcher", speech_monitor.GetNextUtterance());
ASSERT_EQ("Button", speech_monitor.GetNextUtterance());
ASSERT_EQ("Shelf", speech_monitor.GetNextUtterance());
ASSERT_EQ("Tool bar", speech_monitor.GetNextUtterance());
ASSERT_EQ(", window", speech_monitor.GetNextUtterance());
// Verifies that before moving the focus to the app icon, hotseat is hidden.
ASSERT_EQ(ash::HotseatState::kHidden,
controller->shelf()->shelf_layout_manager()->hotseat_state());
// Press the search + right. Expects that the browser icon receives the
// accessibility focus and the hotseat is shown.
event_generator.PressKey(ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN);
const int browser_index =
ash::ShelfModel::Get()->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT);
EXPECT_EQ(
base::UTF16ToASCII(ash::ShelfModel::Get()->items()[browser_index].title),
speech_monitor.GetNextUtterance());
EXPECT_EQ(ash::HotseatState::kExtended,
controller->shelf()->shelf_layout_manager()->hotseat_state());
}
namespace {
class ShelfAppBrowserTestWithDesks : public ShelfAppBrowserTest {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment