blob: 11a2e791aa876d6ad72049800ab19d11a9241f3b [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/accessibility/test_ax_node_helper.h"
#include <map>
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/ax_table_info.h"
#include "ui/accessibility/ax_tree_observer.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace ui {
// static
std::unique_ptr<TestAXNodeHelper> TestAXNodeHelper::Create(AXTree* tree,
AXNode* node) {
if (!tree || !node) {
return nullptr;
}
auto helper =
std::unique_ptr<TestAXNodeHelper>(new TestAXNodeHelper(tree, node));
return helper;
}
TestAXNodeHelper::TestAXNodeHelper(AXTree* tree, AXNode* node)
: tree_(tree), node_(node) {}
TestAXNodeHelper::~TestAXNodeHelper() = default;
gfx::Rect TestAXNodeHelper::GetBoundsRect(
const AXCoordinateSystem coordinate_system,
const AXClippingBehavior clipping_behavior,
AXOffscreenResult* offscreen_result) const {
switch (coordinate_system) {
case AXCoordinateSystem::kScreenPhysicalPixels:
// For unit testing purposes, assume a device scale factor of 1 and fall
// through.
case AXCoordinateSystem::kScreenDIPs: {
// We could optionally add clipping here if ever needed.
gfx::RectF bounds = GetLocation();
// For test behavior only, for bounds that are offscreen we currently do
// not apply clipping to the bounds but we still return the offscreen
// status.
if (offscreen_result) {
*offscreen_result = DetermineOffscreenResult(bounds);
}
return gfx::ToEnclosingRect(bounds);
}
case AXCoordinateSystem::kRootFrame:
case AXCoordinateSystem::kFrame:
NOTIMPLEMENTED();
return gfx::Rect();
}
}
gfx::Rect TestAXNodeHelper::GetInnerTextRangeBoundsRect(
const int start_offset,
const int end_offset,
const AXCoordinateSystem coordinate_system,
const AXClippingBehavior clipping_behavior,
AXOffscreenResult* offscreen_result) const {
switch (coordinate_system) {
case AXCoordinateSystem::kScreenPhysicalPixels:
// For unit testing purposes, assume a device scale factor of 1 and fall
// through.
case AXCoordinateSystem::kScreenDIPs: {
gfx::RectF bounds = GetLocation();
// This implementation currently only deals with text node that has role
// kInlineTextBox and kStaticText.
// For test purposes, assume node with kStaticText always has a single
// child with role kInlineTextBox.
if (node_->GetRole() == ax::mojom::Role::kInlineTextBox) {
bounds = GetInlineTextRect(start_offset, end_offset);
} else if (node_->GetRole() == ax::mojom::Role::kStaticText &&
InternalChildCount() > 0) {
auto child = InternalGetChild(0);
if (child &&
child->node_->GetRole() == ax::mojom::Role::kInlineTextBox) {
bounds = child->GetInlineTextRect(start_offset, end_offset);
}
}
// For test behavior only, for bounds that are offscreen we currently do
// not apply clipping to the bounds but we still return the offscreen
// status.
if (offscreen_result) {
*offscreen_result = DetermineOffscreenResult(bounds);
}
return gfx::ToEnclosingRect(bounds);
}
case AXCoordinateSystem::kRootFrame:
case AXCoordinateSystem::kFrame:
NOTIMPLEMENTED();
return gfx::Rect();
}
}
const AXNodeData& TestAXNodeHelper::GetData() const {
return node_->data();
}
gfx::RectF TestAXNodeHelper::GetLocation() const {
return GetData().relative_bounds.bounds;
}
int TestAXNodeHelper::InternalChildCount() const {
return static_cast<int>(node_->GetUnignoredChildCount());
}
std::unique_ptr<TestAXNodeHelper> TestAXNodeHelper::InternalGetChild(
int index) const {
CHECK_GE(index, 0);
CHECK_LT(index, InternalChildCount());
return Create(tree_,
node_->GetUnignoredChildAtIndex(static_cast<size_t>(index)));
}
gfx::RectF TestAXNodeHelper::GetInlineTextRect(const int start_offset,
const int end_offset) const {
DCHECK(start_offset >= 0 && end_offset >= 0 && start_offset <= end_offset);
const std::vector<int32_t>& character_offsets = node_->GetIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets);
gfx::RectF location = GetLocation();
gfx::RectF bounds;
switch (static_cast<ax::mojom::WritingDirection>(
node_->GetIntAttribute(ax::mojom::IntAttribute::kTextDirection))) {
// Currently only kNone and kLtr are supported text direction.
case ax::mojom::WritingDirection::kNone:
case ax::mojom::WritingDirection::kLtr: {
int start_pixel_offset =
start_offset > 0 ? character_offsets[start_offset - 1] : location.x();
int end_pixel_offset =
end_offset > 0 ? character_offsets[end_offset - 1] : location.x();
bounds =
gfx::RectF(start_pixel_offset, location.y(),
end_pixel_offset - start_pixel_offset, location.height());
break;
}
default:
NOTIMPLEMENTED();
}
return bounds;
}
bool TestAXNodeHelper::Intersects(gfx::RectF rect1, gfx::RectF rect2) const {
// The logic below is based on gfx::RectF::Intersects.
// gfx::RectF::Intersects returns false if either of the two rects is empty.
// This function is used in tests to determine offscreen status. We want to
// include empty rect in our logic since the bounding box of a degenerate text
// range is initially empty (width=0), and we do not want to mark it as
// offscreen.
return rect1.x() < rect2.right() && rect1.right() > rect2.x() &&
rect1.y() < rect2.bottom() && rect1.bottom() > rect2.y();
}
AXOffscreenResult TestAXNodeHelper::DetermineOffscreenResult(
gfx::RectF bounds) const {
if (!tree_ || !tree_->root())
return AXOffscreenResult::kOnscreen;
const AXNodeData& root_web_area_node_data = tree_->root()->data();
gfx::RectF root_web_area_bounds =
root_web_area_node_data.relative_bounds.bounds;
// For testing, we only look at the current node's bound relative to the root
// web area bounds to determine offscreen status. We currently do not look at
// the bounds of the immediate parent of the node for determining offscreen
// status.
// We only determine offscreen result if the root web area bounds is actually
// set in the test, and we mark a node as offscreen only when |bounds| is
// completely outside of |root_web_area_bounds| (i.e. not contained by
// |root_web_area_bounds|). We default the offscreen result of every other
// situation to AXOffscreenResult::kOnscreen.
if (!root_web_area_bounds.IsEmpty() &&
!Intersects(bounds, root_web_area_bounds)) {
return AXOffscreenResult::kOffscreen;
}
return AXOffscreenResult::kOnscreen;
}
} // namespace ui