blob: 63a87f882da4f38bbebd675e501aea4873baeb71 [file] [log] [blame]
// Copyright 2014 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 "content/renderer/accessibility/blink_ax_tree_source.h"
#include <stddef.h>
#include <set>
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/common/accessibility_messages.h"
#include "content/renderer/accessibility/blink_ax_enum_conversion.h"
#include "content/renderer/accessibility/render_accessibility_impl.h"
#include "content/renderer/browser_plugin/browser_plugin.h"
#include "content/renderer/render_frame_impl.h"
#include "content/renderer/render_frame_proxy.h"
#include "content/renderer/render_view_impl.h"
#include "content/renderer/web_frame_utils.h"
#include "third_party/WebKit/public/platform/WebFloatRect.h"
#include "third_party/WebKit/public/platform/WebRect.h"
#include "third_party/WebKit/public/platform/WebSize.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/platform/WebVector.h"
#include "third_party/WebKit/public/web/WebAXEnums.h"
#include "third_party/WebKit/public/web/WebAXObject.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebElement.h"
#include "third_party/WebKit/public/web/WebFormControlElement.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "third_party/WebKit/public/web/WebNode.h"
#include "third_party/WebKit/public/web/WebPlugin.h"
#include "third_party/WebKit/public/web/WebPluginContainer.h"
#include "third_party/WebKit/public/web/WebView.h"
using base::ASCIIToUTF16;
using base::UTF16ToUTF8;
using blink::WebAXObject;
using blink::WebDocument;
using blink::WebElement;
using blink::WebFloatRect;
using blink::WebFrame;
using blink::WebLocalFrame;
using blink::WebNode;
using blink::WebPlugin;
using blink::WebPluginContainer;
using blink::WebVector;
using blink::WebView;
namespace content {
namespace {
void AddIntListAttributeFromWebObjects(ui::AXIntListAttribute attr,
const WebVector<WebAXObject>& objects,
AXContentNodeData* dst) {
std::vector<int32_t> ids;
for (size_t i = 0; i < objects.size(); i++)
ids.push_back(objects[i].axID());
if (!ids.empty())
dst->AddIntListAttribute(attr, ids);
}
class AXContentNodeDataSparseAttributeAdapter
: public blink::WebAXSparseAttributeClient {
public:
AXContentNodeDataSparseAttributeAdapter(AXContentNodeData* dst) : dst_(dst) {
DCHECK(dst_);
}
~AXContentNodeDataSparseAttributeAdapter() override {}
private:
AXContentNodeData* dst_;
void addBoolAttribute(blink::WebAXBoolAttribute attribute,
bool value) override {
NOTREACHED();
}
void addStringAttribute(blink::WebAXStringAttribute attribute,
const blink::WebString& value) override {
switch (attribute) {
case blink::WebAXStringAttribute::AriaKeyShortcuts:
dst_->AddStringAttribute(ui::AX_ATTR_KEY_SHORTCUTS, value.utf8());
break;
case blink::WebAXStringAttribute::AriaRoleDescription:
dst_->AddStringAttribute(ui::AX_ATTR_ROLE_DESCRIPTION, value.utf8());
break;
default:
NOTREACHED();
}
}
void addObjectAttribute(blink::WebAXObjectAttribute attribute,
const blink::WebAXObject& value) override {
switch (attribute) {
case blink::WebAXObjectAttribute::AriaActiveDescendant:
dst_->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID, value.axID());
break;
case blink::WebAXObjectAttribute::AriaErrorMessage:
dst_->AddIntAttribute(ui::AX_ATTR_ERRORMESSAGE_ID, value.axID());
break;
default:
NOTREACHED();
}
}
void addObjectVectorAttribute(
blink::WebAXObjectVectorAttribute attribute,
const blink::WebVector<WebAXObject>& value) override {
switch (attribute) {
case blink::WebAXObjectVectorAttribute::AriaControls:
AddIntListAttributeFromWebObjects(ui::AX_ATTR_CONTROLS_IDS, value,
dst_);
break;
case blink::WebAXObjectVectorAttribute::AriaDetails:
AddIntListAttributeFromWebObjects(ui::AX_ATTR_DETAILS_IDS, value,
dst_);
break;
case blink::WebAXObjectVectorAttribute::AriaFlowTo:
AddIntListAttributeFromWebObjects(ui::AX_ATTR_FLOWTO_IDS, value, dst_);
break;
default:
NOTREACHED();
}
}
};
WebAXObject ParentObjectUnignored(WebAXObject child) {
WebAXObject parent = child.parentObject();
while (!parent.isDetached() && parent.accessibilityIsIgnored())
parent = parent.parentObject();
return parent;
}
// Returns true if |ancestor| is the first unignored parent of |child|,
// which means that when walking up the parent chain from |child|,
// |ancestor| is the *first* ancestor that isn't marked as
// accessibilityIsIgnored().
bool IsParentUnignoredOf(WebAXObject ancestor,
WebAXObject child) {
WebAXObject parent = ParentObjectUnignored(child);
return parent.equals(ancestor);
}
std::string GetEquivalentAriaRoleString(const ui::AXRole role) {
switch (role) {
case ui::AX_ROLE_ARTICLE:
return "article";
case ui::AX_ROLE_BANNER:
return "banner";
case ui::AX_ROLE_BUTTON:
return "button";
case ui::AX_ROLE_COMPLEMENTARY:
return "complementary";
case ui::AX_ROLE_FIGURE:
return "figure";
case ui::AX_ROLE_FOOTER:
return "contentinfo";
case ui::AX_ROLE_HEADING:
return "heading";
case ui::AX_ROLE_IMAGE:
return "img";
case ui::AX_ROLE_MAIN:
return "main";
case ui::AX_ROLE_NAVIGATION:
return "navigation";
case ui::AX_ROLE_RADIO_BUTTON:
return "radio";
case ui::AX_ROLE_REGION:
return "region";
case ui::AX_ROLE_SLIDER:
return "slider";
case ui::AX_ROLE_TIME:
return "time";
default:
break;
}
return std::string();
}
} // namespace
ScopedFreezeBlinkAXTreeSource::ScopedFreezeBlinkAXTreeSource(
BlinkAXTreeSource* tree_source)
: tree_source_(tree_source) {
tree_source_->Freeze();
}
ScopedFreezeBlinkAXTreeSource::~ScopedFreezeBlinkAXTreeSource() {
tree_source_->Thaw();
}
BlinkAXTreeSource::BlinkAXTreeSource(RenderFrameImpl* render_frame,
AccessibilityMode mode)
: render_frame_(render_frame),
accessibility_mode_(mode),
frozen_(false) {}
BlinkAXTreeSource::~BlinkAXTreeSource() {
}
void BlinkAXTreeSource::Freeze() {
CHECK(!frozen_);
frozen_ = true;
if (render_frame_ && render_frame_->GetWebFrame())
document_ = render_frame_->GetWebFrame()->document();
else
document_ = WebDocument();
root_ = ComputeRoot();
if (!document_.isNull())
focus_ = document_.focusedAccessibilityObject();
else
focus_ = WebAXObject();
}
void BlinkAXTreeSource::Thaw() {
CHECK(frozen_);
frozen_ = false;
}
void BlinkAXTreeSource::SetRoot(blink::WebAXObject root) {
CHECK(!frozen_);
explicit_root_ = root;
}
bool BlinkAXTreeSource::IsInTree(blink::WebAXObject node) const {
CHECK(frozen_);
while (IsValid(node)) {
if (node.equals(root()))
return true;
node = GetParent(node);
}
return false;
}
bool BlinkAXTreeSource::GetTreeData(AXContentTreeData* tree_data) const {
CHECK(frozen_);
tree_data->doctype = "html";
tree_data->loaded = root().isLoaded();
tree_data->loading_progress = root().estimatedLoadingProgress();
tree_data->mimetype =
document().isXHTMLDocument() ? "text/xhtml" : "text/html";
tree_data->title = document().title().utf8();
tree_data->url = document().url().string().utf8();
if (!focus().isNull())
tree_data->focus_id = focus().axID();
WebAXObject anchor_object, focus_object;
int anchor_offset, focus_offset;
blink::WebAXTextAffinity anchor_affinity, focus_affinity;
root().selection(anchor_object, anchor_offset, anchor_affinity, focus_object,
focus_offset, focus_affinity);
if (!anchor_object.isNull() && !focus_object.isNull() &&
anchor_offset >= 0 && focus_offset >= 0) {
int32_t anchor_id = anchor_object.axID();
int32_t focus_id = focus_object.axID();
tree_data->sel_anchor_object_id = anchor_id;
tree_data->sel_anchor_offset = anchor_offset;
tree_data->sel_focus_object_id = focus_id;
tree_data->sel_focus_offset = focus_offset;
tree_data->sel_anchor_affinity = AXTextAffinityFromBlink(anchor_affinity);
tree_data->sel_focus_affinity = AXTextAffinityFromBlink(focus_affinity);
}
// Get the tree ID for this frame and the parent frame.
WebLocalFrame* web_frame = document().frame();
if (web_frame) {
RenderFrame* render_frame = RenderFrame::FromWebFrame(web_frame);
tree_data->routing_id = render_frame->GetRoutingID();
// Get the tree ID for the parent frame.
blink::WebFrame* parent_web_frame = web_frame->parent();
if (parent_web_frame) {
tree_data->parent_routing_id =
GetRoutingIdForFrameOrProxy(parent_web_frame);
}
}
return true;
}
blink::WebAXObject BlinkAXTreeSource::GetRoot() const {
if (frozen_)
return root_;
else
return ComputeRoot();
}
blink::WebAXObject BlinkAXTreeSource::GetFromId(int32_t id) const {
return GetMainDocument().accessibilityObjectFromID(id);
}
int32_t BlinkAXTreeSource::GetId(blink::WebAXObject node) const {
return node.axID();
}
void BlinkAXTreeSource::GetChildren(
blink::WebAXObject parent,
std::vector<blink::WebAXObject>* out_children) const {
CHECK(frozen_);
if (parent.role() == blink::WebAXRoleStaticText) {
int32_t focus_id = focus().axID();
blink::WebAXObject ancestor = parent;
while (!ancestor.isDetached()) {
if (ancestor.axID() == accessibility_focus_id_ ||
(ancestor.axID() == focus_id && ancestor.isEditable())) {
parent.loadInlineTextBoxes();
break;
}
ancestor = ancestor.parentObject();
}
}
bool is_iframe = false;
WebNode node = parent.node();
if (!node.isNull() && node.isElementNode())
is_iframe = node.to<WebElement>().hasHTMLTagName("iframe");
for (unsigned i = 0; i < parent.childCount(); i++) {
blink::WebAXObject child = parent.childAt(i);
// The child may be invalid due to issues in blink accessibility code.
if (child.isDetached())
continue;
// Skip children whose parent isn't |parent|.
// As an exception, include children of an iframe element.
if (!is_iframe && !IsParentUnignoredOf(parent, child))
continue;
out_children->push_back(child);
}
}
blink::WebAXObject BlinkAXTreeSource::GetParent(
blink::WebAXObject node) const {
CHECK(frozen_);
// Blink returns ignored objects when walking up the parent chain,
// we have to skip those here. Also, stop when we get to the root
// element.
do {
if (node.equals(root()))
return blink::WebAXObject();
node = node.parentObject();
} while (!node.isDetached() && node.accessibilityIsIgnored());
return node;
}
bool BlinkAXTreeSource::IsValid(blink::WebAXObject node) const {
return !node.isDetached(); // This also checks if it's null.
}
bool BlinkAXTreeSource::IsEqual(blink::WebAXObject node1,
blink::WebAXObject node2) const {
return node1.equals(node2);
}
blink::WebAXObject BlinkAXTreeSource::GetNull() const {
return blink::WebAXObject();
}
void BlinkAXTreeSource::SerializeNode(blink::WebAXObject src,
AXContentNodeData* dst) const {
dst->role = AXRoleFromBlink(src.role());
dst->state = AXStateFromBlink(src);
dst->id = src.axID();
WebAXObject offset_container;
WebFloatRect bounds_in_container;
SkMatrix44 container_transform;
src.getRelativeBounds(
offset_container, bounds_in_container, container_transform);
dst->location = bounds_in_container;
if (!container_transform.isIdentity())
dst->transform = base::WrapUnique(new gfx::Transform(container_transform));
if (!offset_container.isDetached())
dst->offset_container_id = offset_container.axID();
AXContentNodeDataSparseAttributeAdapter sparse_attribute_adapter(dst);
src.getSparseAXAttributes(sparse_attribute_adapter);
blink::WebAXNameFrom nameFrom;
blink::WebVector<blink::WebAXObject> nameObjects;
blink::WebString web_name = src.name(nameFrom, nameObjects);
if (!web_name.isEmpty()) {
dst->AddStringAttribute(ui::AX_ATTR_NAME, web_name.utf8());
dst->AddIntAttribute(ui::AX_ATTR_NAME_FROM, AXNameFromFromBlink(nameFrom));
AddIntListAttributeFromWebObjects(
ui::AX_ATTR_LABELLEDBY_IDS, nameObjects, dst);
}
blink::WebAXDescriptionFrom descriptionFrom;
blink::WebVector<blink::WebAXObject> descriptionObjects;
blink::WebString web_description = src.description(
nameFrom, descriptionFrom, descriptionObjects);
if (!web_description.isEmpty()) {
dst->AddStringAttribute(ui::AX_ATTR_DESCRIPTION, web_description.utf8());
dst->AddIntAttribute(ui::AX_ATTR_DESCRIPTION_FROM,
AXDescriptionFromFromBlink(descriptionFrom));
AddIntListAttributeFromWebObjects(
ui::AX_ATTR_DESCRIBEDBY_IDS, descriptionObjects, dst);
}
std::string value;
if (src.valueDescription().length()) {
dst->AddStringAttribute(ui::AX_ATTR_VALUE, src.valueDescription().utf8());
} else {
dst->AddStringAttribute(ui::AX_ATTR_VALUE, src.stringValue().utf8());
}
if (src.isButtonStateMixed())
dst->AddBoolAttribute(ui::AX_ATTR_STATE_MIXED, true);
if (src.canSetValueAttribute())
dst->AddBoolAttribute(ui::AX_ATTR_CAN_SET_VALUE, true);
if (!src.url().isEmpty())
dst->AddStringAttribute(ui::AX_ATTR_URL, src.url().string().utf8());
// The following set of attributes are only accessed when the accessibility
// mode is set to screen reader mode, otherwise only the more basic
// attributes are populated.
if (accessibility_mode_.has_mode(AccessibilityMode::kScreenReader)) {
blink::WebString web_placeholder = src.placeholder(nameFrom);
if (!web_placeholder.isEmpty())
dst->AddStringAttribute(ui::AX_ATTR_PLACEHOLDER, web_placeholder.utf8());
if (dst->role == ui::AX_ROLE_COLOR_WELL)
dst->AddIntAttribute(ui::AX_ATTR_COLOR_VALUE, src.colorValue());
if (dst->role == ui::AX_ROLE_LINK) {
blink::WebAXObject target = src.inPageLinkTarget();
if (!target.isNull()) {
int32_t target_id = target.axID();
dst->AddIntAttribute(ui::AX_ATTR_IN_PAGE_LINK_TARGET_ID, target_id);
}
}
if (dst->role == ui::AX_ROLE_RADIO_BUTTON) {
AddIntListAttributeFromWebObjects(ui::AX_ATTR_RADIO_GROUP_IDS,
src.radioButtonsInGroup(), dst);
}
// Text attributes.
if (src.backgroundColor())
dst->AddIntAttribute(ui::AX_ATTR_BACKGROUND_COLOR, src.backgroundColor());
if (src.color())
dst->AddIntAttribute(ui::AX_ATTR_COLOR, src.color());
WebAXObject parent = ParentObjectUnignored(src);
if (src.fontFamily().length()) {
if (parent.isNull() || parent.fontFamily() != src.fontFamily())
dst->AddStringAttribute(ui::AX_ATTR_FONT_FAMILY,
src.fontFamily().utf8());
}
// Font size is in pixels.
if (src.fontSize())
dst->AddFloatAttribute(ui::AX_ATTR_FONT_SIZE, src.fontSize());
if (src.ariaCurrentState()) {
dst->AddIntAttribute(ui::AX_ATTR_ARIA_CURRENT_STATE,
AXAriaCurrentStateFromBlink(src.ariaCurrentState()));
}
if (src.invalidState()) {
dst->AddIntAttribute(ui::AX_ATTR_INVALID_STATE,
AXInvalidStateFromBlink(src.invalidState()));
}
if (src.invalidState() == blink::WebAXInvalidStateOther &&
src.ariaInvalidValue().length()) {
dst->AddStringAttribute(
ui::AX_ATTR_ARIA_INVALID_VALUE, src.ariaInvalidValue().utf8());
}
if (src.textDirection()) {
dst->AddIntAttribute(ui::AX_ATTR_TEXT_DIRECTION,
AXTextDirectionFromBlink(src.textDirection()));
}
if (src.textStyle()) {
dst->AddIntAttribute(ui::AX_ATTR_TEXT_STYLE,
AXTextStyleFromBlink(src.textStyle()));
}
if (dst->role == ui::AX_ROLE_INLINE_TEXT_BOX) {
WebVector<int> src_character_offsets;
src.characterOffsets(src_character_offsets);
std::vector<int32_t> character_offsets;
character_offsets.reserve(src_character_offsets.size());
for (size_t i = 0; i < src_character_offsets.size(); ++i)
character_offsets.push_back(src_character_offsets[i]);
dst->AddIntListAttribute(ui::AX_ATTR_CHARACTER_OFFSETS,
character_offsets);
WebVector<int> src_word_starts;
WebVector<int> src_word_ends;
src.wordBoundaries(src_word_starts, src_word_ends);
std::vector<int32_t> word_starts;
std::vector<int32_t> word_ends;
word_starts.reserve(src_word_starts.size());
word_ends.reserve(src_word_starts.size());
for (size_t i = 0; i < src_word_starts.size(); ++i) {
word_starts.push_back(src_word_starts[i]);
word_ends.push_back(src_word_ends[i]);
}
dst->AddIntListAttribute(ui::AX_ATTR_WORD_STARTS, word_starts);
dst->AddIntListAttribute(ui::AX_ATTR_WORD_ENDS, word_ends);
}
if (src.accessKey().length()) {
dst->AddStringAttribute(ui::AX_ATTR_ACCESS_KEY, src.accessKey().utf8());
}
if (src.ariaAutoComplete().length()) {
dst->AddStringAttribute(
ui::AX_ATTR_AUTO_COMPLETE,
src.ariaAutoComplete().utf8());
}
if (src.action() != blink::WebAXSupportedAction::None) {
dst->AddIntAttribute(ui::AX_ATTR_ACTION,
AXSupportedActionFromBlink(src.action()));
}
if (src.isAriaReadOnly())
dst->AddBoolAttribute(ui::AX_ATTR_ARIA_READONLY, true);
if (src.hasComputedStyle()) {
dst->AddStringAttribute(
ui::AX_ATTR_DISPLAY, src.computedStyleDisplay().utf8());
}
if (src.language().length()) {
if (parent.isNull() || parent.language() != src.language())
dst->AddStringAttribute(ui::AX_ATTR_LANGUAGE, src.language().utf8());
}
if (src.keyboardShortcut().length()) {
dst->AddStringAttribute(
ui::AX_ATTR_SHORTCUT,
src.keyboardShortcut().utf8());
}
if (!src.nextOnLine().isDetached()) {
dst->AddIntAttribute(ui::AX_ATTR_NEXT_ON_LINE_ID,
src.nextOnLine().axID());
}
if (!src.previousOnLine().isDetached()) {
dst->AddIntAttribute(ui::AX_ATTR_PREVIOUS_ON_LINE_ID,
src.previousOnLine().axID());
}
if (!src.ariaActiveDescendant().isDetached()) {
dst->AddIntAttribute(ui::AX_ATTR_ACTIVEDESCENDANT_ID,
src.ariaActiveDescendant().axID());
}
if (dst->role == ui::AX_ROLE_HEADING && src.headingLevel()) {
dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL, src.headingLevel());
} else if ((dst->role == ui::AX_ROLE_TREE_ITEM ||
dst->role == ui::AX_ROLE_ROW) &&
src.hierarchicalLevel()) {
dst->AddIntAttribute(ui::AX_ATTR_HIERARCHICAL_LEVEL,
src.hierarchicalLevel());
}
if (src.setSize())
dst->AddIntAttribute(ui::AX_ATTR_SET_SIZE, src.setSize());
if (src.posInSet())
dst->AddIntAttribute(ui::AX_ATTR_POS_IN_SET, src.posInSet());
if (src.canvasHasFallbackContent())
dst->AddBoolAttribute(ui::AX_ATTR_CANVAS_HAS_FALLBACK, true);
// Spelling, grammar and other document markers.
WebVector<blink::WebAXMarkerType> src_marker_types;
WebVector<int> src_marker_starts;
WebVector<int> src_marker_ends;
src.markers(src_marker_types, src_marker_starts, src_marker_ends);
DCHECK_EQ(src_marker_types.size(), src_marker_starts.size());
DCHECK_EQ(src_marker_starts.size(), src_marker_ends.size());
if (src_marker_types.size()) {
std::vector<int32_t> marker_types;
std::vector<int32_t> marker_starts;
std::vector<int32_t> marker_ends;
marker_types.reserve(src_marker_types.size());
marker_starts.reserve(src_marker_starts.size());
marker_ends.reserve(src_marker_ends.size());
for (size_t i = 0; i < src_marker_types.size(); ++i) {
marker_types.push_back(
static_cast<int32_t>(AXMarkerTypeFromBlink(src_marker_types[i])));
marker_starts.push_back(src_marker_starts[i]);
marker_ends.push_back(src_marker_ends[i]);
}
dst->AddIntListAttribute(ui::AX_ATTR_MARKER_TYPES, marker_types);
dst->AddIntListAttribute(ui::AX_ATTR_MARKER_STARTS, marker_starts);
dst->AddIntListAttribute(ui::AX_ATTR_MARKER_ENDS, marker_ends);
}
if (src.isInLiveRegion()) {
dst->AddBoolAttribute(ui::AX_ATTR_LIVE_ATOMIC, src.liveRegionAtomic());
dst->AddBoolAttribute(ui::AX_ATTR_LIVE_BUSY, src.liveRegionBusy());
if (src.liveRegionBusy())
dst->state |= (1 << ui::AX_STATE_BUSY);
if (!src.liveRegionStatus().isEmpty()) {
dst->AddStringAttribute(
ui::AX_ATTR_LIVE_STATUS,
src.liveRegionStatus().utf8());
}
dst->AddStringAttribute(
ui::AX_ATTR_LIVE_RELEVANT,
src.liveRegionRelevant().utf8());
// If we are not at the root of an atomic live region.
if (src.containerLiveRegionAtomic() &&
!src.liveRegionRoot().isDetached() &&
!src.liveRegionAtomic()) {
dst->AddIntAttribute(ui::AX_ATTR_MEMBER_OF_ID,
src.liveRegionRoot().axID());
}
dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_ATOMIC,
src.containerLiveRegionAtomic());
dst->AddBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY,
src.containerLiveRegionBusy());
dst->AddStringAttribute(
ui::AX_ATTR_CONTAINER_LIVE_STATUS,
src.containerLiveRegionStatus().utf8());
dst->AddStringAttribute(
ui::AX_ATTR_CONTAINER_LIVE_RELEVANT,
src.containerLiveRegionRelevant().utf8());
}
if (dst->role == ui::AX_ROLE_PROGRESS_INDICATOR ||
dst->role == ui::AX_ROLE_METER ||
dst->role == ui::AX_ROLE_SCROLL_BAR ||
dst->role == ui::AX_ROLE_SLIDER ||
dst->role == ui::AX_ROLE_SPIN_BUTTON) {
dst->AddFloatAttribute(ui::AX_ATTR_VALUE_FOR_RANGE, src.valueForRange());
dst->AddFloatAttribute(ui::AX_ATTR_MAX_VALUE_FOR_RANGE,
src.maxValueForRange());
dst->AddFloatAttribute(ui::AX_ATTR_MIN_VALUE_FOR_RANGE,
src.minValueForRange());
}
if (dst->role == ui::AX_ROLE_DIALOG ||
dst->role == ui::AX_ROLE_ALERT_DIALOG) {
dst->AddBoolAttribute(ui::AX_ATTR_MODAL, src.isModal());
}
if (dst->role == ui::AX_ROLE_ROOT_WEB_AREA)
dst->AddStringAttribute(ui::AX_ATTR_HTML_TAG, "#document");
if (dst->role == ui::AX_ROLE_TABLE) {
int column_count = src.columnCount();
int row_count = src.rowCount();
if (column_count > 0 && row_count > 0) {
std::set<int32_t> unique_cell_id_set;
std::vector<int32_t> cell_ids;
std::vector<int32_t> unique_cell_ids;
dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_COUNT, column_count);
dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_COUNT, row_count);
WebAXObject header = src.headerContainerObject();
if (!header.isDetached())
dst->AddIntAttribute(ui::AX_ATTR_TABLE_HEADER_ID, header.axID());
for (int i = 0; i < column_count * row_count; ++i) {
WebAXObject cell = src.cellForColumnAndRow(
i % column_count, i / column_count);
int cell_id = -1;
if (!cell.isDetached()) {
cell_id = cell.axID();
if (unique_cell_id_set.find(cell_id) == unique_cell_id_set.end()) {
unique_cell_id_set.insert(cell_id);
unique_cell_ids.push_back(cell_id);
}
}
cell_ids.push_back(cell_id);
}
dst->AddIntListAttribute(ui::AX_ATTR_CELL_IDS, cell_ids);
dst->AddIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS, unique_cell_ids);
}
}
if (dst->role == ui::AX_ROLE_TABLE ||
dst->role == ui::AX_ROLE_GRID ||
dst->role == ui::AX_ROLE_TREE_GRID) {
int aria_colcount = src.ariaColumnCount();
if (aria_colcount)
dst->AddIntAttribute(ui::AX_ATTR_ARIA_COL_COUNT, aria_colcount);
int aria_rowcount = src.ariaRowCount();
if (aria_rowcount)
dst->AddIntAttribute(ui::AX_ATTR_ARIA_ROW_COUNT, aria_rowcount);
}
if (dst->role == ui::AX_ROLE_ROW) {
dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_INDEX, src.rowIndex());
WebAXObject header = src.rowHeader();
if (!header.isDetached())
dst->AddIntAttribute(ui::AX_ATTR_TABLE_ROW_HEADER_ID, header.axID());
}
if (dst->role == ui::AX_ROLE_COLUMN) {
dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_INDEX, src.columnIndex());
WebAXObject header = src.columnHeader();
if (!header.isDetached())
dst->AddIntAttribute(ui::AX_ATTR_TABLE_COLUMN_HEADER_ID, header.axID());
}
if (dst->role == ui::AX_ROLE_CELL ||
dst->role == ui::AX_ROLE_ROW_HEADER ||
dst->role == ui::AX_ROLE_COLUMN_HEADER ||
dst->role == ui::AX_ROLE_ROW) {
if (dst->role != ui::AX_ROLE_ROW) {
dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_INDEX,
src.cellColumnIndex());
dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_COLUMN_SPAN,
src.cellColumnSpan());
dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_INDEX,
src.cellRowIndex());
dst->AddIntAttribute(ui::AX_ATTR_TABLE_CELL_ROW_SPAN,src.cellRowSpan());
int aria_colindex = src.ariaColumnIndex();
if (aria_colindex)
dst->AddIntAttribute(ui::AX_ATTR_ARIA_COL_INDEX, aria_colindex);
}
int aria_rowindex = src.ariaRowIndex();
if (aria_rowindex)
dst->AddIntAttribute(ui::AX_ATTR_ARIA_ROW_INDEX, aria_rowindex);
}
if ((dst->role == ui::AX_ROLE_ROW_HEADER ||
dst->role == ui::AX_ROLE_COLUMN_HEADER) && src.sortDirection()) {
dst->AddIntAttribute(ui::AX_ATTR_SORT_DIRECTION,
AXSortDirectionFromBlink(src.sortDirection()));
}
}
// The majority of the rest of this code computes attributes needed for
// all modes, not just for screen readers.
WebNode node = src.node();
bool is_iframe = false;
if (!node.isNull() && node.isElementNode()) {
WebElement element = node.to<WebElement>();
is_iframe = element.hasHTMLTagName("iframe");
if (accessibility_mode_.has_mode(AccessibilityMode::kHTML)) {
// TODO(ctguil): The tagName in WebKit is lower cased but
// HTMLElement::nodeName calls localNameUpper. Consider adding
// a WebElement method that returns the original lower cased tagName.
dst->AddStringAttribute(
ui::AX_ATTR_HTML_TAG,
base::ToLowerASCII(element.tagName().utf8()));
for (unsigned i = 0; i < element.attributeCount(); ++i) {
std::string name = base::ToLowerASCII(
element.attributeLocalName(i).utf8());
std::string value = element.attributeValue(i).utf8();
dst->html_attributes.push_back(std::make_pair(name, value));
}
}
if (src.isEditable()) {
dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_START, src.selectionStart());
dst->AddIntAttribute(ui::AX_ATTR_TEXT_SEL_END, src.selectionEnd());
#if defined(OS_CHROMEOS)
// This attribute will soon be deprecated; see crbug.com/669134.
WebVector<int> src_line_breaks;
src.lineBreaks(src_line_breaks);
if (src_line_breaks.size()) {
std::vector<int32_t> line_breaks;
line_breaks.reserve(src_line_breaks.size());
for (size_t i = 0; i < src_line_breaks.size(); ++i)
line_breaks.push_back(src_line_breaks[i]);
dst->AddIntListAttribute(ui::AX_ATTR_LINE_BREAKS, line_breaks);
}
#endif // defined OS_CHROMEOS
}
// ARIA role.
if (element.hasAttribute("role")) {
dst->AddStringAttribute(
ui::AX_ATTR_ROLE,
element.getAttribute("role").utf8());
} else {
std::string role = GetEquivalentAriaRoleString(dst->role);
if (!role.empty())
dst->AddStringAttribute(ui::AX_ATTR_ROLE, role);
}
// Browser plugin (used in a <webview>).
BrowserPlugin* browser_plugin = BrowserPlugin::GetFromNode(element);
if (browser_plugin) {
dst->AddContentIntAttribute(
AX_CONTENT_ATTR_CHILD_BROWSER_PLUGIN_INSTANCE_ID,
browser_plugin->browser_plugin_instance_id());
}
// Frames and iframes.
WebFrame* frame = WebFrame::fromFrameOwnerElement(element);
if (frame) {
dst->AddContentIntAttribute(
AX_CONTENT_ATTR_CHILD_ROUTING_ID,
GetRoutingIdForFrameOrProxy(frame));
}
}
// Add the ids of *indirect* children - those who are children of this node,
// but whose parent is *not* this node. One example is a table
// cell, which is a child of both a row and a column. Because the cell's
// parent is the row, the row adds it as a child, and the column adds it
// as an indirect child.
int child_count = src.childCount();
for (int i = 0; i < child_count; ++i) {
WebAXObject child = src.childAt(i);
std::vector<int32_t> indirect_child_ids;
if (!is_iframe && !child.isDetached() && !IsParentUnignoredOf(src, child))
indirect_child_ids.push_back(child.axID());
if (indirect_child_ids.size() > 0) {
dst->AddIntListAttribute(
ui::AX_ATTR_INDIRECT_CHILD_IDS, indirect_child_ids);
}
}
if (src.isScrollableContainer()) {
const gfx::Point& scrollOffset = src.getScrollOffset();
dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X, scrollOffset.x());
dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y, scrollOffset.y());
const gfx::Point& minScrollOffset = src.minimumScrollOffset();
dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MIN, minScrollOffset.x());
dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MIN, minScrollOffset.y());
const gfx::Point& maxScrollOffset = src.maximumScrollOffset();
dst->AddIntAttribute(ui::AX_ATTR_SCROLL_X_MAX, maxScrollOffset.x());
dst->AddIntAttribute(ui::AX_ATTR_SCROLL_Y_MAX, maxScrollOffset.y());
}
if (dst->id == image_data_node_id_) {
dst->AddStringAttribute(ui::AX_ATTR_IMAGE_DATA_URL,
src.imageDataUrl(max_image_data_size_).utf8());
}
}
blink::WebDocument BlinkAXTreeSource::GetMainDocument() const {
CHECK(frozen_);
return document_;
}
WebAXObject BlinkAXTreeSource::ComputeRoot() const {
if (!explicit_root_.isNull())
return explicit_root_;
if (!render_frame_ || !render_frame_->GetWebFrame())
return WebAXObject();
WebDocument document = render_frame_->GetWebFrame()->document();
if (!document.isNull())
return document.accessibilityObject();
return WebAXObject();
}
} // namespace content