Chromium Code Reviews| Index: ui/accessibility/platform/ax_android_snapshot.cc |
| diff --git a/ui/accessibility/platform/ax_android_snapshot.cc b/ui/accessibility/platform/ax_android_snapshot.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b73d46705d242a1a33443a7f71b42200c4e3c708 |
| --- /dev/null |
| +++ b/ui/accessibility/platform/ax_android_snapshot.cc |
| @@ -0,0 +1,256 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "ui/accessibility/platform/ax_android_snapshot.h" |
| + |
| +#include <memory> |
| +#include <string> |
| + |
| +#include "base/logging.h" |
| +#include "base/memory/ptr_util.h" |
| +#include "ui/accessibility/ax_enums.h" |
| +#include "ui/accessibility/ax_node.h" |
| +#include "ui/accessibility/ax_serializable_tree.h" |
| +#include "ui/accessibility/platform/ax_android_constants.h" |
| +#include "ui/gfx/geometry/rect_conversions.h" |
| +#include "ui/gfx/transform.h" |
| + |
| +namespace ui { |
| + |
| +namespace { |
| + |
| +std::string GetClassName(const AXNode* node) { |
|
dmazzoni
2017/04/20 21:04:07
This code is all copied from browser_accessibility
Muyuan
2017/04/21 18:45:12
Done.
|
| + switch (node->data().role) { |
| + case ui::AX_ROLE_SEARCH_BOX: |
| + case ui::AX_ROLE_SPIN_BUTTON: |
| + case ui::AX_ROLE_TEXT_FIELD: |
| + return ui::kAXEditTextClassname; |
| + case ui::AX_ROLE_SLIDER: |
| + return ui::kAXSeekBarClassname; |
| + case ui::AX_ROLE_COLOR_WELL: |
| + case ui::AX_ROLE_COMBO_BOX: |
| + case ui::AX_ROLE_DATE: |
| + case ui::AX_ROLE_POP_UP_BUTTON: |
| + case ui::AX_ROLE_INPUT_TIME: |
| + return ui::kAXSpinnerClassname; |
| + case ui::AX_ROLE_BUTTON: |
| + case ui::AX_ROLE_MENU_BUTTON: |
| + return ui::kAXButtonClassname; |
| + case ui::AX_ROLE_CHECK_BOX: |
| + case ui::AX_ROLE_SWITCH: |
| + return ui::kAXCheckBoxClassname; |
| + case ui::AX_ROLE_RADIO_BUTTON: |
| + return ui::kAXRadioButtonClassname; |
| + case ui::AX_ROLE_TOGGLE_BUTTON: |
| + return ui::kAXToggleButtonClassname; |
| + case ui::AX_ROLE_CANVAS: |
| + case ui::AX_ROLE_IMAGE: |
| + case ui::AX_ROLE_SVG_ROOT: |
| + return ui::kAXImageClassname; |
| + case ui::AX_ROLE_METER: |
| + case ui::AX_ROLE_PROGRESS_INDICATOR: |
| + return ui::kAXProgressBarClassname; |
| + case ui::AX_ROLE_TAB_LIST: |
| + return ui::kAXTabWidgetClassname; |
| + case ui::AX_ROLE_GRID: |
| + case ui::AX_ROLE_TABLE: |
| + return ui::kAXGridViewClassname; |
| + case ui::AX_ROLE_LIST: |
| + case ui::AX_ROLE_LIST_BOX: |
| + case ui::AX_ROLE_DESCRIPTION_LIST: |
| + return ui::kAXListViewClassname; |
| + case ui::AX_ROLE_DIALOG: |
| + return ui::kAXDialogClassname; |
| + case ui::AX_ROLE_ROOT_WEB_AREA: |
| + return node->parent() == nullptr ? ui::kAXWebViewClassname |
| + : ui::kAXViewClassname; |
| + case ui::AX_ROLE_MENU_ITEM: |
| + case ui::AX_ROLE_MENU_ITEM_CHECK_BOX: |
| + case ui::AX_ROLE_MENU_ITEM_RADIO: |
| + return ui::kAXMenuItemClassname; |
| + default: |
| + return ui::kAXViewClassname; |
| + } |
| +} |
| + |
| +base::string16 GetText(const AXNode* node) { |
| + if (node->IsTextNode()) { |
| + return node->data().GetString16Attribute(ui::AX_ATTR_NAME); |
| + } |
| + base::string16 text; |
| + for (auto* child : node->children()) { |
| + text += GetText(child); |
| + } |
| + return text; |
| +} |
| + |
| +bool HasFocusableChild(const AXNode* node) { |
| + for (auto* child : node->children()) { |
| + if ((child->data().state & ui::AX_STATE_FOCUSABLE) != 0 || |
| + HasFocusableChild(child)) { |
| + return true; |
| + } |
| + } |
| + return false; |
| +} |
| + |
| +bool IsLeaf(const AXNode* node) { |
| + if (node->child_count() == 0) |
| + return true; |
| + |
| + if (node->IsTextNode()) { |
| + return true; |
| + } |
| + |
| + switch (node->data().role) { |
| + case ui::AX_ROLE_IMAGE: |
| + case ui::AX_ROLE_METER: |
| + case ui::AX_ROLE_SCROLL_BAR: |
| + case ui::AX_ROLE_SLIDER: |
| + case ui::AX_ROLE_SPLITTER: |
| + case ui::AX_ROLE_PROGRESS_INDICATOR: |
| + case ui::AX_ROLE_DATE: |
| + case ui::AX_ROLE_DATE_TIME: |
| + case ui::AX_ROLE_INPUT_TIME: |
| + return true; |
| + default: |
| + return false; |
| + } |
| +} |
| + |
| +gfx::Rect RelativeToAbsoluteBounds(const AXNode* node, |
| + gfx::RectF bounds, |
| + const AXTree* tree) { |
| + const AXNode* current = node; |
| + while (current != nullptr) { |
| + if (current->data().transform) |
| + current->data().transform->TransformRect(&bounds); |
| + auto* container = tree->GetFromId(current->data().offset_container_id); |
| + if (!container) { |
| + if (current == tree->root()) |
| + container = current->parent(); |
| + else |
| + container = tree->root(); |
| + } |
| + if (!container || container == current) |
| + break; |
| + |
| + gfx::RectF container_bounds = container->data().location; |
| + bounds.Offset(container_bounds.x(), container_bounds.y()); |
| + current = container; |
| + } |
| + return gfx::ToEnclosingRect(bounds); |
| +} |
| + |
| +void FixEmptyBounds(const AXNode* node, gfx::RectF* bounds, |
| + const AXTree* tree); |
| + |
| +gfx::Rect GetPageBoundsRect(const AXNode* node, const AXTree* tree) { |
| + gfx::RectF bounds = node->data().location; |
| + FixEmptyBounds(node, &bounds, tree); |
| + return RelativeToAbsoluteBounds(node, bounds, tree); |
| +} |
| + |
| +void FixEmptyBounds(const AXNode* node, |
| + gfx::RectF* bounds, |
| + const AXTree* tree) { |
| + if (bounds->width() > 0 && bounds->height() > 0) |
| + return; |
| + for (auto* child : node->children()) { |
| + gfx::Rect child_bounds = GetPageBoundsRect(child, tree); |
| + if (child_bounds.width() == 0 || child_bounds.height() == 0) |
| + continue; |
| + if (bounds->width() == 0 || bounds->height() == 0) { |
| + *bounds = gfx::RectF(child_bounds); |
| + continue; |
| + } |
| + bounds->Union(gfx::RectF(child_bounds)); |
| + } |
| +} |
| + |
| +std::unique_ptr<AXSnapshotNodeAndroid> WalkAXTreeDepthFirst( |
| + const AXNode* node, |
| + gfx::Rect rect, |
| + const ui::AXTreeUpdate& update, |
| + const AXTree* tree) { |
| + auto result = base::MakeUnique<AXSnapshotNodeAndroid>(); |
| + if (node->IsTextNode()) |
| + result->text = GetText(node); |
| + result->class_name = GetClassName(node); |
| + |
| + result->text_size = -1.0; |
| + result->bgcolor = 0; |
| + result->color = 0; |
| + result->bold = 0; |
| + result->italic = 0; |
| + result->line_through = 0; |
| + result->underline = 0; |
| + |
| + if (node->data().HasFloatAttribute(ui::AX_ATTR_FONT_SIZE)) { |
| + gfx::RectF text_size_rect( |
| + 0, 0, 1, node->data().GetFloatAttribute(ui::AX_ATTR_FONT_SIZE)); |
| + gfx::Rect scaled_text_size_rect = |
| + RelativeToAbsoluteBounds(node, text_size_rect, tree); |
| + result->text_size = scaled_text_size_rect.height(); |
| + |
| + const int text_style = node->data().GetIntAttribute(ui::AX_ATTR_TEXT_STYLE); |
| + result->color = node->data().GetIntAttribute(ui::AX_ATTR_COLOR); |
| + result->bgcolor = |
| + node->data().GetIntAttribute(ui::AX_ATTR_BACKGROUND_COLOR); |
| + result->bold = (text_style & ui::AX_TEXT_STYLE_BOLD) != 0; |
| + result->italic = (text_style & ui::AX_TEXT_STYLE_ITALIC) != 0; |
| + result->line_through = (text_style & ui::AX_TEXT_STYLE_LINE_THROUGH) != 0; |
| + result->underline = (text_style & ui::AX_TEXT_STYLE_ITALIC) != 0; |
| + } |
| + |
| + const gfx::Rect& absolute_rect = GetPageBoundsRect(node, tree); |
| + gfx::Rect parent_relative_rect = absolute_rect; |
| + bool is_root = node->parent() == nullptr; |
| + if (!is_root) { |
| + parent_relative_rect.Offset(-rect.OffsetFromOrigin()); |
| + } |
| + result->rect = gfx::Rect(parent_relative_rect.x(), parent_relative_rect.y(), |
| + absolute_rect.width(), absolute_rect.height()); |
| + result->has_selection = false; |
| + |
| + if (IsLeaf(node)) { |
| + int start_selection = 0; |
| + int end_selection = 0; |
| + if (update.tree_data.sel_anchor_object_id == node->data().id) { |
| + start_selection = update.tree_data.sel_anchor_offset; |
| + end_selection = GetText(node).length(); |
| + } |
| + if (update.tree_data.sel_focus_object_id == node->data().id) { |
| + end_selection = update.tree_data.sel_focus_offset; |
| + } |
| + if (end_selection > 0) { |
| + result->has_selection = true; |
| + result->start_selection = start_selection; |
| + result->end_selection = end_selection; |
| + } |
| + } |
| + |
| + for (auto* child : node->children()) { |
| + result->children.push_back( |
| + WalkAXTreeDepthFirst(child, absolute_rect, update, tree)); |
| + } |
| + |
| + return result; |
| +} |
| + |
| +} // namespace |
| + |
| +AX_EXPORT AXSnapshotNodeAndroid::AXSnapshotNodeAndroid() = default; |
| +AX_EXPORT AXSnapshotNodeAndroid::~AXSnapshotNodeAndroid() = default; |
| + |
| +AX_EXPORT std::unique_ptr<AXSnapshotNodeAndroid> AXSnapshotNodeAndroid::Create( |
| + const AXTreeUpdate& update) { |
| + auto tree = base::MakeUnique<ui::AXSerializableTree>(); |
| + if (!tree->Unserialize(update)) { |
| + LOG(FATAL) << tree->error(); |
| + } |
| + return WalkAXTreeDepthFirst(tree->root(), gfx::Rect(), update, tree.get()); |
| +} |
| + |
| +} // namespace ui |