Commit 7750c6d5 authored by Aaron Leventhal's avatar Aaron Leventhal Committed by Commit Bot

Fix regression where Mac menus are not read by VoiceOver

Ensure that the focus is reported as the current menu item in all menus,
not just in autofill popups, by using the popup focus override, similar
to what's used for autofill popups.

AX-Relnotes: n/a
Bug: 1100230
Change-Id: I80c0414aa5b675c966aab8953547fa36e0bac8f6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2284050
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
Reviewed-by: default avatarPeter Boström <pbos@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#787340}
parent b53dbb8a
...@@ -19,15 +19,30 @@ ...@@ -19,15 +19,30 @@
#include "ui/views/accessibility/ax_event_manager.h" #include "ui/views/accessibility/ax_event_manager.h"
#include "ui/views/accessibility/ax_event_observer.h" #include "ui/views/accessibility/ax_event_observer.h"
#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/submenu_view.h" #include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
#if !defined(OS_CHROMEOS)
#include "ui/accessibility/platform/ax_platform_node.h"
#endif
namespace views { namespace views {
namespace test { namespace test {
namespace { namespace {
class TestButton : public Button {
public:
TestButton() : Button(nullptr) {}
TestButton(const TestButton&) = delete;
TestButton& operator=(const TestButton&) = delete;
~TestButton() override = default;
};
class TestAXEventObserver : public views::AXEventObserver { class TestAXEventObserver : public views::AXEventObserver {
public: public:
TestAXEventObserver() { views::AXEventManager::Get()->AddObserver(this); } TestAXEventObserver() { views::AXEventManager::Get()->AddObserver(this); }
...@@ -105,6 +120,10 @@ class MenuControllerUITest : public InProcessBrowserTest { ...@@ -105,6 +120,10 @@ class MenuControllerUITest : public InProcessBrowserTest {
ui::AXNodeData item_node_data; ui::AXNodeData item_node_data;
first_item_->GetViewAccessibility().GetAccessibleNodeData(&item_node_data); first_item_->GetViewAccessibility().GetAccessibleNodeData(&item_node_data);
EXPECT_EQ(item_node_data.role, ax::mojom::Role::kMenuItem); EXPECT_EQ(item_node_data.role, ax::mojom::Role::kMenuItem);
#if !defined(OS_CHROMEOS) // ChromeOS does not use popup focus override.
EXPECT_TRUE(first_item_->GetViewAccessibility().IsFocusedForTesting());
#endif
ui::AXNodeData menu_node_data; ui::AXNodeData menu_node_data;
menu_item->GetSubmenu()->GetViewAccessibility().GetAccessibleNodeData( menu_item->GetSubmenu()->GetViewAccessibility().GetAccessibleNodeData(
&menu_node_data); &menu_node_data);
...@@ -130,6 +149,10 @@ class MenuControllerUITest : public InProcessBrowserTest { ...@@ -130,6 +149,10 @@ class MenuControllerUITest : public InProcessBrowserTest {
}; };
IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) { IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) {
#if !defined(OS_CHROMEOS)
ui::AXPlatformNode::NotifyAddAXModeFlags(ui::kAXModeComplete);
#endif
// Create a parent widget. // Create a parent widget.
Widget* widget = new views::Widget; Widget* widget = new views::Widget;
Widget::InitParams params(Widget::InitParams::TYPE_WINDOW); Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
...@@ -140,7 +163,19 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) { ...@@ -140,7 +163,19 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) {
#endif #endif
widget->Init(std::move(params)); widget->Init(std::move(params));
widget->Show(); widget->Show();
views::test::WidgetActivationWaiter waiter(widget, true);
widget->Activate(); widget->Activate();
waiter.Wait();
// Create a focused test button, used to assert that it has accessibility
// focus before and after menu item is active, but not during.
TestButton button;
widget->GetContentsView()->AddChildView(&button);
FocusManager* focus_manager = widget->GetFocusManager();
focus_manager->SetFocusedView(&button);
EXPECT_TRUE(button.HasFocus());
EXPECT_TRUE(button.GetViewAccessibility().IsFocusedForTesting());
// SetupMenu leaves the mouse position where the first menu item will be // SetupMenu leaves the mouse position where the first menu item will be
// when we run the menu. // when we run the menu.
TestAXEventObserver observer; TestAXEventObserver observer;
...@@ -157,6 +192,9 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) { ...@@ -157,6 +192,9 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) {
EXPECT_EQ(observer.GetMenuPopupEndCount(), 1); EXPECT_EQ(observer.GetMenuPopupEndCount(), 1);
EXPECT_EQ(observer.GetMenuEndCount(), 1); EXPECT_EQ(observer.GetMenuEndCount(), 1);
EXPECT_FALSE(first_item_->IsSelected()); EXPECT_FALSE(first_item_->IsSelected());
#if !defined(OS_CHROMEOS) // ChromeOS does not use popup focus override.
EXPECT_FALSE(first_item_->GetViewAccessibility().IsFocusedForTesting());
#endif
menu_runner_->RunMenuAt(widget, nullptr, gfx::Rect(), menu_runner_->RunMenuAt(widget, nullptr, gfx::Rect(),
views::MenuAnchorPosition::kTopLeft, views::MenuAnchorPosition::kTopLeft,
ui::MENU_SOURCE_NONE); ui::MENU_SOURCE_NONE);
...@@ -169,6 +207,10 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) { ...@@ -169,6 +207,10 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) {
// Process event(s), and check what's selected in the menu. // Process event(s), and check what's selected in the menu.
RunPendingMessages(); RunPendingMessages();
EXPECT_FALSE(first_item_->IsSelected()); EXPECT_FALSE(first_item_->IsSelected());
#if !defined(OS_CHROMEOS) // ChromeOS does not use popup focus override.
EXPECT_FALSE(first_item_->GetViewAccessibility().IsFocusedForTesting());
EXPECT_TRUE(button.GetViewAccessibility().IsFocusedForTesting());
#endif
// Move mouse one pixel to left and verify that the first menu item // Move mouse one pixel to left and verify that the first menu item
// is selected. // is selected.
mouse_pos_.Offset(-1, 0); mouse_pos_.Offset(-1, 0);
...@@ -177,7 +219,15 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) { ...@@ -177,7 +219,15 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, TestMouseOverShownMenu) {
run_loop2.QuitClosure()); run_loop2.QuitClosure());
run_loop2.Run(); run_loop2.Run();
EXPECT_TRUE(first_item_->IsSelected()); EXPECT_TRUE(first_item_->IsSelected());
#if !defined(OS_CHROMEOS) // ChromeOS does not use popup focus override.
EXPECT_TRUE(first_item_->GetViewAccessibility().IsFocusedForTesting());
EXPECT_FALSE(button.GetViewAccessibility().IsFocusedForTesting());
#endif
menu_runner_->Cancel(); menu_runner_->Cancel();
#if !defined(OS_CHROMEOS) // ChromeOS does not use popup focus override.
EXPECT_FALSE(first_item_->GetViewAccessibility().IsFocusedForTesting());
EXPECT_TRUE(button.GetViewAccessibility().IsFocusedForTesting());
#endif
EXPECT_EQ(observer.GetMenuStartCount(), 2); EXPECT_EQ(observer.GetMenuStartCount(), 2);
EXPECT_EQ(observer.GetMenuPopupStartCount(), 2); EXPECT_EQ(observer.GetMenuPopupStartCount(), 2);
EXPECT_EQ(observer.GetMenuPopupEndCount(), 2); EXPECT_EQ(observer.GetMenuPopupEndCount(), 2);
...@@ -195,6 +245,7 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, FocusOnOrphanMenu) { ...@@ -195,6 +245,7 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, FocusOnOrphanMenu) {
// Going into full screen mode prevents pre-test focus and mouse position // Going into full screen mode prevents pre-test focus and mouse position
// state from affecting test, and helps ui_controls function correctly. // state from affecting test, and helps ui_controls function correctly.
chrome::ToggleFullscreenMode(browser()); chrome::ToggleFullscreenMode(browser());
ui::AXPlatformNode::NotifyAddAXModeFlags(ui::kAXModeComplete);
MenuDelegate menu_delegate; MenuDelegate menu_delegate;
MenuItemView* menu_item = new MenuItemView(&menu_delegate); MenuItemView* menu_item = new MenuItemView(&menu_delegate);
TestAXEventObserver observer; TestAXEventObserver observer;
...@@ -221,7 +272,9 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, FocusOnOrphanMenu) { ...@@ -221,7 +272,9 @@ IN_PROC_BROWSER_TEST_F(MenuControllerUITest, FocusOnOrphanMenu) {
false, false, false, false, loop.QuitClosure())); false, false, false, false, loop.QuitClosure()));
loop.Run(); loop.Run();
EXPECT_TRUE(first_item->IsSelected()); EXPECT_TRUE(first_item->IsSelected());
EXPECT_TRUE(first_item->GetViewAccessibility().IsFocusedForTesting());
menu_runner->Cancel(); menu_runner->Cancel();
EXPECT_FALSE(first_item->GetViewAccessibility().IsFocusedForTesting());
EXPECT_EQ(observer.GetMenuStartCount(), 1); EXPECT_EQ(observer.GetMenuStartCount(), 1);
EXPECT_EQ(observer.GetMenuPopupStartCount(), 1); EXPECT_EQ(observer.GetMenuPopupStartCount(), 1);
EXPECT_EQ(observer.GetMenuPopupEndCount(), 1); EXPECT_EQ(observer.GetMenuPopupEndCount(), 1);
......
...@@ -242,6 +242,10 @@ void ViewAccessibility::SetPopupFocusOverride() {} ...@@ -242,6 +242,10 @@ void ViewAccessibility::SetPopupFocusOverride() {}
void ViewAccessibility::EndPopupFocusOverride() {} void ViewAccessibility::EndPopupFocusOverride() {}
bool ViewAccessibility::IsFocusedForTesting() {
return view_->HasFocus() && !focused_virtual_child_;
}
void ViewAccessibility::OverrideRole(const ax::mojom::Role role) { void ViewAccessibility::OverrideRole(const ax::mojom::Role role) {
DCHECK(IsValidRoleForViews(role)) << "Invalid role for Views."; DCHECK(IsValidRoleForViews(role)) << "Invalid role for Views.";
custom_data_.role = role; custom_data_.role = role;
......
...@@ -153,6 +153,9 @@ class VIEWS_EXPORT ViewAccessibility { ...@@ -153,6 +153,9 @@ class VIEWS_EXPORT ViewAccessibility {
// Call when popup closes, if it used SetPopupFocusOverride(). // Call when popup closes, if it used SetPopupFocusOverride().
virtual void EndPopupFocusOverride(); virtual void EndPopupFocusOverride();
// Return true if this view is considered focused.
virtual bool IsFocusedForTesting();
// Call when a menu closes, to restore focus to where it was previously. // Call when a menu closes, to restore focus to where it was previously.
virtual void FireFocusAfterMenuClose(); virtual void FireFocusAfterMenuClose();
......
...@@ -154,6 +154,13 @@ void ViewAXPlatformNodeDelegate::EndPopupFocusOverride() { ...@@ -154,6 +154,13 @@ void ViewAXPlatformNodeDelegate::EndPopupFocusOverride() {
ui::AXPlatformNode::SetPopupFocusOverride(nullptr); ui::AXPlatformNode::SetPopupFocusOverride(nullptr);
} }
bool ViewAXPlatformNodeDelegate::IsFocusedForTesting() {
if (ui::AXPlatformNode::GetPopupFocusOverride())
return ui::AXPlatformNode::GetPopupFocusOverride() == GetNativeObject();
return ViewAccessibility::IsFocusedForTesting();
}
void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent( void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent(
ax::mojom::Event event_type) { ax::mojom::Event event_type) {
DCHECK(ax_platform_node_); DCHECK(ax_platform_node_);
...@@ -166,13 +173,19 @@ void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent( ...@@ -166,13 +173,19 @@ void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent(
// Some events have special handling. // Some events have special handling.
switch (event_type) { switch (event_type) {
case ax::mojom::Event::kFocusAfterMenuClose: {
DCHECK(!ui::AXPlatformNode::GetPopupFocusOverride())
<< "Must call ViewAccessibility::EndPopupFocusOverride() as menu "
"closes.";
break;
}
case ax::mojom::Event::kFocus: { case ax::mojom::Event::kFocus: {
if (ui::AXPlatformNode::GetPopupFocusOverride()) { if (ui::AXPlatformNode::GetPopupFocusOverride()) {
DCHECK_EQ(ui::AXPlatformNode::GetPopupFocusOverride(), DCHECK_EQ(ui::AXPlatformNode::GetPopupFocusOverride(),
GetNativeObject()) GetNativeObject())
<< "If the popup focus override is on, then the kFocus event must " << "If the popup focus override is on, then the kFocus event must "
"match it. Most likely the popup has closed, but did not call " "match it. Most likely the popup has closed, but did not call "
"ViewAccessibility::FireFocusAfterMenuClose(), and focus has " "ViewAccessibility::EndPopupFocusOverride(), and focus has "
"now moved on."; "now moved on.";
} }
break; break;
......
...@@ -91,6 +91,7 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility, ...@@ -91,6 +91,7 @@ class ViewAXPlatformNodeDelegate : public ViewAccessibility,
void SetPopupFocusOverride() override; void SetPopupFocusOverride() override;
void EndPopupFocusOverride() override; void EndPopupFocusOverride() override;
bool IsFocusedForTesting() override;
protected: protected:
explicit ViewAXPlatformNodeDelegate(View* view); explicit ViewAXPlatformNodeDelegate(View* view);
......
...@@ -1385,6 +1385,11 @@ void MenuController::SetSelection(MenuItemView* menu_item, ...@@ -1385,6 +1385,11 @@ void MenuController::SetSelection(MenuItemView* menu_item,
menu_item->GetType() != MenuItemView::Type::kSubMenu || menu_item->GetType() != MenuItemView::Type::kSubMenu ||
(menu_item->GetType() == MenuItemView::Type::kActionableSubMenu && (menu_item->GetType() == MenuItemView::Type::kActionableSubMenu &&
(selection_types & SELECTION_OPEN_SUBMENU) == 0))) { (selection_types & SELECTION_OPEN_SUBMENU) == 0))) {
// Before firing the selection event, ensure that focus appears to be
// within the popup. This is helpful for ATs on some platforms,
// specifically on Windows, where selection events in a list are mapped
// to focus events. Without this call, the focus appears to be elsewhere.
menu_item->GetViewAccessibility().SetPopupFocusOverride();
menu_item->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true); menu_item->NotifyAccessibilityEvent(ax::mojom::Event::kSelection, true);
// Notify an accessibility selected children changed event on the parent // Notify an accessibility selected children changed event on the parent
// submenu. // submenu.
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include "ui/compositor/paint_recorder.h" #include "ui/compositor/paint_recorder.h"
#include "ui/events/event.h" #include "ui/events/event.h"
#include "ui/gfx/canvas.h" #include "ui/gfx/canvas.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/menu/menu_config.h" #include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h" #include "ui/views/controls/menu/menu_controller.h"
#include "ui/views/controls/menu/menu_host.h" #include "ui/views/controls/menu/menu_host.h"
...@@ -428,6 +429,7 @@ void SubmenuView::Hide() { ...@@ -428,6 +429,7 @@ void SubmenuView::Hide() {
if (!GetMenuItem()->GetParentMenuItem()) { if (!GetMenuItem()->GetParentMenuItem()) {
GetScrollViewContainer()->NotifyAccessibilityEvent( GetScrollViewContainer()->NotifyAccessibilityEvent(
ax::mojom::Event::kMenuEnd, true); ax::mojom::Event::kMenuEnd, true);
GetViewAccessibility().EndPopupFocusOverride();
} }
// Fire these kMenuPopupEnd for each menu/submenu that closes/hides. // Fire these kMenuPopupEnd for each menu/submenu that closes/hides.
if (host_->IsVisible()) if (host_->IsVisible())
......
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