| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h" | 5 #include "chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "chrome/browser/extensions/api/automation_internal/automation_event_rou
ter.h" | 9 #include "chrome/browser/extensions/api/automation_internal/automation_event_rou
ter.h" |
| 10 #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h" | 10 #include "chrome/browser/ui/aura/accessibility/automation_manager_aura.h" |
| 11 #include "chrome/common/extensions/chrome_extension_messages.h" | 11 #include "chrome/common/extensions/chrome_extension_messages.h" |
| 12 #include "components/exo/wm_helper.h" | 12 #include "components/exo/wm_helper.h" |
| 13 #include "ui/accessibility/platform/ax_android_constants.h" |
| 13 #include "ui/aura/window.h" | 14 #include "ui/aura/window.h" |
| 14 | 15 |
| 15 namespace { | 16 namespace { |
| 16 | 17 |
| 17 ui::AXEvent ToAXEvent(arc::mojom::AccessibilityEventType arc_event_type) { | 18 ui::AXEvent ToAXEvent(arc::mojom::AccessibilityEventType arc_event_type) { |
| 18 switch (arc_event_type) { | 19 switch (arc_event_type) { |
| 19 case arc::mojom::AccessibilityEventType::VIEW_FOCUSED: | 20 case arc::mojom::AccessibilityEventType::VIEW_FOCUSED: |
| 20 case arc::mojom::AccessibilityEventType::VIEW_ACCESSIBILITY_FOCUSED: | 21 case arc::mojom::AccessibilityEventType::VIEW_ACCESSIBILITY_FOCUSED: |
| 21 return ui::AX_EVENT_FOCUS; | 22 return ui::AX_EVENT_FOCUS; |
| 22 case arc::mojom::AccessibilityEventType::VIEW_ACCESSIBILITY_FOCUS_CLEARED: | 23 case arc::mojom::AccessibilityEventType::VIEW_ACCESSIBILITY_FOCUS_CLEARED: |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 63 gfx::Rect bounds_in_screen = node->boundsInScreen; | 64 gfx::Rect bounds_in_screen = node->boundsInScreen; |
| 64 if (focused_window) { | 65 if (focused_window) { |
| 65 aura::Window* toplevel_window = focused_window->GetToplevelWindow(); | 66 aura::Window* toplevel_window = focused_window->GetToplevelWindow(); |
| 66 return gfx::ScaleToEnclosingRect( | 67 return gfx::ScaleToEnclosingRect( |
| 67 bounds_in_screen, | 68 bounds_in_screen, |
| 68 1.0f / toplevel_window->layer()->device_scale_factor()); | 69 1.0f / toplevel_window->layer()->device_scale_factor()); |
| 69 } | 70 } |
| 70 return bounds_in_screen; | 71 return bounds_in_screen; |
| 71 } | 72 } |
| 72 | 73 |
| 74 bool GetBooleanProperty(arc::mojom::AccessibilityNodeInfoData* node, |
| 75 arc::mojom::AccessibilityBooleanProperty prop) { |
| 76 if (!node->booleanProperties) |
| 77 return false; |
| 78 |
| 79 auto it = node->booleanProperties->find(prop); |
| 80 if (it == node->booleanProperties->end()) |
| 81 return false; |
| 82 |
| 83 return it->second; |
| 84 } |
| 85 |
| 73 bool GetStringProperty(arc::mojom::AccessibilityNodeInfoData* node, | 86 bool GetStringProperty(arc::mojom::AccessibilityNodeInfoData* node, |
| 74 arc::mojom::AccessibilityStringProperty prop, | 87 arc::mojom::AccessibilityStringProperty prop, |
| 75 std::string* out_value) { | 88 std::string* out_value) { |
| 76 if (!node->stringProperties) | 89 if (!node->stringProperties) |
| 77 return false; | 90 return false; |
| 78 | 91 |
| 79 auto it = node->stringProperties->find(prop); | 92 auto it = node->stringProperties->find(prop); |
| 80 if (it == node->stringProperties->end()) | 93 if (it == node->stringProperties->end()) |
| 81 return false; | 94 return false; |
| 82 | 95 |
| 83 *out_value = it->second; | 96 *out_value = it->second; |
| 84 return true; | 97 return true; |
| 85 } | 98 } |
| 86 | 99 |
| 100 void PopulateAXRole(arc::mojom::AccessibilityNodeInfoData* node, |
| 101 ui::AXNodeData* out_data) { |
| 102 std::string class_name; |
| 103 GetStringProperty(node, arc::mojom::AccessibilityStringProperty::CLASS_NAME, |
| 104 &class_name); |
| 105 |
| 106 #define MAP_ROLE(android_class_name, chrome_role) \ |
| 107 if (class_name == android_class_name) { \ |
| 108 out_data->role = chrome_role; \ |
| 109 return; \ |
| 110 } |
| 111 |
| 112 // These mappings were taken from accessibility utils (Android -> Chrome) and |
| 113 // BrowserAccessibilityAndroid. They do not completely match the above two |
| 114 // sources. |
| 115 MAP_ROLE(ui::kAXAbsListViewClassname, ui::AX_ROLE_LIST); |
| 116 MAP_ROLE(ui::kAXButtonClassname, ui::AX_ROLE_BUTTON); |
| 117 MAP_ROLE(ui::kAXCheckBoxClassname, ui::AX_ROLE_CHECK_BOX); |
| 118 MAP_ROLE(ui::kAXCheckedTextViewClassname, ui::AX_ROLE_STATIC_TEXT); |
| 119 MAP_ROLE(ui::kAXCompoundButtonClassname, ui::AX_ROLE_CHECK_BOX); |
| 120 MAP_ROLE(ui::kAXDialogClassname, ui::AX_ROLE_DIALOG); |
| 121 MAP_ROLE(ui::kAXEditTextClassname, ui::AX_ROLE_TEXT_FIELD); |
| 122 MAP_ROLE(ui::kAXGridViewClassname, ui::AX_ROLE_TABLE); |
| 123 MAP_ROLE(ui::kAXImageClassname, ui::AX_ROLE_IMAGE); |
| 124 if (GetBooleanProperty(node, |
| 125 arc::mojom::AccessibilityBooleanProperty::CLICKABLE)) { |
| 126 MAP_ROLE(ui::kAXImageViewClassname, ui::AX_ROLE_BUTTON); |
| 127 } else { |
| 128 MAP_ROLE(ui::kAXImageViewClassname, ui::AX_ROLE_IMAGE); |
| 129 } |
| 130 MAP_ROLE(ui::kAXListViewClassname, ui::AX_ROLE_LIST); |
| 131 MAP_ROLE(ui::kAXMenuItemClassname, ui::AX_ROLE_MENU_ITEM); |
| 132 MAP_ROLE(ui::kAXPagerClassname, ui::AX_ROLE_SCROLL_AREA); |
| 133 MAP_ROLE(ui::kAXProgressBarClassname, ui::AX_ROLE_PROGRESS_INDICATOR); |
| 134 MAP_ROLE(ui::kAXRadioButtonClassname, ui::AX_ROLE_RADIO_BUTTON); |
| 135 MAP_ROLE(ui::kAXSeekBarClassname, ui::AX_ROLE_SLIDER); |
| 136 MAP_ROLE(ui::kAXSpinnerClassname, ui::AX_ROLE_POP_UP_BUTTON); |
| 137 MAP_ROLE(ui::kAXSwitchClassname, ui::AX_ROLE_SWITCH); |
| 138 MAP_ROLE(ui::kAXTabWidgetClassname, ui::AX_ROLE_TAB_LIST); |
| 139 MAP_ROLE(ui::kAXToggleButtonClassname, ui::AX_ROLE_TOGGLE_BUTTON); |
| 140 MAP_ROLE(ui::kAXViewClassname, ui::AX_ROLE_DIV); |
| 141 MAP_ROLE(ui::kAXViewGroupClassname, ui::AX_ROLE_GROUP); |
| 142 MAP_ROLE(ui::kAXWebViewClassname, ui::AX_ROLE_WEB_VIEW); |
| 143 |
| 144 #undef MAP_ROLE |
| 145 |
| 146 std::string text; |
| 147 GetStringProperty(node, arc::mojom::AccessibilityStringProperty::TEXT, &text); |
| 148 if (!text.empty()) |
| 149 out_data->role = ui::AX_ROLE_STATIC_TEXT; |
| 150 else |
| 151 out_data->role = ui::AX_ROLE_DIV; |
| 152 } |
| 153 |
| 154 void PopulateAXState(arc::mojom::AccessibilityNodeInfoData* node, |
| 155 ui::AXNodeData* out_data) { |
| 156 out_data->state = 0; |
| 157 |
| 158 #define MAP_STATE(android_boolean_property, chrome_state) \ |
| 159 if (GetBooleanProperty(node, android_boolean_property)) \ |
| 160 out_data->AddStateFlag(chrome_state); |
| 161 |
| 162 using AXBooleanProperty = arc::mojom::AccessibilityBooleanProperty; |
| 163 |
| 164 // These mappings were taken from accessibility utils (Android -> Chrome) and |
| 165 // BrowserAccessibilityAndroid. They do not completely match the above two |
| 166 // sources. |
| 167 // The FOCUSABLE state is not mapped because Android places focusability on |
| 168 // many ancestor nodes. |
| 169 MAP_STATE(AXBooleanProperty::CHECKED, ui::AX_STATE_CHECKED); |
| 170 MAP_STATE(AXBooleanProperty::EDITABLE, ui::AX_STATE_EDITABLE); |
| 171 MAP_STATE(AXBooleanProperty::MULTI_LINE, ui::AX_STATE_MULTILINE); |
| 172 MAP_STATE(AXBooleanProperty::PASSWORD, ui::AX_STATE_PROTECTED); |
| 173 MAP_STATE(AXBooleanProperty::SELECTED, ui::AX_STATE_SELECTED); |
| 174 |
| 175 #undef MAP_STATE |
| 176 |
| 177 if (!GetBooleanProperty(node, AXBooleanProperty::ENABLED)) |
| 178 out_data->AddStateFlag(ui::AX_STATE_DISABLED); |
| 179 } |
| 180 |
| 87 } // namespace | 181 } // namespace |
| 88 | 182 |
| 89 namespace arc { | 183 namespace arc { |
| 90 | 184 |
| 91 AXTreeSourceArc::AXTreeSourceArc(int32_t id) | 185 AXTreeSourceArc::AXTreeSourceArc(int32_t id) |
| 92 : tree_id_(id), | 186 : tree_id_(id), |
| 93 current_tree_serializer_(new AXTreeArcSerializer(this)), | 187 current_tree_serializer_(new AXTreeArcSerializer(this)), |
| 94 root_id_(-1), | 188 root_id_(-1), |
| 95 focused_node_id_(-1) {} | 189 focused_node_id_(-1) {} |
| 96 | 190 |
| 97 AXTreeSourceArc::~AXTreeSourceArc() { | 191 AXTreeSourceArc::~AXTreeSourceArc() { |
| 98 Reset(); | 192 Reset(); |
| 99 } | 193 } |
| 100 | 194 |
| 101 void AXTreeSourceArc::NotifyAccessibilityEvent( | 195 void AXTreeSourceArc::NotifyAccessibilityEvent( |
| 102 mojom::AccessibilityEventData* event_data) { | 196 mojom::AccessibilityEventData* event_data) { |
| 103 tree_map_.clear(); | 197 tree_map_.clear(); |
| 104 parent_map_.clear(); | 198 parent_map_.clear(); |
| 105 root_id_ = -1; | 199 root_id_ = -1; |
| 106 focused_node_id_ = -1; | |
| 107 for (size_t i = 0; i < event_data->nodeData.size(); ++i) { | 200 for (size_t i = 0; i < event_data->nodeData.size(); ++i) { |
| 108 if (!event_data->nodeData[i]->intListProperties) | 201 if (!event_data->nodeData[i]->intListProperties) |
| 109 continue; | 202 continue; |
| 110 auto it = event_data->nodeData[i]->intListProperties->find( | 203 auto it = event_data->nodeData[i]->intListProperties->find( |
| 111 arc::mojom::AccessibilityIntListProperty::CHILD_NODE_IDS); | 204 arc::mojom::AccessibilityIntListProperty::CHILD_NODE_IDS); |
| 112 if (it != event_data->nodeData[i]->intListProperties->end()) { | 205 if (it != event_data->nodeData[i]->intListProperties->end()) { |
| 113 for (size_t j = 0; j < it->second.size(); ++j) | 206 for (size_t j = 0; j < it->second.size(); ++j) |
| 114 parent_map_[it->second[j]] = event_data->nodeData[i]->id; | 207 parent_map_[it->second[j]] = event_data->nodeData[i]->id; |
| 115 } | 208 } |
| 116 } | 209 } |
| 117 | 210 |
| 118 for (size_t i = 0; i < event_data->nodeData.size(); ++i) { | 211 for (size_t i = 0; i < event_data->nodeData.size(); ++i) { |
| 119 int32_t id = event_data->nodeData[i]->id; | 212 int32_t id = event_data->nodeData[i]->id; |
| 120 tree_map_[id] = event_data->nodeData[i].get(); | 213 tree_map_[id] = event_data->nodeData[i].get(); |
| 121 if (parent_map_.find(id) == parent_map_.end()) { | 214 if (parent_map_.find(id) == parent_map_.end()) { |
| 122 CHECK_EQ(-1, root_id_) << "Duplicated root"; | 215 CHECK_EQ(-1, root_id_) << "Duplicated root"; |
| 123 root_id_ = id; | 216 root_id_ = id; |
| 124 } | 217 } |
| 125 } | 218 } |
| 126 | 219 |
| 127 ExtensionMsg_AccessibilityEventParams params; | 220 ExtensionMsg_AccessibilityEventParams params; |
| 128 params.event_type = ToAXEvent(event_data->eventType); | 221 params.event_type = ToAXEvent(event_data->eventType); |
| 129 if (params.event_type == ui::AX_EVENT_FOCUS) | 222 if (params.event_type == ui::AX_EVENT_FOCUS) |
| 130 focused_node_id_ = params.id; | 223 focused_node_id_ = params.id; |
| 224 else if (params.event_type == ui::AX_EVENT_BLUR) |
| 225 focused_node_id_ = -1; |
| 131 params.tree_id = tree_id_; | 226 params.tree_id = tree_id_; |
| 132 params.id = event_data->sourceId; | 227 params.id = event_data->sourceId; |
| 133 | 228 |
| 134 current_tree_serializer_->SerializeChanges(GetFromId(event_data->sourceId), | 229 current_tree_serializer_->SerializeChanges(GetFromId(event_data->sourceId), |
| 135 ¶ms.update); | 230 ¶ms.update); |
| 136 | 231 |
| 137 extensions::AutomationEventRouter* router = | 232 extensions::AutomationEventRouter* router = |
| 138 extensions::AutomationEventRouter::GetInstance(); | 233 extensions::AutomationEventRouter::GetInstance(); |
| 139 router->DispatchAccessibilityEvent(params); | 234 router->DispatchAccessibilityEvent(params); |
| 140 } | 235 } |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 203 | 298 |
| 204 mojom::AccessibilityNodeInfoData* AXTreeSourceArc::GetNull() const { | 299 mojom::AccessibilityNodeInfoData* AXTreeSourceArc::GetNull() const { |
| 205 return nullptr; | 300 return nullptr; |
| 206 } | 301 } |
| 207 | 302 |
| 208 void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, | 303 void AXTreeSourceArc::SerializeNode(mojom::AccessibilityNodeInfoData* node, |
| 209 ui::AXNodeData* out_data) const { | 304 ui::AXNodeData* out_data) const { |
| 210 if (!node) | 305 if (!node) |
| 211 return; | 306 return; |
| 212 out_data->id = node->id; | 307 out_data->id = node->id; |
| 213 out_data->state = 0; | |
| 214 | 308 |
| 215 using AXStringProperty = arc::mojom::AccessibilityStringProperty; | 309 using AXStringProperty = arc::mojom::AccessibilityStringProperty; |
| 216 std::string text; | 310 std::string text; |
| 217 if (GetStringProperty(node, AXStringProperty::TEXT, &text)) | 311 if (GetStringProperty(node, AXStringProperty::TEXT, &text)) |
| 218 out_data->SetName(text); | 312 out_data->SetName(text); |
| 219 else if (GetStringProperty(node, AXStringProperty::CONTENT_DESCRIPTION, | 313 else if (GetStringProperty(node, AXStringProperty::CONTENT_DESCRIPTION, |
| 220 &text)) | 314 &text)) |
| 221 out_data->SetName(text); | 315 out_data->SetName(text); |
| 222 | 316 |
| 223 int32_t id = node->id; | 317 int32_t id = node->id; |
| 224 if (id == root_id_) | 318 if (id == root_id_) |
| 225 out_data->role = ui::AX_ROLE_ROOT_WEB_AREA; | 319 out_data->role = ui::AX_ROLE_ROOT_WEB_AREA; |
| 226 else if (!text.empty()) | |
| 227 out_data->role = ui::AX_ROLE_STATIC_TEXT; | |
| 228 else | 320 else |
| 229 out_data->role = ui::AX_ROLE_DIV; | 321 PopulateAXRole(node, out_data); |
| 322 |
| 323 PopulateAXState(node, out_data); |
| 230 | 324 |
| 231 const gfx::Rect bounds_in_screen = GetBounds(node); | 325 const gfx::Rect bounds_in_screen = GetBounds(node); |
| 232 out_data->location.SetRect(bounds_in_screen.x(), bounds_in_screen.y(), | 326 out_data->location.SetRect(bounds_in_screen.x(), bounds_in_screen.y(), |
| 233 bounds_in_screen.width(), | 327 bounds_in_screen.width(), |
| 234 bounds_in_screen.height()); | 328 bounds_in_screen.height()); |
| 329 |
| 330 if (out_data->role == ui::AX_ROLE_TEXT_FIELD && !text.empty()) |
| 331 out_data->AddStringAttribute(ui::AX_ATTR_VALUE, text); |
| 235 } | 332 } |
| 236 | 333 |
| 237 void AXTreeSourceArc::Reset() { | 334 void AXTreeSourceArc::Reset() { |
| 238 tree_map_.clear(); | 335 tree_map_.clear(); |
| 239 parent_map_.clear(); | 336 parent_map_.clear(); |
| 240 current_tree_serializer_.reset(new AXTreeArcSerializer(this)); | 337 current_tree_serializer_.reset(new AXTreeArcSerializer(this)); |
| 241 root_id_ = -1; | 338 root_id_ = -1; |
| 242 focused_node_id_ = -1; | 339 focused_node_id_ = -1; |
| 243 extensions::AutomationEventRouter* router = | 340 extensions::AutomationEventRouter* router = |
| 244 extensions::AutomationEventRouter::GetInstance(); | 341 extensions::AutomationEventRouter::GetInstance(); |
| 245 router->DispatchTreeDestroyedEvent(tree_id_, nullptr); | 342 router->DispatchTreeDestroyedEvent(tree_id_, nullptr); |
| 246 } | 343 } |
| 247 | 344 |
| 248 } // namespace arc | 345 } // namespace arc |
| OLD | NEW |