Commit 48d8b520 authored by Dominic Mazzoni's avatar Dominic Mazzoni Committed by Commit Bot

Implement accessibility support for views::ScrollView

The Switch Access feature on Chrome OS needs to explicitly control
scrollable containers. Expose the current scroll position via
GetAccessibleNodeData, and implement the required accessible actions
to set the scroll offset or scroll by one page in any direction.

Bug: 1108941
Change-Id: Icaf36b762b6651f04feb497012d7a029d87062ad
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2319545
Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarRobert Liao <robliao@chromium.org>
Reviewed-by: default avatarAnastasia Helfinstein <anastasi@google.com>
Cr-Commit-Position: refs/heads/master@{#792822}
parent 02649e91
......@@ -80,6 +80,7 @@ class AutomationManagerAura : public ui::AXActionHandler,
private:
friend class base::NoDestructor<AutomationManagerAura>;
FRIEND_TEST_ALL_PREFIXES(AutomationManagerAuraBrowserTest, ScrollView);
FRIEND_TEST_ALL_PREFIXES(AutomationManagerAuraBrowserTest, WebAppearsOnce);
AutomationManagerAura();
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
......@@ -16,12 +17,20 @@
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/common/extension_messages.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_event_bundle_sink.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h"
#include "ui/views/accessibility/ax_tree_source_views.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/radio_button.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
namespace {
......@@ -214,3 +223,85 @@ IN_PROC_BROWSER_TEST_F(AutomationManagerAuraBrowserTest,
AddFailureOnWidgetAccessibilityError(widget);
}
IN_PROC_BROWSER_TEST_F(AutomationManagerAuraBrowserTest, ScrollView) {
auto cache = std::make_unique<views::AXAuraObjCache>();
auto* cache_ptr = cache.get();
AutomationManagerAura* manager = AutomationManagerAura::GetInstance();
manager->set_ax_aura_obj_cache_for_testing(std::move(cache));
manager->Enable();
auto* tree = manager->current_tree_.get();
// Create a widget with size 200, 200.
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = {0, 0, 200, 200};
widget->Init(std::move(params));
// Add a ScrollView, with contents consisting of a View of size 1000x2000.
views::View* root_view = widget->GetRootView();
auto orig_scroll_view = std::make_unique<views::ScrollView>();
views::View* scrollable =
orig_scroll_view->SetContents(std::make_unique<views::View>());
scrollable->SetBounds(0, 0, 1000, 2000);
root_view->SetLayoutManager(std::make_unique<views::FlexLayout>())
->SetOrientation(views::LayoutOrientation::kVertical);
auto full_flex =
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
views::MaximumFlexSizeRule::kUnbounded)
.WithWeight(1);
orig_scroll_view->SetProperty(views::kFlexBehaviorKey, full_flex);
views::View* scroll_view =
root_view->AddChildView(std::move(orig_scroll_view));
widget->Show();
widget->Activate();
root_view->GetLayoutManager()->Layout(root_view);
// Get the accessibility data from the scroll view's AXAuraObjCache wrapper.
views::AXAuraObjWrapper* scroll_view_wrapper =
cache_ptr->GetOrCreate(scroll_view);
ui::AXNodeData node_data;
tree->SerializeNode(scroll_view_wrapper, &node_data);
// Allow the scroll offsets to be off by 20 pixels due to platform-specific
// differences.
constexpr int kAllowedError = 20;
// The scroll position should be at the top left and the
// max values should reflect the overall canvas size of (1000, 2000)
// with a window size of (200, 200).
EXPECT_EQ(0, node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollX));
EXPECT_EQ(0, node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollXMin));
EXPECT_NEAR(800,
node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollXMax),
kAllowedError);
EXPECT_EQ(0, node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollY));
EXPECT_EQ(0, node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollYMin));
EXPECT_NEAR(1800,
node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax),
kAllowedError);
// Scroll right and check the X position.
ui::AXActionData action_data;
action_data.action = ax::mojom::Action::kScrollRight;
scroll_view_wrapper->HandleAccessibleAction(action_data);
tree->SerializeNode(scroll_view_wrapper, &node_data);
EXPECT_NEAR(200, node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollX),
kAllowedError);
// Scroll down and check the Y position.
action_data.action = ax::mojom::Action::kScrollDown;
scroll_view_wrapper->HandleAccessibleAction(action_data);
tree->SerializeNode(scroll_view_wrapper, &node_data);
EXPECT_NEAR(200, node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollY),
kAllowedError);
// Scroll to a specific location.
action_data.action = ax::mojom::Action::kSetScrollOffset;
action_data.target_point.SetPoint(50, 315);
scroll_view_wrapper->HandleAccessibleAction(action_data);
tree->SerializeNode(scroll_view_wrapper, &node_data);
EXPECT_EQ(50, node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollX));
EXPECT_EQ(315, node_data.GetIntAttribute(ax::mojom::IntAttribute::kScrollY));
}
......@@ -12,6 +12,9 @@
#include "base/macros.h"
#include "base/numerics/ranges.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/overscroll/scroll_input_handler.h"
#include "ui/events/event.h"
......@@ -641,6 +644,70 @@ void ScrollView::OnThemeChanged() {
UpdateBackground();
}
void ScrollView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
View::GetAccessibleNodeData(node_data);
if (!contents_)
return;
ScrollBar* horizontal = horizontal_scroll_bar();
if (horizontal) {
node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollX,
CurrentOffset().x());
node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollXMin,
horizontal->GetMinPosition());
node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollXMax,
horizontal->GetMaxPosition());
}
ScrollBar* vertical = vertical_scroll_bar();
if (vertical) {
node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollY,
CurrentOffset().y());
node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollYMin,
vertical->GetMinPosition());
node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollYMax,
vertical->GetMaxPosition());
}
if (horizontal || vertical)
node_data->AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, true);
}
bool ScrollView::HandleAccessibleAction(const ui::AXActionData& action_data) {
if (!contents_)
return View::HandleAccessibleAction(action_data);
ScrollBar* horizontal = horizontal_scroll_bar();
ScrollBar* vertical = vertical_scroll_bar();
switch (action_data.action) {
case ax::mojom::Action::kScrollLeft:
if (horizontal)
return horizontal->ScrollByAmount(ScrollBar::ScrollAmount::kPrevPage);
else
return false;
case ax::mojom::Action::kScrollRight:
if (horizontal)
return horizontal->ScrollByAmount(ScrollBar::ScrollAmount::kNextPage);
else
return false;
case ax::mojom::Action::kScrollUp:
if (vertical)
return vertical->ScrollByAmount(ScrollBar::ScrollAmount::kPrevPage);
else
return false;
case ax::mojom::Action::kScrollDown:
if (vertical)
return vertical->ScrollByAmount(ScrollBar::ScrollAmount::kNextPage);
else
return false;
case ax::mojom::Action::kSetScrollOffset:
ScrollToOffset(gfx::ScrollOffset(action_data.target_point.x(),
action_data.target_point.y()));
return true;
default:
return View::HandleAccessibleAction(action_data);
break;
}
}
void ScrollView::ScrollToPosition(ScrollBar* source, int position) {
if (!contents_)
return;
......
......@@ -143,6 +143,8 @@ class VIEWS_EXPORT ScrollView : public View, public ScrollBarController {
void OnScrollEvent(ui::ScrollEvent* event) override;
void OnGestureEvent(ui::GestureEvent* event) override;
void OnThemeChanged() override;
void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
bool HandleAccessibleAction(const ui::AXActionData& action_data) override;
// ScrollBarController overrides:
void ScrollToPosition(ScrollBar* source, int position) override;
......
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