Commit d69d3bec authored by Brian Liu Xu's avatar Brian Liu Xu Committed by Commit Bot

Make AXVirtualView bounds relative to the owner view bounds

This changelist includes the following fixes for virtual views:

- Ensures that virtual view accessible bounds are provided for
  |TreeView| nodes when requested. PopulateAccessibilityData() is
  responsible for providing the values, but it was being bypassed in
  GetBoundsRect() and HitTestSync(), which were accessing
  |custom_data_| instead of calling GetData().

- Converts the virtual view accessible bounds to device pixels by
  scaling it by the device scale factor.

- Makes virtual view accessible bounds robust to changes in the owner
  view's bounds in screen. This can happen if the host window was moved
  or resized.

Bug: 811277
Change-Id: Icb9379e635e8c09d299a9022f24714f32ffed150
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2129987Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarKevin Babbitt <kbabbitt@microsoft.com>
Commit-Queue: Brian Liu Xu <brx@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#757448}
parent edcaf9fc
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <utility> #include <utility>
#include "base/callback.h" #include "base/callback.h"
#include "base/containers/adapters.h"
#include "base/no_destructor.h" #include "base/no_destructor.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_action_data.h"
...@@ -236,6 +237,15 @@ const ui::AXNodeData& AXVirtualView::GetData() const { ...@@ -236,6 +237,15 @@ const ui::AXNodeData& AXVirtualView::GetData() const {
if (populate_data_callback_ && GetOwnerView()) if (populate_data_callback_ && GetOwnerView())
populate_data_callback_.Run(&node_data); populate_data_callback_.Run(&node_data);
// According to the ARIA spec, the node should not be ignored if it is
// focusable. This is to ensure that the focusable node is both understandable
// and operable.
if (node_data.HasState(ax::mojom::State::kIgnored) &&
node_data.HasState(ax::mojom::State::kFocusable)) {
node_data.RemoveState(ax::mojom::State::kIgnored);
}
return node_data; return node_data;
} }
...@@ -302,12 +312,24 @@ gfx::Rect AXVirtualView::GetBoundsRect( ...@@ -302,12 +312,24 @@ gfx::Rect AXVirtualView::GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system, const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior, const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const { ui::AXOffscreenResult* offscreen_result) const {
// We could optionally add clipping here if ever needed.
// TODO(nektar): Implement bounds that are relative to the parent.
gfx::Rect bounds = gfx::ToEnclosingRect(GetData().relative_bounds.bounds);
View* owner_view = GetOwnerView();
if (owner_view && owner_view->GetWidget())
View::ConvertRectToScreen(owner_view, &bounds);
switch (coordinate_system) { switch (coordinate_system) {
case ui::AXCoordinateSystem::kScreenDIPs: case ui::AXCoordinateSystem::kScreenDIPs:
// We could optionally add clipping here if ever needed. return bounds;
// TODO(nektar): Implement bounds that are relative to the parent. case ui::AXCoordinateSystem::kScreenPhysicalPixels: {
return gfx::ToEnclosingRect(custom_data_.relative_bounds.bounds); float scale_factor = 1.0;
case ui::AXCoordinateSystem::kScreenPhysicalPixels: if (owner_view && owner_view->GetWidget()) {
gfx::NativeView native_view = owner_view->GetWidget()->GetNativeView();
if (native_view)
scale_factor = ui::GetScaleFactorForNativeView(native_view);
}
return gfx::ScaleToEnclosingRect(bounds, scale_factor);
}
case ui::AXCoordinateSystem::kRootFrame: case ui::AXCoordinateSystem::kRootFrame:
case ui::AXCoordinateSystem::kFrame: case ui::AXCoordinateSystem::kFrame:
NOTIMPLEMENTED(); NOTIMPLEMENTED();
...@@ -318,27 +340,39 @@ gfx::Rect AXVirtualView::GetBoundsRect( ...@@ -318,27 +340,39 @@ gfx::Rect AXVirtualView::GetBoundsRect(
gfx::NativeViewAccessible AXVirtualView::HitTestSync( gfx::NativeViewAccessible AXVirtualView::HitTestSync(
int screen_physical_pixel_x, int screen_physical_pixel_x,
int screen_physical_pixel_y) const { int screen_physical_pixel_y) const {
if (custom_data_.relative_bounds.bounds.Contains( const ui::AXNodeData& node_data = GetData();
static_cast<float>(screen_physical_pixel_x), if (node_data.HasState(ax::mojom::State::kInvisible))
static_cast<float>(screen_physical_pixel_y))) { return nullptr;
if (!IsIgnored())
return GetNativeObject();
}
// Check if the point is within any of the virtual children of this view. // Check if the point is within any of the virtual children of this view.
// AXVirtualView's HitTestSync is a recursive function that will return the // AXVirtualView's HitTestSync is a recursive function that will return the
// deepest child, since it does not support relative bounds. // deepest child, since it does not support relative bounds.
for (const std::unique_ptr<AXVirtualView>& child : children_) { // Search the greater indices first, since they're on top in the z-order.
for (const std::unique_ptr<AXVirtualView>& child :
base::Reversed(children_)) {
gfx::NativeViewAccessible result = gfx::NativeViewAccessible result =
child->HitTestSync(screen_physical_pixel_x, screen_physical_pixel_y); child->HitTestSync(screen_physical_pixel_x, screen_physical_pixel_y);
if (result) if (result)
return result; return result;
} }
// If it's not inside any of our virtual children, and it's inside the bounds
// of this virtual view, then it's inside this virtual view.
gfx::Rect bounds_in_screen_physical_pixels =
GetBoundsRect(ui::AXCoordinateSystem::kScreenPhysicalPixels,
ui::AXClippingBehavior::kUnclipped);
if (bounds_in_screen_physical_pixels.Contains(
static_cast<float>(screen_physical_pixel_x),
static_cast<float>(screen_physical_pixel_y)) &&
!node_data.IsIgnored()) {
return GetNativeObject();
}
return nullptr; return nullptr;
} }
gfx::NativeViewAccessible AXVirtualView::GetFocus() { gfx::NativeViewAccessible AXVirtualView::GetFocus() {
auto* owner_view = GetOwnerView(); View* owner_view = GetOwnerView();
if (owner_view) { if (owner_view) {
if (!(owner_view->HasFocus())) { if (!(owner_view->HasFocus())) {
return nullptr; return nullptr;
...@@ -389,15 +423,7 @@ gfx::AcceleratedWidget AXVirtualView::GetTargetForNativeAccessibilityEvent() { ...@@ -389,15 +423,7 @@ gfx::AcceleratedWidget AXVirtualView::GetTargetForNativeAccessibilityEvent() {
} }
bool AXVirtualView::IsIgnored() const { bool AXVirtualView::IsIgnored() const {
const ui::AXNodeData& node_data = GetData(); return GetData().IsIgnored();
// According to the ARIA spec, the node should not be ignored if it is
// focusable. This is to ensure that the focusable node is both understandable
// and operable.
if (node_data.HasState(ax::mojom::State::kFocusable))
return false;
return node_data.IsIgnored();
} }
bool AXVirtualView::HandleAccessibleAction( bool AXVirtualView::HandleAccessibleAction(
......
...@@ -142,7 +142,7 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase { ...@@ -142,7 +142,7 @@ class VIEWS_EXPORT AXVirtualView : public ui::AXPlatformNodeDelegateBase {
gfx::Rect GetBoundsRect( gfx::Rect GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system, const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior, const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const override; ui::AXOffscreenResult* offscreen_result = nullptr) const override;
gfx::NativeViewAccessible HitTestSync( gfx::NativeViewAccessible HitTestSync(
int screen_physical_pixel_x, int screen_physical_pixel_x,
int screen_physical_pixel_y) const override; int screen_physical_pixel_y) const override;
......
...@@ -626,6 +626,51 @@ TEST_F(AXVirtualViewTest, Navigation) { ...@@ -626,6 +626,51 @@ TEST_F(AXVirtualViewTest, Navigation) {
EXPECT_EQ(0, virtual_child_4->GetIndexInParent()); EXPECT_EQ(0, virtual_child_4->GetIndexInParent());
} }
TEST_F(AXVirtualViewTest, HitTesting) {
ASSERT_EQ(0, virtual_label_->GetChildCount());
const gfx::Vector2d offset_from_origin =
button_->GetBoundsInScreen().OffsetFromOrigin();
// Test that hit testing is recursive.
AXVirtualView* virtual_child_1 = new AXVirtualView;
virtual_child_1->GetCustomData().relative_bounds.bounds =
gfx::RectF(0, 0, 10, 10);
virtual_label_->AddChildView(base::WrapUnique(virtual_child_1));
AXVirtualView* virtual_child_2 = new AXVirtualView;
virtual_child_2->GetCustomData().relative_bounds.bounds =
gfx::RectF(5, 5, 5, 5);
virtual_child_1->AddChildView(base::WrapUnique(virtual_child_2));
gfx::Point point_1 = gfx::Point(2, 2) + offset_from_origin;
EXPECT_EQ(virtual_child_1->GetNativeObject(),
virtual_child_1->HitTestSync(point_1.x(), point_1.y()));
gfx::Point point_2 = gfx::Point(7, 7) + offset_from_origin;
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->HitTestSync(point_2.x(), point_2.y()));
// Test that hit testing follows the z-order.
AXVirtualView* virtual_child_3 = new AXVirtualView;
virtual_child_3->GetCustomData().relative_bounds.bounds =
gfx::RectF(5, 5, 10, 10);
virtual_label_->AddChildView(base::WrapUnique(virtual_child_3));
AXVirtualView* virtual_child_4 = new AXVirtualView;
virtual_child_4->GetCustomData().relative_bounds.bounds =
gfx::RectF(10, 10, 10, 10);
virtual_child_3->AddChildView(base::WrapUnique(virtual_child_4));
EXPECT_EQ(virtual_child_3->GetNativeObject(),
virtual_label_->HitTestSync(point_2.x(), point_2.y()));
gfx::Point point_3 = gfx::Point(12, 12) + offset_from_origin;
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_label_->HitTestSync(point_3.x(), point_3.y()));
// Test that hit testing skips ignored nodes but not their descendants.
virtual_child_3->GetCustomData().AddState(ax::mojom::State::kIgnored);
EXPECT_EQ(virtual_child_2->GetNativeObject(),
virtual_label_->HitTestSync(point_2.x(), point_2.y()));
EXPECT_EQ(virtual_child_4->GetNativeObject(),
virtual_label_->HitTestSync(point_3.x(), point_3.y()));
}
// Test for GetTargetForNativeAccessibilityEvent(). // Test for GetTargetForNativeAccessibilityEvent().
#if defined(OS_WIN) #if defined(OS_WIN)
TEST_F(AXVirtualViewTest, GetTargetForEvents) { TEST_F(AXVirtualViewTest, GetTargetForEvents) {
......
...@@ -1422,28 +1422,33 @@ void TableView::UpdateVirtualAccessibilityChildrenBounds() { ...@@ -1422,28 +1422,33 @@ void TableView::UpdateVirtualAccessibilityChildrenBounds() {
} }
gfx::Rect TableView::CalculateHeaderRowAccessibilityBounds() const { gfx::Rect TableView::CalculateHeaderRowAccessibilityBounds() const {
return AdjustRectForAXRelativeBounds(header_->GetVisibleBounds()); gfx::Rect header_bounds = header_->GetVisibleBounds();
gfx::Point header_origin = header_bounds.origin();
ConvertPointToTarget(header_, this, &header_origin);
header_bounds.set_origin(header_origin);
return header_bounds;
} }
gfx::Rect TableView::CalculateHeaderCellAccessibilityBounds( gfx::Rect TableView::CalculateHeaderCellAccessibilityBounds(
const int visible_column_index) const { const int visible_column_index) const {
const gfx::Rect& header_bounds = CalculateHeaderRowAccessibilityBounds();
const VisibleColumn& visible_column = visible_columns_[visible_column_index]; const VisibleColumn& visible_column = visible_columns_[visible_column_index];
gfx::Rect header_cell_bounds(visible_column.x, header_->y(), gfx::Rect header_cell_bounds(visible_column.x, header_bounds.y(),
visible_column.width, header_->height()); visible_column.width, header_bounds.height());
return AdjustRectForAXRelativeBounds(header_cell_bounds); return header_cell_bounds;
} }
gfx::Rect TableView::CalculateTableRowAccessibilityBounds( gfx::Rect TableView::CalculateTableRowAccessibilityBounds(
const int row_index) const { const int row_index) const {
gfx::Rect row_bounds = GetRowBounds(row_index); gfx::Rect row_bounds = GetRowBounds(row_index);
return AdjustRectForAXRelativeBounds(row_bounds); return row_bounds;
} }
gfx::Rect TableView::CalculateTableCellAccessibilityBounds( gfx::Rect TableView::CalculateTableCellAccessibilityBounds(
const int row_index, const int row_index,
const int visible_column_index) const { const int visible_column_index) const {
gfx::Rect cell_bounds = GetCellBounds(row_index, visible_column_index); gfx::Rect cell_bounds = GetCellBounds(row_index, visible_column_index);
return AdjustRectForAXRelativeBounds(cell_bounds); return cell_bounds;
} }
void TableView::UpdateAccessibilityFocus() { void TableView::UpdateAccessibilityFocus() {
...@@ -1510,13 +1515,6 @@ AXVirtualView* TableView::GetVirtualAccessibilityCell( ...@@ -1510,13 +1515,6 @@ AXVirtualView* TableView::GetVirtualAccessibilityCell(
return i->get(); return i->get();
} }
gfx::Rect TableView::AdjustRectForAXRelativeBounds(
const gfx::Rect& rect) const {
gfx::Rect converted_rect = rect;
View::ConvertRectToScreen(this, &converted_rect);
return converted_rect;
}
DEFINE_ENUM_CONVERTERS(TableTypes, DEFINE_ENUM_CONVERTERS(TableTypes,
{TableTypes::TEXT_ONLY, base::ASCIIToUTF16("TEXT_ONLY")}, {TableTypes::TEXT_ONLY, base::ASCIIToUTF16("TEXT_ONLY")},
{TableTypes::ICON_AND_TEXT, {TableTypes::ICON_AND_TEXT,
......
...@@ -383,11 +383,6 @@ class VIEWS_EXPORT TableView : public views::View, ...@@ -383,11 +383,6 @@ class VIEWS_EXPORT TableView : public views::View,
// |visible_column_index| indexes into |visible_columns_|. // |visible_column_index| indexes into |visible_columns_|.
AXVirtualView* GetVirtualAccessibilityCell(int row, int visible_column_index); AXVirtualView* GetVirtualAccessibilityCell(int row, int visible_column_index);
// Returns |rect|, adjusted for use in AXRelativeBounds by translating it into
// screen coordinates. The result must be converted to gfx::RectF when setting
// into AXRelativeBounds.
gfx::Rect AdjustRectForAXRelativeBounds(const gfx::Rect& rect) const;
ui::TableModel* model_ = nullptr; ui::TableModel* model_ = nullptr;
std::vector<ui::TableColumn> columns_; std::vector<ui::TableColumn> columns_;
......
...@@ -80,23 +80,33 @@ class TableViewTestHelper { ...@@ -80,23 +80,33 @@ class TableViewTestHelper {
// Generate the bounds for the header row and cells. // Generate the bounds for the header row and cells.
auto header_row = std::vector<gfx::Rect>(); auto header_row = std::vector<gfx::Rect>();
header_row.push_back(table_->CalculateHeaderRowAccessibilityBounds()); gfx::Rect header_row_bounds =
table_->CalculateHeaderRowAccessibilityBounds();
View::ConvertRectToScreen(table_, &header_row_bounds);
header_row.push_back(header_row_bounds);
for (size_t column_index = 0; column_index < visible_col_count(); for (size_t column_index = 0; column_index < visible_col_count();
column_index++) { column_index++) {
header_row.push_back( gfx::Rect header_cell_bounds =
table_->CalculateHeaderCellAccessibilityBounds(column_index)); table_->CalculateHeaderCellAccessibilityBounds(column_index);
View::ConvertRectToScreen(table_, &header_cell_bounds);
header_row.push_back(header_cell_bounds);
} }
expected_bounds.push_back(header_row); expected_bounds.push_back(header_row);
// Generate the bounds for the table rows and cells. // Generate the bounds for the table rows and cells.
for (int row_index = 0; row_index < table_->GetRowCount(); row_index++) { for (int row_index = 0; row_index < table_->GetRowCount(); row_index++) {
auto table_row = std::vector<gfx::Rect>(); auto table_row = std::vector<gfx::Rect>();
table_row.push_back( gfx::Rect table_row_bounds =
table_->CalculateTableRowAccessibilityBounds(row_index)); table_->CalculateTableRowAccessibilityBounds(row_index);
View::ConvertRectToScreen(table_, &table_row_bounds);
table_row.push_back(table_row_bounds);
for (size_t column_index = 0; column_index < visible_col_count(); for (size_t column_index = 0; column_index < visible_col_count();
column_index++) { column_index++) {
table_row.push_back(table_->CalculateTableCellAccessibilityBounds( gfx::Rect table_cell_bounds =
row_index, column_index)); table_->CalculateTableCellAccessibilityBounds(row_index,
column_index);
View::ConvertRectToScreen(table_, &table_cell_bounds);
table_row.push_back(table_cell_bounds);
} }
expected_bounds.push_back(table_row); expected_bounds.push_back(table_row);
} }
......
...@@ -922,7 +922,6 @@ void TreeView::PopulateAccessibilityData(InternalNode* node, ...@@ -922,7 +922,6 @@ void TreeView::PopulateAccessibilityData(InternalNode* node,
data->AddAction(ax::mojom::Action::kFocus); data->AddAction(ax::mojom::Action::kFocus);
data->AddAction(ax::mojom::Action::kScrollToMakeVisible); data->AddAction(ax::mojom::Action::kScrollToMakeVisible);
gfx::Rect node_bounds = GetBackgroundBoundsForNode(node); gfx::Rect node_bounds = GetBackgroundBoundsForNode(node);
View::ConvertRectToScreen(this, &node_bounds);
data->relative_bounds.bounds = gfx::RectF(node_bounds); data->relative_bounds.bounds = gfx::RectF(node_bounds);
} else { } else {
data->AddState(ax::mojom::State::kInvisible); data->AddState(ax::mojom::State::kInvisible);
......
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