blob: 35f97e733b7dfc4a850d059fd159b99d5c650d09 [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/web_test/renderer/web_ax_object_proxy.h"
#include <stddef.h>
#include <algorithm>
#include <map>
#include <utility>
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "gin/handle.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/web/blink.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/skia/include/core/SkMatrix44.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom-shared.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/transform.h"
namespace content {
namespace {
// Map role value to string, matching Safari/Mac platform implementation to
// avoid rebaselining web tests.
std::string RoleToString(ax::mojom::Role role) {
std::string prefix = "k";
std::ostringstream result;
result << role;
// Check that |result| starts with |prefix|.
DCHECK_EQ(result.str().find(prefix), 0ull);
return "AXRole: AX" + result.str().substr(prefix.size());
}
std::string GetStringValue(const blink::WebAXObject& object) {
std::string value;
if (object.Role() == ax::mojom::Role::kColorWell) {
unsigned int color = object.ColorValue();
unsigned int red = (color >> 16) & 0xFF;
unsigned int green = (color >> 8) & 0xFF;
unsigned int blue = color & 0xFF;
value = base::StringPrintf("rgba(%d, %d, %d, 1)", red, green, blue);
} else {
value = object.GetValueForControl().Utf8();
}
return value.insert(0, "AXValue: ");
}
std::string GetRole(const blink::WebAXObject& object) {
std::string role_string = RoleToString(object.Role());
// Special-case canvas with fallback content because Chromium wants to treat
// this as essentially a separate role that it can map differently depending
// on the platform.
if (object.Role() == ax::mojom::Role::kCanvas &&
object.CanvasHasFallbackContent()) {
role_string += "WithFallbackContent";
}
return role_string;
}
std::string GetLanguage(const blink::WebAXObject& object) {
std::string language = object.Language().Utf8();
return language.insert(0, "AXLanguage: ");
}
std::string GetAttributes(const blink::WebAXObject& object) {
std::string attributes(object.GetName().Utf8());
attributes.append("\n");
attributes.append(GetRole(object));
return attributes;
}
// New bounds calculation algorithm. Retrieves the frame-relative bounds
// of an object by calling getRelativeBounds and then applying the offsets
// and transforms recursively on each container of this object.
gfx::RectF BoundsForObject(const blink::WebAXObject& object) {
blink::WebAXObject container;
gfx::RectF bounds;
SkMatrix44 matrix;
object.GetRelativeBounds(container, bounds, matrix);
gfx::RectF computed_bounds(0, 0, bounds.width(), bounds.height());
while (!container.IsDetached()) {
computed_bounds.Offset(bounds.x(), bounds.y());
computed_bounds.Offset(-container.GetScrollOffset().x(),
-container.GetScrollOffset().y());
if (!matrix.isIdentity()) {
gfx::Transform transform(matrix);
transform.TransformRect(&computed_bounds);
}
container.GetRelativeBounds(container, bounds, matrix);
}
return computed_bounds;
}
gfx::Rect BoundsForCharacter(const blink::WebAXObject& object,
int character_index) {
DCHECK_EQ(object.Role(), ax::mojom::Role::kStaticText);
int end = 0;
for (unsigned i = 0; i < object.ChildCount(); i++) {
blink::WebAXObject inline_text_box = object.ChildAt(i);
DCHECK_EQ(inline_text_box.Role(), ax::mojom::Role::kInlineTextBox);
int start = end;
blink::WebString name = inline_text_box.GetName();
end += name.length();
if (character_index < start || character_index >= end)
continue;
gfx::RectF inline_text_box_rect = BoundsForObject(inline_text_box);
int local_index = character_index - start;
blink::WebVector<int> character_offsets;
inline_text_box.CharacterOffsets(character_offsets);
if (character_offsets.size() != name.length())
return gfx::Rect();
switch (inline_text_box.GetTextDirection()) {
case ax::mojom::WritingDirection::kLtr: {
if (local_index) {
int left =
inline_text_box_rect.x() + character_offsets[local_index - 1];
int width = character_offsets[local_index] -
character_offsets[local_index - 1];
return gfx::Rect(left, inline_text_box_rect.y(), width,
inline_text_box_rect.height());
}
return gfx::Rect(inline_text_box_rect.x(), inline_text_box_rect.y(),
character_offsets[0], inline_text_box_rect.height());
}
case ax::mojom::WritingDirection::kRtl: {
int right = inline_text_box_rect.x() + inline_text_box_rect.width();
if (local_index) {
int left = right - character_offsets[local_index];
int width = character_offsets[local_index] -
character_offsets[local_index - 1];
return gfx::Rect(left, inline_text_box_rect.y(), width,
inline_text_box_rect.height());
}
int left = right - character_offsets[0];
return gfx::Rect(left, inline_text_box_rect.y(), character_offsets[0],
inline_text_box_rect.height());
}
case ax::mojom::WritingDirection::kTtb: {
if (local_index) {
int top =
inline_text_box_rect.y() + character_offsets[local_index - 1];
int height = character_offsets[local_index] -
character_offsets[local_index - 1];
return gfx::Rect(inline_text_box_rect.x(), top,
inline_text_box_rect.width(), height);
}
return gfx::Rect(inline_text_box_rect.x(), inline_text_box_rect.y(),
inline_text_box_rect.width(), character_offsets[0]);
}
case ax::mojom::WritingDirection::kBtt: {
int bottom = inline_text_box_rect.y() + inline_text_box_rect.height();
if (local_index) {
int top = bottom - character_offsets[local_index];
int height = character_offsets[local_index] -
character_offsets[local_index - 1];
return gfx::Rect(inline_text_box_rect.x(), top,
inline_text_box_rect.width(), height);
}
int top = bottom - character_offsets[0];
return gfx::Rect(inline_text_box_rect.x(), top,
inline_text_box_rect.width(), character_offsets[0]);
}
default:
NOTREACHED();
return gfx::Rect();
}
}
DCHECK(false);
return gfx::Rect();
}
void GetBoundariesForOneWord(const blink::WebAXObject& object,
int character_index,
int& word_start,
int& word_end) {
int end = 0;
for (size_t i = 0; i < object.ChildCount(); i++) {
blink::WebAXObject inline_text_box = object.ChildAt(i);
DCHECK_EQ(inline_text_box.Role(), ax::mojom::Role::kInlineTextBox);
int start = end;
blink::WebString name = inline_text_box.GetName();
end += name.length();
if (end <= character_index)
continue;
int local_index = character_index - start;
blink::WebVector<int> starts;
blink::WebVector<int> ends;
inline_text_box.GetWordBoundaries(starts, ends);
size_t word_count = starts.size();
DCHECK_EQ(ends.size(), word_count);
// If there are no words, use the InlineTextBox boundaries.
if (!word_count) {
word_start = start;
word_end = end;
return;
}
// Look for a character within any word other than the last.
for (size_t j = 0; j < word_count - 1; j++) {
if (local_index < ends[j]) {
word_start = start + starts[j];
word_end = start + ends[j];
return;
}
}
// Return the last word by default.
word_start = start + starts[word_count - 1];
word_end = start + ends[word_count - 1];
return;
}
}
// Collects attributes into a string, delimited by dashes. Used by all methods
// that output lists of attributes: attributesOfLinkedUIElementsCallback,
// AttributesOfChildrenCallback, etc.
class AttributesCollector {
public:
AttributesCollector() {}
~AttributesCollector() {}
void CollectAttributes(const blink::WebAXObject& object) {
attributes_.append("\n------------\n");
attributes_.append(GetAttributes(object));
}
std::string attributes() const { return attributes_; }
private:
std::string attributes_;
DISALLOW_COPY_AND_ASSIGN(AttributesCollector);
};
} // namespace
gin::WrapperInfo WebAXObjectProxy::kWrapperInfo = {gin::kEmbedderNativeGin};
WebAXObjectProxy::WebAXObjectProxy(const blink::WebAXObject& object,
WebAXObjectProxy::Factory* factory)
: accessibility_object_(object), factory_(factory) {}
WebAXObjectProxy::~WebAXObjectProxy() {
// v8::Persistent will leak on destroy, due to the default
// NonCopyablePersistentTraits (it claims this may change in the future).
notification_callback_.Reset();
}
void WebAXObjectProxy::UpdateLayout() {
blink::WebAXObject::UpdateLayout(accessibility_object_.GetDocument());
}
ui::AXNodeData WebAXObjectProxy::GetAXNodeData() const {
ui::AXNodeData node_data;
accessibility_object_.Serialize(&node_data, ui::kAXModeComplete);
return node_data;
}
gin::ObjectTemplateBuilder WebAXObjectProxy::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return gin::Wrappable<WebAXObjectProxy>::GetObjectTemplateBuilder(isolate)
.SetProperty("role", &WebAXObjectProxy::Role)
.SetProperty("stringValue", &WebAXObjectProxy::StringValue)
.SetProperty("language", &WebAXObjectProxy::Language)
.SetProperty("x", &WebAXObjectProxy::X)
.SetProperty("y", &WebAXObjectProxy::Y)
.SetProperty("width", &WebAXObjectProxy::Width)
.SetProperty("height", &WebAXObjectProxy::Height)
.SetProperty("inPageLinkTarget", &WebAXObjectProxy::InPageLinkTarget)
.SetProperty("intValue", &WebAXObjectProxy::IntValue)
.SetProperty("minValue", &WebAXObjectProxy::MinValue)
.SetProperty("maxValue", &WebAXObjectProxy::MaxValue)
.SetProperty("stepValue", &WebAXObjectProxy::StepValue)
.SetProperty("valueDescription", &WebAXObjectProxy::ValueDescription)
.SetProperty("childrenCount", &WebAXObjectProxy::ChildrenCount)
.SetProperty("selectionIsBackward",
&WebAXObjectProxy::SelectionIsBackward)
.SetProperty("selectionAnchorObject",
&WebAXObjectProxy::SelectionAnchorObject)
.SetProperty("selectionAnchorOffset",
&WebAXObjectProxy::SelectionAnchorOffset)
.SetProperty("selectionAnchorAffinity",
&WebAXObjectProxy::SelectionAnchorAffinity)
.SetProperty("selectionFocusObject",
&WebAXObjectProxy::SelectionFocusObject)
.SetProperty("selectionFocusOffset",
&WebAXObjectProxy::SelectionFocusOffset)
.SetProperty("selectionFocusAffinity",
&WebAXObjectProxy::SelectionFocusAffinity)
.SetProperty("isAtomic", &WebAXObjectProxy::IsAtomic)
.SetProperty("isAutofillAvailable",
&WebAXObjectProxy::IsAutofillAvailable)
.SetProperty("isBusy", &WebAXObjectProxy::IsBusy)
.SetProperty("isRequired", &WebAXObjectProxy::IsRequired)
.SetProperty("isEditable", &WebAXObjectProxy::IsEditable)
.SetProperty("isEditableRoot", &WebAXObjectProxy::IsEditableRoot)
.SetProperty("isRichlyEditable", &WebAXObjectProxy::IsRichlyEditable)
.SetProperty("isFocused", &WebAXObjectProxy::IsFocused)
.SetProperty("isFocusable", &WebAXObjectProxy::IsFocusable)
.SetProperty("isModal", &WebAXObjectProxy::IsModal)
.SetProperty("isSelected", &WebAXObjectProxy::IsSelected)
.SetProperty("isSelectable", &WebAXObjectProxy::IsSelectable)
.SetProperty("isMultiLine", &WebAXObjectProxy::IsMultiLine)
.SetProperty("isMultiSelectable", &WebAXObjectProxy::IsMultiSelectable)
.SetProperty("isSelectedOptionActive",
&WebAXObjectProxy::IsSelectedOptionActive)
.SetProperty("isExpanded", &WebAXObjectProxy::IsExpanded)
.SetProperty("checked", &WebAXObjectProxy::Checked)
.SetProperty("isVisible", &WebAXObjectProxy::IsVisible)
.SetProperty("isVisited", &WebAXObjectProxy::IsVisited)
.SetProperty("isOffScreen", &WebAXObjectProxy::IsOffScreen)
.SetProperty("isCollapsed", &WebAXObjectProxy::IsCollapsed)
.SetProperty("hasPopup", &WebAXObjectProxy::HasPopup)
.SetProperty("isValid", &WebAXObjectProxy::IsValid)
.SetProperty("isReadOnly", &WebAXObjectProxy::IsReadOnly)
.SetProperty("isIgnored", &WebAXObjectProxy::IsIgnored)
.SetProperty("restriction", &WebAXObjectProxy::Restriction)
.SetProperty("activeDescendant", &WebAXObjectProxy::ActiveDescendant)
.SetProperty("backgroundColor", &WebAXObjectProxy::BackgroundColor)
.SetProperty("color", &WebAXObjectProxy::Color)
.SetProperty("colorValue", &WebAXObjectProxy::ColorValue)
.SetProperty("fontFamily", &WebAXObjectProxy::FontFamily)
.SetProperty("fontSize", &WebAXObjectProxy::FontSize)
.SetProperty("autocomplete", &WebAXObjectProxy::Autocomplete)
.SetProperty("current", &WebAXObjectProxy::Current)
.SetProperty("invalid", &WebAXObjectProxy::Invalid)
.SetProperty("keyShortcuts", &WebAXObjectProxy::KeyShortcuts)
.SetProperty("ariaColumnCount", &WebAXObjectProxy::AriaColumnCount)
.SetProperty("ariaColumnIndex", &WebAXObjectProxy::AriaColumnIndex)
.SetProperty("ariaColumnSpan", &WebAXObjectProxy::AriaColumnSpan)
.SetProperty("ariaRowCount", &WebAXObjectProxy::AriaRowCount)
.SetProperty("ariaRowIndex", &WebAXObjectProxy::AriaRowIndex)
.SetProperty("ariaRowSpan", &WebAXObjectProxy::AriaRowSpan)
.SetProperty("live", &WebAXObjectProxy::Live)
.SetProperty("orientation", &WebAXObjectProxy::Orientation)
.SetProperty("relevant", &WebAXObjectProxy::Relevant)
.SetProperty("roleDescription", &WebAXObjectProxy::RoleDescription)
.SetProperty("sort", &WebAXObjectProxy::Sort)
.SetProperty("url", &WebAXObjectProxy::Url)
.SetProperty("hierarchicalLevel", &WebAXObjectProxy::HierarchicalLevel)
.SetProperty("posInSet", &WebAXObjectProxy::PosInSet)
.SetProperty("setSize", &WebAXObjectProxy::SetSize)
.SetProperty("clickPointX", &WebAXObjectProxy::ClickPointX)
.SetProperty("clickPointY", &WebAXObjectProxy::ClickPointY)
.SetProperty("rowCount", &WebAXObjectProxy::RowCount)
.SetProperty("rowHeadersCount", &WebAXObjectProxy::RowHeadersCount)
.SetProperty("columnCount", &WebAXObjectProxy::ColumnCount)
.SetProperty("columnHeadersCount", &WebAXObjectProxy::ColumnHeadersCount)
.SetProperty("isClickable", &WebAXObjectProxy::IsClickable)
//
// NEW bounding rect calculation - high-level interface
//
.SetProperty("boundsX", &WebAXObjectProxy::BoundsX)
.SetProperty("boundsY", &WebAXObjectProxy::BoundsY)
.SetProperty("boundsWidth", &WebAXObjectProxy::BoundsWidth)
.SetProperty("boundsHeight", &WebAXObjectProxy::BoundsHeight)
.SetMethod("allAttributes", &WebAXObjectProxy::AllAttributes)
.SetMethod("attributesOfChildren",
&WebAXObjectProxy::AttributesOfChildren)
.SetMethod("ariaActiveDescendantElement",
&WebAXObjectProxy::AriaActiveDescendantElement)
.SetMethod("ariaControlsElementAtIndex",
&WebAXObjectProxy::AriaControlsElementAtIndex)
.SetMethod("ariaDetailsElementAtIndex",
&WebAXObjectProxy::AriaDetailsElementAtIndex)
.SetMethod("ariaErrorMessageElement",
&WebAXObjectProxy::AriaErrorMessageElement)
.SetMethod("ariaFlowToElementAtIndex",
&WebAXObjectProxy::AriaFlowToElementAtIndex)
.SetMethod("ariaOwnsElementAtIndex",
&WebAXObjectProxy::AriaOwnsElementAtIndex)
.SetMethod("boundsForRange", &WebAXObjectProxy::BoundsForRange)
.SetMethod("childAtIndex", &WebAXObjectProxy::ChildAtIndex)
.SetMethod("elementAtPoint", &WebAXObjectProxy::ElementAtPoint)
.SetMethod("rowHeaderAtIndex", &WebAXObjectProxy::RowHeaderAtIndex)
.SetMethod("columnHeaderAtIndex", &WebAXObjectProxy::ColumnHeaderAtIndex)
.SetMethod("rowIndexRange", &WebAXObjectProxy::RowIndexRange)
.SetMethod("columnIndexRange", &WebAXObjectProxy::ColumnIndexRange)
.SetMethod("cellForColumnAndRow", &WebAXObjectProxy::CellForColumnAndRow)
.SetMethod("setSelectedTextRange",
&WebAXObjectProxy::SetSelectedTextRange)
.SetMethod("setSelection", &WebAXObjectProxy::SetSelection)
.SetMethod("isAttributeSettable", &WebAXObjectProxy::IsAttributeSettable)
.SetMethod("isPressActionSupported",
&WebAXObjectProxy::IsPressActionSupported)
.SetMethod("parentElement", &WebAXObjectProxy::ParentElement)
.SetMethod("increment", &WebAXObjectProxy::Increment)
.SetMethod("decrement", &WebAXObjectProxy::Decrement)
.SetMethod("showMenu", &WebAXObjectProxy::ShowMenu)
.SetMethod("press", &WebAXObjectProxy::Press)
.SetMethod("setValue", &WebAXObjectProxy::SetValue)
.SetMethod("isEqual", &WebAXObjectProxy::IsEqual)
.SetMethod("setNotificationListener",
&WebAXObjectProxy::SetNotificationListener)
.SetMethod("unsetNotificationListener",
&WebAXObjectProxy::UnsetNotificationListener)
.SetMethod("takeFocus", &WebAXObjectProxy::TakeFocus)
.SetMethod("scrollToMakeVisible", &WebAXObjectProxy::ScrollToMakeVisible)
.SetMethod("scrollToMakeVisibleWithSubFocus",
&WebAXObjectProxy::ScrollToMakeVisibleWithSubFocus)
.SetMethod("scrollToGlobalPoint", &WebAXObjectProxy::ScrollToGlobalPoint)
.SetMethod("scrollX", &WebAXObjectProxy::ScrollX)
.SetMethod("scrollY", &WebAXObjectProxy::ScrollY)
.SetMethod("toString", &WebAXObjectProxy::ToString)
.SetMethod("wordStart", &WebAXObjectProxy::WordStart)
.SetMethod("wordEnd", &WebAXObjectProxy::WordEnd)
.SetMethod("nextOnLine", &WebAXObjectProxy::NextOnLine)
.SetMethod("previousOnLine", &WebAXObjectProxy::PreviousOnLine)
.SetMethod("misspellingAtIndex", &WebAXObjectProxy::MisspellingAtIndex)
// TODO(hajimehoshi): This is for backward compatibility. Remove them.
.SetMethod("addNotificationListener",
&WebAXObjectProxy::SetNotificationListener)
.SetMethod("removeNotificationListener",
&WebAXObjectProxy::UnsetNotificationListener)
//
// NEW accessible name and description accessors
//
.SetProperty("name", &WebAXObjectProxy::Name)
.SetProperty("nameFrom", &WebAXObjectProxy::NameFrom)
.SetMethod("nameElementCount", &WebAXObjectProxy::NameElementCount)
.SetMethod("nameElementAtIndex", &WebAXObjectProxy::NameElementAtIndex)
.SetProperty("description", &WebAXObjectProxy::Description)
.SetProperty("descriptionFrom", &WebAXObjectProxy::DescriptionFrom)
.SetProperty("placeholder", &WebAXObjectProxy::Placeholder)
.SetProperty("misspellingsCount", &WebAXObjectProxy::MisspellingsCount)
.SetMethod("descriptionElementCount",
&WebAXObjectProxy::DescriptionElementCount)
.SetMethod("descriptionElementAtIndex",
&WebAXObjectProxy::DescriptionElementAtIndex)
//
// NEW bounding rect calculation - low-level interface
//
.SetMethod("offsetContainer", &WebAXObjectProxy::OffsetContainer)
.SetMethod("boundsInContainerX", &WebAXObjectProxy::BoundsInContainerX)
.SetMethod("boundsInContainerY", &WebAXObjectProxy::BoundsInContainerY)
.SetMethod("boundsInContainerWidth",
&WebAXObjectProxy::BoundsInContainerWidth)
.SetMethod("boundsInContainerHeight",
&WebAXObjectProxy::BoundsInContainerHeight)
.SetMethod("hasNonIdentityTransform",
&WebAXObjectProxy::HasNonIdentityTransform);
}
v8::Local<v8::Object> WebAXObjectProxy::GetChildAtIndex(unsigned index) {
UpdateLayout();
return factory_->GetOrCreate(accessibility_object_.ChildAt(index));
}
bool WebAXObjectProxy::IsRoot() const {
return false;
}
bool WebAXObjectProxy::IsEqualToObject(const blink::WebAXObject& other) {
return accessibility_object_.Equals(other);
}
void WebAXObjectProxy::NotificationReceived(
blink::WebLocalFrame* frame,
const std::string& notification_name,
const std::vector<ui::AXEventIntent>& event_intents) {
if (notification_callback_.IsEmpty())
return;
v8::Local<v8::Context> context = frame->MainWorldScriptContext();
if (context.IsEmpty())
return;
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::Local<v8::Array> intents_array(
v8::Array::New(isolate, event_intents.size()));
for (size_t i = 0; i < event_intents.size(); ++i) {
intents_array
->CreateDataProperty(context, uint32_t{i},
v8::String::NewFromUtf8(
isolate, event_intents[i].ToString().c_str())
.ToLocalChecked())
.Check();
}
v8::Local<v8::Value> argv[] = {
v8::String::NewFromUtf8(isolate, notification_name.c_str())
.ToLocalChecked(),
intents_array};
// TODO(aboxhall): Can we force this to run in a new task, to avoid
// dirtying layout during post-layout hooks?
frame->CallFunctionEvenIfScriptDisabled(
v8::Local<v8::Function>::New(isolate, notification_callback_),
context->Global(), base::size(argv), argv);
}
void WebAXObjectProxy::Reset() {
notification_callback_.Reset();
}
std::string WebAXObjectProxy::Role() {
UpdateLayout();
return GetRole(accessibility_object_);
}
std::string WebAXObjectProxy::StringValue() {
UpdateLayout();
return GetStringValue(accessibility_object_);
}
std::string WebAXObjectProxy::Language() {
UpdateLayout();
return GetLanguage(accessibility_object_);
}
int WebAXObjectProxy::X() {
UpdateLayout();
return BoundsForObject(accessibility_object_).x();
}
int WebAXObjectProxy::Y() {
UpdateLayout();
return BoundsForObject(accessibility_object_).y();
}
int WebAXObjectProxy::Width() {
UpdateLayout();
return BoundsForObject(accessibility_object_).width();
}
int WebAXObjectProxy::Height() {
UpdateLayout();
return BoundsForObject(accessibility_object_).height();
}
v8::Local<v8::Value> WebAXObjectProxy::InPageLinkTarget() {
UpdateLayout();
blink::WebAXObject target = accessibility_object_.InPageLinkTarget();
if (target.IsNull())
return v8::Null(blink::MainThreadIsolate());
return factory_->GetOrCreate(target);
}
int WebAXObjectProxy::IntValue() {
UpdateLayout();
if (accessibility_object_.SupportsRangeValue()) {
float value = 0.0f;
accessibility_object_.ValueForRange(&value);
return static_cast<int>(value);
} else if (accessibility_object_.Role() == ax::mojom::Role::kHeading) {
return accessibility_object_.HeadingLevel();
} else {
return atoi(accessibility_object_.GetValueForControl().Utf8().data());
}
}
int WebAXObjectProxy::MinValue() {
UpdateLayout();
float min_value = 0.0f;
accessibility_object_.MinValueForRange(&min_value);
return min_value;
}
int WebAXObjectProxy::MaxValue() {
UpdateLayout();
float max_value = 0.0f;
accessibility_object_.MaxValueForRange(&max_value);
return max_value;
}
int WebAXObjectProxy::StepValue() {
UpdateLayout();
float step_value = 0.0f;
accessibility_object_.StepValueForRange(&step_value);
return step_value;
}
std::string WebAXObjectProxy::ValueDescription() {
UpdateLayout();
std::string value_description =
GetAXNodeData().GetStringAttribute(ax::mojom::StringAttribute::kValue);
return value_description.insert(0, "AXValueDescription: ");
}
int WebAXObjectProxy::ChildrenCount() {
UpdateLayout();
int count = 1; // Root object always has only one child, the WebView.
if (!IsRoot())
count = accessibility_object_.ChildCount();
return count;
}
bool WebAXObjectProxy::SelectionIsBackward() {
UpdateLayout();
bool is_selection_backward = false;
blink::WebAXObject anchor_object;
int anchor_offset = -1;
ax::mojom::TextAffinity anchor_affinity;
blink::WebAXObject focus_object;
int focus_offset = -1;
ax::mojom::TextAffinity focus_affinity;
accessibility_object_.Selection(is_selection_backward, anchor_object,
anchor_offset, anchor_affinity, focus_object,
focus_offset, focus_affinity);
return is_selection_backward;
}
v8::Local<v8::Value> WebAXObjectProxy::SelectionAnchorObject() {
UpdateLayout();
bool is_selection_backward = false;
blink::WebAXObject anchor_object;
int anchor_offset = -1;
ax::mojom::TextAffinity anchor_affinity;
blink::WebAXObject focus_object;
int focus_offset = -1;
ax::mojom::TextAffinity focus_affinity;
accessibility_object_.Selection(is_selection_backward, anchor_object,
anchor_offset, anchor_affinity, focus_object,
focus_offset, focus_affinity);
if (anchor_object.IsNull())
return v8::Null(blink::MainThreadIsolate());
return factory_->GetOrCreate(anchor_object);
}
int WebAXObjectProxy::SelectionAnchorOffset() {
UpdateLayout();
bool is_selection_backward = false;
blink::WebAXObject anchor_object;
int anchor_offset = -1;
ax::mojom::TextAffinity anchor_affinity;
blink::WebAXObject focus_object;
int focus_offset = -1;
ax::mojom::TextAffinity focus_affinity;
accessibility_object_.Selection(is_selection_backward, anchor_object,
anchor_offset, anchor_affinity, focus_object,
focus_offset, focus_affinity);
if (anchor_offset < 0)
return -1;
return anchor_offset;
}
std::string WebAXObjectProxy::SelectionAnchorAffinity() {
UpdateLayout();
bool is_selection_backward = false;
blink::WebAXObject anchor_object;
int anchor_offset = -1;
ax::mojom::TextAffinity anchor_affinity;
blink::WebAXObject focus_object;
int focus_offset = -1;
ax::mojom::TextAffinity focus_affinity;
accessibility_object_.Selection(is_selection_backward, anchor_object,
anchor_offset, anchor_affinity, focus_object,
focus_offset, focus_affinity);
return anchor_affinity == ax::mojom::TextAffinity::kUpstream ? "upstream"
: "downstream";
}
v8::Local<v8::Value> WebAXObjectProxy::SelectionFocusObject() {
UpdateLayout();
bool is_selection_backward = false;
blink::WebAXObject anchor_object;
int anchor_offset = -1;
ax::mojom::TextAffinity anchor_affinity;
blink::WebAXObject focus_object;
int focus_offset = -1;
ax::mojom::TextAffinity focus_affinity;
accessibility_object_.Selection(is_selection_backward, anchor_object,
anchor_offset, anchor_affinity, focus_object,
focus_offset, focus_affinity);
if (focus_object.IsNull())
return v8::Null(blink::MainThreadIsolate());
return factory_->GetOrCreate(focus_object);
}
int WebAXObjectProxy::SelectionFocusOffset() {
UpdateLayout();
bool is_selection_backward = false;
blink::WebAXObject anchor_object;
int anchor_offset = -1;
ax::mojom::TextAffinity anchor_affinity;
blink::WebAXObject focus_object;
int focus_offset = -1;
ax::mojom::TextAffinity focus_affinity;
accessibility_object_.Selection(is_selection_backward, anchor_object,
anchor_offset, anchor_affinity, focus_object,
focus_offset, focus_affinity);
if (focus_offset < 0)
return -1;
return focus_offset;
}
std::string WebAXObjectProxy::SelectionFocusAffinity() {
UpdateLayout();
bool is_selection_backward = false;
blink::WebAXObject anchor_object;
int anchor_offset = -1;
ax::mojom::TextAffinity anchor_affinity;
blink::WebAXObject focus_object;
int focus_offset = -1;
ax::mojom::TextAffinity focus_affinity;
accessibility_object_.Selection(is_selection_backward, anchor_object,
anchor_offset, anchor_affinity, focus_object,
focus_offset, focus_affinity);
return focus_affinity == ax::mojom::TextAffinity::kUpstream ? "upstream"
: "downstream";
}
bool WebAXObjectProxy::IsAtomic() {
UpdateLayout();
return accessibility_object_.LiveRegionAtomic();
}
bool WebAXObjectProxy::IsAutofillAvailable() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kAutofillAvailable);
}
bool WebAXObjectProxy::IsBusy() {
UpdateLayout();
return GetAXNodeData().GetBoolAttribute(ax::mojom::BoolAttribute::kBusy);
}
std::string WebAXObjectProxy::Restriction() {
UpdateLayout();
blink::WebAXRestriction web_ax_restriction =
static_cast<blink::WebAXRestriction>(GetAXNodeData().GetRestriction());
switch (web_ax_restriction) {
case blink::kWebAXRestrictionReadOnly:
return "readOnly";
case blink::kWebAXRestrictionDisabled:
return "disabled";
case blink::kWebAXRestrictionNone:
break;
}
return "none";
}
bool WebAXObjectProxy::IsRequired() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kRequired);
}
bool WebAXObjectProxy::IsEditableRoot() {
UpdateLayout();
return GetAXNodeData().GetBoolAttribute(
ax::mojom::BoolAttribute::kEditableRoot);
}
bool WebAXObjectProxy::IsEditable() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kEditable);
}
bool WebAXObjectProxy::IsRichlyEditable() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kRichlyEditable);
}
bool WebAXObjectProxy::IsFocused() {
UpdateLayout();
return accessibility_object_.IsFocused();
}
bool WebAXObjectProxy::IsFocusable() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kFocusable);
}
bool WebAXObjectProxy::IsModal() {
UpdateLayout();
return accessibility_object_.IsModal();
}
bool WebAXObjectProxy::IsSelected() {
UpdateLayout();
return GetAXNodeData().GetBoolAttribute(ax::mojom::BoolAttribute::kSelected);
}
bool WebAXObjectProxy::IsSelectable() {
UpdateLayout();
ui::AXNodeData node_data = GetAXNodeData();
// It's selectable if it has the attribute, whether it's true or false.
return node_data.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected) &&
node_data.GetRestriction() != ax::mojom::Restriction::kDisabled;
}
bool WebAXObjectProxy::IsMultiLine() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kMultiline);
}
bool WebAXObjectProxy::IsMultiSelectable() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kMultiselectable);
}
bool WebAXObjectProxy::IsSelectedOptionActive() {
UpdateLayout();
return accessibility_object_.IsSelectedOptionActive();
}
bool WebAXObjectProxy::IsExpanded() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kExpanded);
}
std::string WebAXObjectProxy::Checked() {
UpdateLayout();
switch (accessibility_object_.CheckedState()) {
case ax::mojom::CheckedState::kTrue:
return "true";
case ax::mojom::CheckedState::kMixed:
return "mixed";
case ax::mojom::CheckedState::kFalse:
return "false";
default:
return std::string();
}
}
bool WebAXObjectProxy::IsCollapsed() {
UpdateLayout();
return GetAXNodeData().HasState(ax::mojom::State::kCollapsed);
}
bool WebAXObjectProxy::IsVisible() {
UpdateLayout();
return !GetAXNodeData().HasState(ax::mojom::State::kInvisible);
}
bool WebAXObjectProxy::IsVisited() {
UpdateLayout();
return accessibility_object_.IsVisited();
}
bool WebAXObjectProxy::IsOffScreen() {
UpdateLayout();
return accessibility_object_.IsOffScreen();
}
bool WebAXObjectProxy::IsValid() {
UpdateLayout();
return !accessibility_object_.IsDetached();
}
bool WebAXObjectProxy::IsReadOnly() {
UpdateLayout();
return GetAXNodeData().GetRestriction() == ax::mojom::Restriction::kReadOnly;
}
bool WebAXObjectProxy::IsIgnored() {
UpdateLayout();
return accessibility_object_.AccessibilityIsIgnored();
}
v8::Local<v8::Object> WebAXObjectProxy::ActiveDescendant() {
UpdateLayout();
blink::WebAXObject element = accessibility_object_.AriaActiveDescendant();
return factory_->GetOrCreate(element);
}
unsigned int WebAXObjectProxy::BackgroundColor() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(
ax::mojom::IntAttribute::kBackgroundColor);
}
unsigned int WebAXObjectProxy::Color() {
UpdateLayout();
unsigned int color =
GetAXNodeData().GetIntAttribute(ax::mojom::IntAttribute::kColor);
// Remove the alpha because it's always 1 and thus not informative.
return color & 0xFFFFFF;
}
// For input elements of type color.
unsigned int WebAXObjectProxy::ColorValue() {
UpdateLayout();
return accessibility_object_.ColorValue();
}
std::string WebAXObjectProxy::FontFamily() {
UpdateLayout();
std::string font_family = GetAXNodeData().GetStringAttribute(
ax::mojom::StringAttribute::kFontFamily);
return font_family.insert(0, "AXFontFamily: ");
}
float WebAXObjectProxy::FontSize() {
UpdateLayout();
return GetAXNodeData().GetFloatAttribute(
ax::mojom::FloatAttribute::kFontSize);
}
std::string WebAXObjectProxy::Autocomplete() {
UpdateLayout();
return accessibility_object_.AutoComplete().Utf8();
}
std::string WebAXObjectProxy::Current() {
UpdateLayout();
switch (accessibility_object_.AriaCurrentState()) {
case ax::mojom::AriaCurrentState::kFalse:
return "false";
case ax::mojom::AriaCurrentState::kTrue:
return "true";
case ax::mojom::AriaCurrentState::kPage:
return "page";
case ax::mojom::AriaCurrentState::kStep:
return "step";
case ax::mojom::AriaCurrentState::kLocation:
return "location";
case ax::mojom::AriaCurrentState::kDate:
return "date";
case ax::mojom::AriaCurrentState::kTime:
return "time";
default:
return std::string();
}
}
std::string WebAXObjectProxy::HasPopup() {
UpdateLayout();
switch (GetAXNodeData().GetHasPopup()) {
case ax::mojom::HasPopup::kTrue:
return "true";
case ax::mojom::HasPopup::kMenu:
return "menu";
case ax::mojom::HasPopup::kListbox:
return "listbox";
case ax::mojom::HasPopup::kTree:
return "tree";
case ax::mojom::HasPopup::kGrid:
return "grid";
case ax::mojom::HasPopup::kDialog:
return "dialog";
default:
return std::string();
}
}
std::string WebAXObjectProxy::Invalid() {
UpdateLayout();
switch (accessibility_object_.InvalidState()) {
case ax::mojom::InvalidState::kFalse:
return "false";
case ax::mojom::InvalidState::kTrue:
return "true";
case ax::mojom::InvalidState::kOther:
return "other";
default:
return std::string();
}
}
std::string WebAXObjectProxy::KeyShortcuts() {
UpdateLayout();
return GetAXNodeData().GetStringAttribute(
ax::mojom::StringAttribute::kKeyShortcuts);
}
int32_t WebAXObjectProxy::AriaColumnCount() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(
ax::mojom::IntAttribute::kAriaColumnCount);
}
uint32_t WebAXObjectProxy::AriaColumnIndex() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(
ax::mojom::IntAttribute::kAriaCellColumnIndex);
}
uint32_t WebAXObjectProxy::AriaColumnSpan() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(
ax::mojom::IntAttribute::kAriaCellColumnSpan);
}
int32_t WebAXObjectProxy::AriaRowCount() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(
ax::mojom::IntAttribute::kAriaRowCount);
}
uint32_t WebAXObjectProxy::AriaRowIndex() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(
ax::mojom::IntAttribute::kAriaCellRowIndex);
}
uint32_t WebAXObjectProxy::AriaRowSpan() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(
ax::mojom::IntAttribute::kAriaCellRowSpan);
}
std::string WebAXObjectProxy::Live() {
UpdateLayout();
return accessibility_object_.LiveRegionStatus().Utf8();
}
std::string WebAXObjectProxy::Orientation() {
UpdateLayout();
ui::AXNodeData node_data = GetAXNodeData();
if (node_data.HasState(ax::mojom::State::kVertical))
return "AXOrientation: AXVerticalOrientation";
else if (node_data.HasState(ax::mojom::State::kHorizontal))
return "AXOrientation: AXHorizontalOrientation";
return std::string();
}
std::string WebAXObjectProxy::Relevant() {
UpdateLayout();
return accessibility_object_.LiveRegionRelevant().Utf8();
}
std::string WebAXObjectProxy::RoleDescription() {
UpdateLayout();
return GetAXNodeData().GetStringAttribute(
ax::mojom::StringAttribute::kRoleDescription);
}
std::string WebAXObjectProxy::Sort() {
UpdateLayout();
switch (accessibility_object_.SortDirection()) {
case ax::mojom::SortDirection::kAscending:
return "ascending";
case ax::mojom::SortDirection::kDescending:
return "descending";
case ax::mojom::SortDirection::kOther:
return "other";
default:
return std::string();
}
}
std::string WebAXObjectProxy::Url() {
UpdateLayout();
return accessibility_object_.Url().GetString().Utf8();
}
int WebAXObjectProxy::HierarchicalLevel() {
UpdateLayout();
return accessibility_object_.HierarchicalLevel();
}
int WebAXObjectProxy::PosInSet() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
}
int WebAXObjectProxy::SetSize() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
}
int WebAXObjectProxy::ClickPointX() {
UpdateLayout();
gfx::RectF bounds = BoundsForObject(accessibility_object_);
return bounds.x() + bounds.width() / 2;
}
int WebAXObjectProxy::ClickPointY() {
UpdateLayout();
gfx::RectF bounds = BoundsForObject(accessibility_object_);
return bounds.y() + bounds.height() / 2;
}
int32_t WebAXObjectProxy::RowCount() {
UpdateLayout();
return static_cast<int32_t>(accessibility_object_.RowCount());
}
int32_t WebAXObjectProxy::RowHeadersCount() {
UpdateLayout();
blink::WebVector<blink::WebAXObject> headers;
accessibility_object_.RowHeaders(headers);
return static_cast<int32_t>(headers.size());
}
int32_t WebAXObjectProxy::ColumnCount() {
UpdateLayout();
return static_cast<int32_t>(accessibility_object_.ColumnCount());
}
int32_t WebAXObjectProxy::ColumnHeadersCount() {
UpdateLayout();
blink::WebVector<blink::WebAXObject> headers;
accessibility_object_.ColumnHeaders(headers);
return static_cast<int32_t>(headers.size());
}
bool WebAXObjectProxy::IsClickable() {
UpdateLayout();
return accessibility_object_.IsClickable();
}
v8::Local<v8::Object> WebAXObjectProxy::AriaActiveDescendantElement() {
UpdateLayout();
int ax_id = GetAXNodeData().GetIntAttribute(
ax::mojom::IntAttribute::kActivedescendantId);
if (!ax_id)
return v8::Local<v8::Object>();
blink::WebAXObject web_ax_object = blink::WebAXObject::FromWebDocumentByID(
accessibility_object_.GetDocument(), ax_id);
return factory_->GetOrCreate(web_ax_object);
}
v8::Local<v8::Object> WebAXObjectProxy::AriaControlsElementAtIndex(
unsigned index) {
UpdateLayout();
auto ax_ids = GetAXNodeData().GetIntListAttribute(
ax::mojom::IntListAttribute::kControlsIds);
size_t element_count = ax_ids.size();
if (index >= element_count)
return v8::Local<v8::Object>();
blink::WebAXObject web_ax_object = blink::WebAXObject::FromWebDocumentByID(
accessibility_object_.GetDocument(), ax_ids[index]);
return factory_->GetOrCreate(web_ax_object);
}
v8::Local<v8::Object> WebAXObjectProxy::AriaDetailsElementAtIndex(
unsigned index) {
UpdateLayout();
auto ax_ids = GetAXNodeData().GetIntListAttribute(
ax::mojom::IntListAttribute::kDetailsIds);
size_t element_count = ax_ids.size();
if (index >= element_count)
return v8::Local<v8::Object>();
blink::WebAXObject web_ax_object = blink::WebAXObject::FromWebDocumentByID(
accessibility_object_.GetDocument(), ax_ids[index]);
return factory_->GetOrCreate(web_ax_object);
}
v8::Local<v8::Object> WebAXObjectProxy::AriaErrorMessageElement() {
UpdateLayout();
int ax_id =
GetAXNodeData().GetIntAttribute(ax::mojom::IntAttribute::kErrormessageId);
if (!ax_id)
return v8::Local<v8::Object>();
blink::WebAXObject web_ax_object = blink::WebAXObject::FromWebDocumentByID(
accessibility_object_.GetDocument(), ax_id);
return factory_->GetOrCreate(web_ax_object);
}
v8::Local<v8::Object> WebAXObjectProxy::AriaFlowToElementAtIndex(
unsigned index) {
UpdateLayout();
auto ax_ids = GetAXNodeData().GetIntListAttribute(
ax::mojom::IntListAttribute::kFlowtoIds);
size_t element_count = ax_ids.size();
if (index >= element_count)
return v8::Local<v8::Object>();
blink::WebAXObject web_ax_object = blink::WebAXObject::FromWebDocumentByID(
accessibility_object_.GetDocument(), ax_ids[index]);
return factory_->GetOrCreate(web_ax_object);
}
v8::Local<v8::Object> WebAXObjectProxy::AriaOwnsElementAtIndex(unsigned index) {
UpdateLayout();
blink::WebVector<blink::WebAXObject> elements;
accessibility_object_.AriaOwns(elements);
size_t element_count = elements.size();
if (index >= element_count)
return v8::Local<v8::Object>();
return factory_->GetOrCreate(elements[index]);
}
std::string WebAXObjectProxy::AllAttributes() {
UpdateLayout();
return GetAttributes(accessibility_object_);
}
std::string WebAXObjectProxy::AttributesOfChildren() {
UpdateLayout();
AttributesCollector collector;
unsigned size = accessibility_object_.ChildCount();
for (unsigned i = 0; i < size; ++i)
collector.CollectAttributes(accessibility_object_.ChildAt(i));
return collector.attributes();
}
std::string WebAXObjectProxy::BoundsForRange(int start, int end) {
UpdateLayout();
if (accessibility_object_.Role() != ax::mojom::Role::kStaticText)
return std::string();
if (!accessibility_object_.MaybeUpdateLayoutAndCheckValidity())
return std::string();
int len = end - start;
// Get the bounds for each character and union them into one large rectangle.
// This is just for testing so it doesn't need to be efficient.
gfx::Rect bounds = BoundsForCharacter(accessibility_object_, start);
for (int i = 1; i < len; i++) {
gfx::Rect next = BoundsForCharacter(accessibility_object_, start + i);
int right = std::max(bounds.x() + bounds.width(), next.x() + next.width());
int bottom =
std::max(bounds.y() + bounds.height(), next.y() + next.height());
bounds.set_x(std::min(bounds.x(), next.x()));
bounds.set_y(std::min(bounds.y(), next.y()));
bounds.set_width(right - bounds.x());
bounds.set_height(bottom - bounds.y());
}
return base::StringPrintf("{x: %d, y: %d, width: %d, height: %d}", bounds.x(),
bounds.y(), bounds.width(), bounds.height());
}
v8::Local<v8::Object> WebAXObjectProxy::ChildAtIndex(int index) {
// Scripts can sometimes provide bad input.
// Return undefined object in case of range error, rather than passing a bad
// index into the a11y core, where it would trigger a DCHECK.
int num_children = ChildrenCount();
if (index < 0 || index >= num_children)
return v8::Local<v8::Object>();
return GetChildAtIndex(index);
}
v8::Local<v8::Object> WebAXObjectProxy::ElementAtPoint(int x, int y) {
UpdateLayout();
gfx::Point point(x, y);
blink::WebAXObject obj = accessibility_object_.HitTest(point);
if (obj.IsNull())
return v8::Local<v8::Object>();
return factory_->GetOrCreate(obj);
}
v8::Local<v8::Object> WebAXObjectProxy::RowHeaderAtIndex(unsigned index) {
UpdateLayout();
blink::WebVector<blink::WebAXObject> headers;
accessibility_object_.RowHeaders(headers);
size_t header_count = headers.size();
if (index >= header_count)
return {};
return factory_->GetOrCreate(headers[index]);
}
v8::Local<v8::Object> WebAXObjectProxy::ColumnHeaderAtIndex(unsigned index) {
UpdateLayout();
blink::WebVector<blink::WebAXObject> headers;
accessibility_object_.ColumnHeaders(headers);
size_t header_count = headers.size();
if (index >= header_count)
return {};
return factory_->GetOrCreate(headers[index]);
}
std::string WebAXObjectProxy::RowIndexRange() {
UpdateLayout();
unsigned row_index = accessibility_object_.CellRowIndex();
unsigned row_span = accessibility_object_.CellRowSpan();
return base::StringPrintf("{%d, %d}", row_index, row_span);
}
std::string WebAXObjectProxy::ColumnIndexRange() {
UpdateLayout();
unsigned column_index = accessibility_object_.CellColumnIndex();
unsigned column_span = accessibility_object_.CellColumnSpan();
return base::StringPrintf("{%d, %d}", column_index, column_span);
}
v8::Local<v8::Object> WebAXObjectProxy::CellForColumnAndRow(int column,
int row) {
UpdateLayout();
blink::WebAXObject obj =
accessibility_object_.CellForColumnAndRow(column, row);
if (obj.IsNull())
return v8::Local<v8::Object>();
return factory_->GetOrCreate(obj);
}
void WebAXObjectProxy::SetSelectedTextRange(int selection_start, int length) {
UpdateLayout();
accessibility_object_.SetSelection(accessibility_object_, selection_start,
accessibility_object_,
selection_start + length);
}
bool WebAXObjectProxy::SetSelection(v8::Local<v8::Value> anchor_object,
int anchor_offset,
v8::Local<v8::Value> focus_object,
int focus_offset) {
if (anchor_object.IsEmpty() || focus_object.IsEmpty() ||
!anchor_object->IsObject() || !focus_object->IsObject() ||
anchor_offset < 0 || focus_offset < 0) {
return false;
}
WebAXObjectProxy* web_ax_anchor = nullptr;
if (!gin::ConvertFromV8(blink::MainThreadIsolate(), anchor_object,
&web_ax_anchor)) {
return false;
}
DCHECK(web_ax_anchor);
WebAXObjectProxy* web_ax_focus = nullptr;
if (!gin::ConvertFromV8(blink::MainThreadIsolate(), focus_object,
&web_ax_focus)) {
return false;
}
DCHECK(web_ax_focus);
UpdateLayout();
return accessibility_object_.SetSelection(
web_ax_anchor->accessibility_object_, anchor_offset,
web_ax_focus->accessibility_object_, focus_offset);
}
bool WebAXObjectProxy::IsAttributeSettable(const std::string& attribute) {
UpdateLayout();
bool settable = false;
if (attribute == "AXValue")
settable = accessibility_object_.CanSetValueAttribute();
return settable;
}
bool WebAXObjectProxy::IsPressActionSupported() {
UpdateLayout();
return accessibility_object_.CanPress();
}
v8::Local<v8::Object> WebAXObjectProxy::ParentElement() {
UpdateLayout();
blink::WebAXObject parent_object = accessibility_object_.ParentObject();
return factory_->GetOrCreate(parent_object);
}
void WebAXObjectProxy::Increment() {
UpdateLayout();
ui::AXActionData action_data;
action_data.action = ax::mojom::Action::kIncrement;
accessibility_object_.PerformAction(action_data);
}
void WebAXObjectProxy::Decrement() {
UpdateLayout();
ui::AXActionData action_data;
action_data.action = ax::mojom::Action::kDecrement;
accessibility_object_.PerformAction(action_data);
}
void WebAXObjectProxy::ShowMenu() {
UpdateLayout();
ui::AXActionData action_data;
action_data.action = ax::mojom::Action::kShowContextMenu;
accessibility_object_.PerformAction(action_data);
}
void WebAXObjectProxy::Press() {
UpdateLayout();
ui::AXActionData action_data;
action_data.action = ax::mojom::Action::kDoDefault;
accessibility_object_.PerformAction(action_data);
}
bool WebAXObjectProxy::SetValue(const std::string& value) {
UpdateLayout();
if (GetAXNodeData().GetRestriction() != ax::mojom::Restriction::kNone ||
accessibility_object_.GetValueForControl().IsEmpty()) {
return false;
}
ui::AXActionData action_data;
action_data.action = ax::mojom::Action::kSetValue;
action_data.value = value;
return accessibility_object_.PerformAction(action_data);
}
bool WebAXObjectProxy::IsEqual(v8::Local<v8::Object> proxy) {
WebAXObjectProxy* unwrapped_proxy = nullptr;
if (!gin::ConvertFromV8(blink::MainThreadIsolate(), proxy, &unwrapped_proxy))
return false;
return unwrapped_proxy->IsEqualToObject(accessibility_object_);
}
void WebAXObjectProxy::SetNotificationListener(
v8::Local<v8::Function> callback) {
v8::Isolate* isolate = blink::MainThreadIsolate();
notification_callback_.Reset(isolate, callback);
}
void WebAXObjectProxy::UnsetNotificationListener() {
notification_callback_.Reset();
}
void WebAXObjectProxy::TakeFocus() {
UpdateLayout();
ui::AXActionData action_data;
action_data.action = ax::mojom::Action::kFocus;
accessibility_object_.PerformAction(action_data);
}
void WebAXObjectProxy::ScrollToMakeVisible() {
UpdateLayout();
accessibility_object_.ScrollToMakeVisible();
}
void WebAXObjectProxy::ScrollToMakeVisibleWithSubFocus(int x,
int y,
int width,
int height) {
UpdateLayout();
accessibility_object_.ScrollToMakeVisibleWithSubFocus(
gfx::Rect(x, y, width, height));
}
void WebAXObjectProxy::ScrollToGlobalPoint(int x, int y) {
UpdateLayout();
ui::AXActionData action_data;
action_data.action = ax::mojom::Action::kScrollToPoint;
action_data.target_point = gfx::Point(x, y);
accessibility_object_.PerformAction(action_data);
}
int WebAXObjectProxy::ScrollX() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
}
int WebAXObjectProxy::ScrollY() {
UpdateLayout();
return GetAXNodeData().GetIntAttribute(ax::mojom::IntAttribute::kScrollY);
}
std::string WebAXObjectProxy::ToString() {
UpdateLayout();
return accessibility_object_.ToString().Utf8();
}
float WebAXObjectProxy::BoundsX() {
UpdateLayout();
return BoundsForObject(accessibility_object_).x();
}
float WebAXObjectProxy::BoundsY() {
UpdateLayout();
return BoundsForObject(accessibility_object_).y();
}
float WebAXObjectProxy::BoundsWidth() {
UpdateLayout();
return BoundsForObject(accessibility_object_).width();
}
float WebAXObjectProxy::BoundsHeight() {
UpdateLayout();
return BoundsForObject(accessibility_object_).height();
}
int WebAXObjectProxy::WordStart(int character_index) {
UpdateLayout();
if (accessibility_object_.Role() != ax::mojom::Role::kStaticText)
return -1;
int word_start = 0, word_end = 0;
GetBoundariesForOneWord(accessibility_object_, character_index, word_start,
word_end);
return word_start;
}
int WebAXObjectProxy::WordEnd(int character_index) {
UpdateLayout();
if (accessibility_object_.Role() != ax::mojom::Role::kStaticText)
return -1;
int word_start = 0, word_end = 0;
GetBoundariesForOneWord(accessibility_object_, character_index, word_start,
word_end);
return word_end;
}
v8::Local<v8::Object> WebAXObjectProxy::NextOnLine() {
UpdateLayout();
blink::WebAXObject obj = accessibility_object_.NextOnLine();
if (obj.IsNull())
return v8::Local<v8::Object>();
return factory_->GetOrCreate(obj);
}
v8::Local<v8::Object> WebAXObjectProxy::PreviousOnLine() {
UpdateLayout();
blink::WebAXObject obj = accessibility_object_.PreviousOnLine();
if (obj.IsNull())
return v8::Local<v8::Object>();
return factory_->GetOrCreate(obj);
}
std::vector<std::string> WebAXObjectProxy::GetMisspellings() const {
std::vector<std::string> misspellings;
std::string text(accessibility_object_.GetName().Utf8());
if (text.empty())
return {};
const ui::AXNodeData& node_data = GetAXNodeData();
std::vector<int32_t> marker_types;
std::vector<int32_t> marker_starts;
std::vector<int32_t> marker_ends;
if (node_data.GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes,
&marker_types) &&
node_data.GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts,
&marker_starts) &&
node_data.GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds,
&marker_ends)) {
DCHECK_EQ(marker_types.size(), marker_starts.size());
DCHECK_EQ(marker_types.size(), marker_ends.size());
for (size_t i = 0; i < marker_types.size(); ++i) {
if (marker_types[i] == int32_t(ax::mojom::MarkerType::kSpelling)) {
DCHECK_LE(marker_starts[i], marker_ends[i]);
misspellings.push_back(
text.substr(marker_starts[i], marker_ends[i] - marker_starts[i]));
}
}
return misspellings;
}
return {};
}
std::string WebAXObjectProxy::MisspellingAtIndex(int index) {
UpdateLayout();
std::vector<std::string> misspellings = GetMisspellings();
if (index < 0 || index >= int{misspellings.size()})
return std::string();
return misspellings[index];
}
std::string WebAXObjectProxy::Name() {
UpdateLayout();
return accessibility_object_.GetName().Utf8();
}
std::string WebAXObjectProxy::NameFrom() {
UpdateLayout();
ax::mojom::NameFrom name_from = ax::mojom::NameFrom::kUninitialized;
blink::WebVector<blink::WebAXObject> name_objects;
accessibility_object_.GetName(name_from, name_objects);
switch (name_from) {
case ax::mojom::NameFrom::kUninitialized:
case ax::mojom::NameFrom::kNone:
return "";
case ax::mojom::NameFrom::kAttribute:
return "attribute";
case ax::mojom::NameFrom::kAttributeExplicitlyEmpty:
return "attributeExplicitlyEmpty";
case ax::mojom::NameFrom::kCaption:
return "caption";
case ax::mojom::NameFrom::kContents:
return "contents";
case ax::mojom::NameFrom::kPlaceholder:
return "placeholder";
case ax::mojom::NameFrom::kRelatedElement:
return "relatedElement";
case ax::mojom::NameFrom::kValue:
return "value";
case ax::mojom::NameFrom::kTitle:
return "title";
}
NOTREACHED();
return std::string();
}
int WebAXObjectProxy::NameElementCount() {
UpdateLayout();
ax::mojom::NameFrom name_from;
blink::WebVector<blink::WebAXObject> name_objects;
accessibility_object_.GetName(name_from, name_objects);
return static_cast<int>(name_objects.size());
}
v8::Local<v8::Object> WebAXObjectProxy::NameElementAtIndex(unsigned index) {
UpdateLayout();
ax::mojom::NameFrom name_from;
blink::WebVector<blink::WebAXObject> name_objects;
accessibility_object_.GetName(name_from, name_objects);
if (index >= name_objects.size())
return {};
return factory_->GetOrCreate(name_objects[index]);
}
std::string WebAXObjectProxy::Description() {
UpdateLayout();
ax::mojom::NameFrom name_from;
blink::WebVector<blink::WebAXObject> name_objects;
accessibility_object_.GetName(name_from, name_objects);
ax::mojom::DescriptionFrom description_from;
blink::WebVector<blink::WebAXObject> description_objects;
return accessibility_object_
.Description(name_from, description_from, description_objects)
.Utf8();
}
std::string WebAXObjectProxy::DescriptionFrom() {
UpdateLayout();
ax::mojom::NameFrom name_from;
blink::WebVector<blink::WebAXObject> name_objects;
accessibility_object_.GetName(name_from, name_objects);
ax::mojom::DescriptionFrom description_from =
ax::mojom::DescriptionFrom::kNone;
blink::WebVector<blink::WebAXObject> description_objects;
accessibility_object_.Description(name_from, description_from,
description_objects);
switch (description_from) {
case ax::mojom::DescriptionFrom::kNone:
return "";
case ax::mojom::DescriptionFrom::kAriaDescription:
return "ariaDescription";
case ax::mojom::DescriptionFrom::kButtonLabel:
return "buttonLabel";
case ax::mojom::DescriptionFrom::kRelatedElement:
return "relatedElement";
case ax::mojom::DescriptionFrom::kRubyAnnotation:
return "rubyAnnotation";
case ax::mojom::DescriptionFrom::kSummary:
return "summary";
case ax::mojom::DescriptionFrom::kTableCaption:
return "tableCaption";
case ax::mojom::DescriptionFrom::kTitle:
return "title";
}
NOTREACHED();
return std::string();
}
std::string WebAXObjectProxy::Placeholder() {
UpdateLayout();
ax::mojom::NameFrom name_from;
blink::WebVector<blink::WebAXObject> name_objects;
accessibility_object_.GetName(name_from, name_objects);
return accessibility_object_.Placeholder(name_from).Utf8();
}
int WebAXObjectProxy::MisspellingsCount() {
UpdateLayout();
return GetMisspellings().size();
}
int WebAXObjectProxy::DescriptionElementCount() {
UpdateLayout();
ax::mojom::NameFrom name_from;
blink::WebVector<blink::WebAXObject> name_objects;
accessibility_object_.GetName(name_from, name_objects);
ax::mojom::DescriptionFrom description_from;
blink::WebVector<blink::WebAXObject> description_objects;
accessibility_object_.Description(name_from, description_from,
description_objects);
return static_cast<int>(description_objects.size());
}
v8::Local<v8::Object> WebAXObjectProxy::DescriptionElementAtIndex(
unsigned index) {
UpdateLayout();
ax::mojom::NameFrom name_from;
blink::WebVector<blink::WebAXObject> name_objects;
accessibility_object_.GetName(name_from, name_objects);
ax::mojom::DescriptionFrom description_from;
blink::WebVector<blink::WebAXObject> description_objects;
accessibility_object_.Description(name_from, description_from,
description_objects);
if (index >= description_objects.size())
return v8::Local<v8::Object>();
return factory_->GetOrCreate(description_objects[index]);
}
v8::Local<v8::Object> WebAXObjectProxy::OffsetContainer() {
UpdateLayout();
blink::WebAXObject container;
gfx::RectF bounds;
SkMatrix44 matrix;
accessibility_object_.GetRelativeBounds(container, bounds, matrix);
return factory_->GetOrCreate(container);
}
float WebAXObjectProxy::BoundsInContainerX() {
UpdateLayout();
blink::WebAXObject container;
gfx::RectF bounds;
SkMatrix44 matrix;
accessibility_object_.GetRelativeBounds(container, bounds, matrix);
return bounds.x();
}
float WebAXObjectProxy::BoundsInContainerY() {
UpdateLayout();
blink::WebAXObject container;
gfx::RectF bounds;
SkMatrix44 matrix;
accessibility_object_.GetRelativeBounds(container, bounds, matrix);
return bounds.y();
}
float WebAXObjectProxy::BoundsInContainerWidth() {
UpdateLayout();
blink::WebAXObject container;
gfx::RectF bounds;
SkMatrix44 matrix;
accessibility_object_.GetRelativeBounds(container, bounds, matrix);
return bounds.width();
}
float WebAXObjectProxy::BoundsInContainerHeight() {
UpdateLayout();
blink::WebAXObject container;
gfx::RectF bounds;
SkMatrix44 matrix;
accessibility_object_.GetRelativeBounds(container, bounds, matrix);
return bounds.height();
}
bool WebAXObjectProxy::HasNonIdentityTransform() {
UpdateLayout();
blink::WebAXObject container;
gfx::RectF bounds;
SkMatrix44 matrix;
accessibility_object_.GetRelativeBounds(container, bounds, matrix);
return !matrix.isIdentity();
}
RootWebAXObjectProxy::RootWebAXObjectProxy(const blink::WebAXObject& object,
Factory* factory)
: WebAXObjectProxy(object, factory) {}
v8::Local<v8::Object> RootWebAXObjectProxy::GetChildAtIndex(unsigned index) {
if (index)
return v8::Local<v8::Object>();
return factory()->GetOrCreate(accessibility_object());
}
bool RootWebAXObjectProxy::IsRoot() const {
return true;
}
WebAXObjectProxyList::WebAXObjectProxyList() = default;
WebAXObjectProxyList::~WebAXObjectProxyList() {
Clear();
}
void WebAXObjectProxyList::Clear() {
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::HandleScope handle_scope(isolate);
for (auto& persistent : elements_) {
auto local = v8::Local<v8::Object>::New(isolate, persistent);
WebAXObjectProxy* proxy = nullptr;
bool ok = gin::ConvertFromV8(isolate, local, &proxy);
DCHECK(ok);
// Because the v8::Persistent in this container uses
// CopyablePersistentObject traits, it will not leak the Persistent objects
// on destruction. However, blink may be keeping a reference to the |proxy|.
// We Reset() it to drop the callback in the proxy object now that its not
// in the proxy list.
proxy->Reset();
}
elements_.clear();
}
v8::Local<v8::Object> WebAXObjectProxyList::GetOrCreate(
const blink::WebAXObject& object) {
if (object.IsNull())
return v8::Local<v8::Object>();
v8::Isolate* isolate = blink::MainThreadIsolate();
for (const auto& persistent : elements_) {
auto local = v8::Local<v8::Object>::New(isolate, persistent);
WebAXObjectProxy* proxy = nullptr;
bool ok = gin::ConvertFromV8(isolate, local, &proxy);
DCHECK(ok);
if (proxy->IsEqualToObject(object))
return local;
}
v8::Local<v8::Value> value_handle =
gin::CreateHandle(isolate, new WebAXObjectProxy(object, this)).ToV8();
v8::Local<v8::Object> handle;
if (value_handle.IsEmpty() ||
!value_handle->ToObject(isolate->GetCurrentContext()).ToLocal(&handle)) {
return {};
}
elements_.emplace_back(isolate, handle);
return handle;
}
} // namespace content