| // 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 |