blob: 8d0ffc5f10caee315a3137b54a25f56e39819d90 [file] [log] [blame]
/*
* Copyright (C) 2009 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/public/web/web_ax_object.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_element.h"
#include "third_party/blink/public/web/web_node.h"
#include "third_party/blink/public/web/web_view.h"
#include "third_party/blink/renderer/core/css/css_primitive_value_mappings.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/exported/web_view_impl.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/input/keyboard_event_manager.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
#include "third_party/blink/renderer/modules/accessibility/ax_position.h"
#include "third_party/blink/renderer/modules/accessibility/ax_range.h"
#include "third_party/blink/renderer/modules/accessibility/ax_selection.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/skia/include/core/SkMatrix44.h"
#include "ui/accessibility/ax_action_data.h"
namespace blink {
namespace {
mojom::blink::ScrollAlignment::Behavior ToBlinkScrollAlignmentBehavior(
ax::mojom::ScrollAlignment alignment) {
switch (alignment) {
case ax::mojom::ScrollAlignment::kNone:
return mojom::blink::ScrollAlignment::Behavior::kNoScroll;
case ax::mojom::ScrollAlignment::kScrollAlignmentCenter:
return mojom::blink::ScrollAlignment::Behavior::kCenter;
case ax::mojom::ScrollAlignment::kScrollAlignmentTop:
return mojom::blink::ScrollAlignment::Behavior::kTop;
case ax::mojom::ScrollAlignment::kScrollAlignmentBottom:
return mojom::blink::ScrollAlignment::Behavior::kBottom;
case ax::mojom::ScrollAlignment::kScrollAlignmentLeft:
return mojom::blink::ScrollAlignment::Behavior::kLeft;
case ax::mojom::ScrollAlignment::kScrollAlignmentRight:
return mojom::blink::ScrollAlignment::Behavior::kRight;
case ax::mojom::ScrollAlignment::kScrollAlignmentClosestEdge:
return mojom::blink::ScrollAlignment::Behavior::kClosestEdge;
}
NOTREACHED() << alignment;
}
} // namespace
// A utility class which uses the lifetime of this object to signify when
// AXObjCache or AXObjectCacheImpl handles programmatic actions.
class ScopedActionAnnotator {
public:
ScopedActionAnnotator(AXObject* obj,
ax::mojom::blink::Action event_from_action)
: cache_(&obj->AXObjectCache()) {
std::pair<ax::mojom::blink::EventFrom, ax::mojom::blink::Action>
event_from_data = cache_->active_event_from_data();
DCHECK_EQ(event_from_data.first, ax::mojom::blink::EventFrom::kNone)
<< "Multiple ScopedActionAnnotator instances cannot be nested.";
DCHECK_EQ(event_from_data.second, ax::mojom::blink::Action::kNone)
<< "event_from_action must not be set before construction.";
cache_->set_active_event_from_data(ax::mojom::blink::EventFrom::kAction,
event_from_action);
}
~ScopedActionAnnotator() {
cache_->set_active_event_from_data(ax::mojom::blink::EventFrom::kNone,
ax::mojom::blink::Action::kNone);
}
private:
Persistent<AXObjectCacheImpl> cache_;
};
#if DCHECK_IS_ON()
static void CheckLayoutClean(const Document* document) {
DCHECK(document);
LocalFrameView* view = document->View();
DCHECK(view);
DCHECK(!document->NeedsLayoutTreeUpdate());
LayoutView* lview = view->GetLayoutView();
DCHECK(!view->NeedsLayout())
<< "\n Layout pending: " << view->LayoutPending()
<< "\n Needs layout: " << (lview && lview->NeedsLayout());
DCHECK_GE(document->Lifecycle().GetState(), DocumentLifecycle::kLayoutClean)
<< "Document lifecycle must be at LayoutClean or later, was "
<< document->Lifecycle().GetState();
}
#endif
void WebAXObject::Reset() {
private_.Reset();
}
void WebAXObject::Assign(const WebAXObject& other) {
private_ = other.private_;
}
bool WebAXObject::Equals(const WebAXObject& n) const {
return private_.Get() == n.private_.Get();
}
bool WebAXObject::IsDetached() const {
if (private_.IsNull())
return true;
return private_->IsDetached();
}
int WebAXObject::AxID() const {
if (IsDetached())
return -1;
return private_->AXObjectID();
}
int WebAXObject::GenerateAXID() const {
if (IsDetached())
return -1;
return private_->AXObjectCache().GenerateAXID();
}
// This method must be called before serializing any accessibility nodes, in
// order to ensure that layout calls are not made at an unsafe time in the
// document lifecycle.
bool WebAXObject::MaybeUpdateLayoutAndCheckValidity() {
if (!IsDetached()) {
if (!MaybeUpdateLayoutAndCheckValidity(GetDocument()))
return false;
}
// Doing a layout can cause this object to be invalid, so check again.
return CheckValidity();
}
// Returns true if the object is valid and can be accessed.
bool WebAXObject::CheckValidity() {
if (IsDetached())
return false;
#if DCHECK_IS_ON()
Node* node = private_->GetNode();
if (!node)
return true;
// Has up-to-date layout info or is display-locked (content-visibility), which
// is handled as a special case inside of accessibility code.
Document* document = private_->GetDocument();
DCHECK(!document->NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(*node) ||
DisplayLockUtilities::NearestLockedExclusiveAncestor(*node))
<< "Node needs layout update and is not display locked";
#endif // DCHECK_IS_ON()
return true;
}
ax::mojom::DefaultActionVerb WebAXObject::Action() const {
if (IsDetached())
return ax::mojom::DefaultActionVerb::kNone;
return private_->Action();
}
bool WebAXObject::CanPress() const {
if (IsDetached())
return false;
return private_->ActionElement() || private_->IsButton() ||
private_->IsMenuRelated();
}
bool WebAXObject::CanSetValueAttribute() const {
if (IsDetached())
return false;
return private_->CanSetValueAttribute();
}
unsigned WebAXObject::ChildCount() const {
if (IsDetached())
return 0;
return private_->ChildCountIncludingIgnored();
}
WebAXObject WebAXObject::ChildAt(unsigned index) const {
if (IsDetached())
return WebAXObject();
return WebAXObject(private_->ChildAtIncludingIgnored(int{index}));
}
WebAXObject WebAXObject::ParentObject() const {
if (IsDetached())
return WebAXObject();
return WebAXObject(private_->ParentObjectIncludedInTree());
}
void WebAXObject::Serialize(ui::AXNodeData* node_data,
ui::AXMode accessibility_mode) const {
if (IsDetached())
return;
private_->Serialize(node_data, accessibility_mode);
}
bool WebAXObject::IsAnchor() const {
if (IsDetached())
return false;
return private_->IsAnchor();
}
WebString WebAXObject::AutoComplete() const {
if (IsDetached())
return WebString();
return private_->AutoComplete();
}
ax::mojom::AriaCurrentState WebAXObject::AriaCurrentState() const {
if (IsDetached())
return ax::mojom::AriaCurrentState::kNone;
return private_->GetAriaCurrentState();
}
ax::mojom::CheckedState WebAXObject::CheckedState() const {
if (IsDetached())
return ax::mojom::CheckedState::kNone;
return private_->CheckedState();
}
bool WebAXObject::IsClickable() const {
if (IsDetached())
return false;
return private_->IsClickable();
}
bool WebAXObject::IsControl() const {
if (IsDetached())
return false;
return private_->IsControl();
}
bool WebAXObject::IsFocused() const {
if (IsDetached())
return false;
return private_->IsFocused();
}
bool WebAXObject::IsLineBreakingObject() const {
if (IsDetached())
return false;
return private_->IsLineBreakingObject();
}
bool WebAXObject::IsLinked() const {
if (IsDetached())
return false;
return private_->IsLinked();
}
bool WebAXObject::IsModal() const {
if (IsDetached())
return false;
return private_->IsModal();
}
bool WebAXObject::IsNativeTextControl() const {
if (IsDetached())
return false;
return private_->IsNativeTextControl();
}
bool WebAXObject::IsOffScreen() const {
if (IsDetached())
return false;
return private_->IsOffScreen();
}
bool WebAXObject::IsSelectedOptionActive() const {
if (IsDetached())
return false;
return private_->IsSelectedOptionActive();
}
bool WebAXObject::IsVisited() const {
if (IsDetached())
return false;
return private_->IsVisited();
}
bool WebAXObject::HasAriaAttribute() const {
if (IsDetached())
return false;
return private_->HasAriaAttribute();
}
WebString WebAXObject::AccessKey() const {
if (IsDetached())
return WebString();
return WebString(private_->AccessKey());
}
// Deprecated.
void WebAXObject::ColorValue(int& r, int& g, int& b) const {
if (IsDetached())
return;
unsigned color = private_->ColorValue();
r = (color >> 16) & 0xFF;
g = (color >> 8) & 0xFF;
b = color & 0xFF;
}
unsigned WebAXObject::ColorValue() const {
if (IsDetached())
return 0;
// RGBA32 is an alias for unsigned int.
return private_->ColorValue();
}
WebAXObject WebAXObject::AriaActiveDescendant() const {
if (IsDetached())
return WebAXObject();
return WebAXObject(private_->ActiveDescendant());
}
WebAXObject WebAXObject::ErrorMessage() const {
if (IsDetached())
return WebAXObject();
return WebAXObject(private_->ErrorMessage());
}
bool WebAXObject::IsEditable() const {
if (IsDetached())
return false;
return private_->IsEditable();
}
bool WebAXObject::IsInLiveRegion() const {
if (IsDetached())
return false;
return !!private_->LiveRegionRoot();
}
bool WebAXObject::LiveRegionAtomic() const {
if (IsDetached())
return false;
return private_->LiveRegionAtomic();
}
WebString WebAXObject::LiveRegionRelevant() const {
if (IsDetached())
return WebString();
return private_->LiveRegionRelevant();
}
WebString WebAXObject::LiveRegionStatus() const {
if (IsDetached())
return WebString();
return private_->LiveRegionStatus();
}
WebAXObject WebAXObject::LiveRegionRoot() const {
if (IsDetached())
return WebAXObject();
AXObject* live_region_root = private_->LiveRegionRoot();
if (live_region_root)
return WebAXObject(live_region_root);
return WebAXObject();
}
bool WebAXObject::ContainerLiveRegionAtomic() const {
if (IsDetached())
return false;
return private_->ContainerLiveRegionAtomic();
}
bool WebAXObject::ContainerLiveRegionBusy() const {
if (IsDetached())
return false;
return private_->ContainerLiveRegionBusy();
}
WebString WebAXObject::ContainerLiveRegionRelevant() const {
if (IsDetached())
return WebString();
return private_->ContainerLiveRegionRelevant();
}
WebString WebAXObject::ContainerLiveRegionStatus() const {
if (IsDetached())
return WebString();
return private_->ContainerLiveRegionStatus();
}
bool WebAXObject::AriaOwns(WebVector<WebAXObject>& owns_elements) const {
// aria-owns rearranges the accessibility tree rather than just
// exposing an attribute.
// FIXME(dmazzoni): remove this function after we stop calling it
// from Chromium. http://crbug.com/489590
return false;
}
bool WebAXObject::CanvasHasFallbackContent() const {
if (IsDetached())
return false;
return private_->CanvasHasFallbackContent();
}
WebString WebAXObject::ImageDataUrl(const gfx::Size& max_size) const {
if (IsDetached())
return WebString();
return private_->ImageDataUrl(IntSize(max_size));
}
ax::mojom::InvalidState WebAXObject::InvalidState() const {
if (IsDetached())
return ax::mojom::InvalidState::kNone;
return private_->GetInvalidState();
}
// Only used when invalidState() returns WebAXInvalidStateOther.
WebString WebAXObject::AriaInvalidValue() const {
if (IsDetached())
return WebString();
return private_->AriaInvalidValue();
}
int WebAXObject::HeadingLevel() const {
if (IsDetached())
return 0;
return private_->HeadingLevel();
}
int WebAXObject::HierarchicalLevel() const {
if (IsDetached())
return 0;
return private_->HierarchicalLevel();
}
// FIXME: This method passes in a point that has page scale applied but assumes
// that (0, 0) is the top left of the visual viewport. In other words, the
// point has the VisualViewport scale applied, but not the VisualViewport
// offset. crbug.com/459591.
WebAXObject WebAXObject::HitTest(const gfx::Point& point) const {
if (IsDetached())
return WebAXObject();
ScopedActionAnnotator annotater(private_.Get(),
ax::mojom::blink::Action::kHitTest);
IntPoint contents_point =
private_->DocumentFrameView()->SoonToBeRemovedUnscaledViewportToContents(
IntPoint(point));
Document* document = private_->GetDocument();
if (!document || !document->View())
return WebAXObject();
if (!document->View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kAccessibility)) {
return WebAXObject();
}
AXObject* hit = private_->AccessibilityHitTest(contents_point);
if (hit)
return WebAXObject(hit);
if (private_->GetBoundsInFrameCoordinates().Contains(contents_point))
return *this;
return WebAXObject();
}
gfx::Rect WebAXObject::GetBoundsInFrameCoordinates() const {
LayoutRect rect = private_->GetBoundsInFrameCoordinates();
return EnclosingIntRect(rect);
}
WebString WebAXObject::KeyboardShortcut() const {
if (IsDetached())
return WebString();
String access_key = private_->AccessKey();
if (access_key.IsNull())
return WebString();
DEFINE_STATIC_LOCAL(String, modifier_string, ());
if (modifier_string.IsNull()) {
unsigned modifiers = KeyboardEventManager::kAccessKeyModifiers;
// Follow the same order as Mozilla MSAA implementation:
// Ctrl+Alt+Shift+Meta+key. MSDN states that keyboard shortcut strings
// should not be localized and defines the separator as "+".
StringBuilder modifier_string_builder;
if (modifiers & WebInputEvent::kControlKey)
modifier_string_builder.Append("Ctrl+");
if (modifiers & WebInputEvent::kAltKey)
modifier_string_builder.Append("Alt+");
if (modifiers & WebInputEvent::kShiftKey)
modifier_string_builder.Append("Shift+");
if (modifiers & WebInputEvent::kMetaKey)
modifier_string_builder.Append("Win+");
modifier_string = modifier_string_builder.ToString();
}
return String(modifier_string + access_key);
}
WebString WebAXObject::Language() const {
if (IsDetached())
return WebString();
return private_->Language();
}
bool WebAXObject::PerformAction(const ui::AXActionData& action_data) const {
if (IsDetached())
return false;
ScopedActionAnnotator annotater(private_.Get(), action_data.action);
return private_->PerformAction(action_data);
}
WebAXObject WebAXObject::InPageLinkTarget() const {
if (IsDetached())
return WebAXObject();
AXObject* target = private_->InPageLinkTarget();
if (!target)
return WebAXObject();
return WebAXObject(target);
}
WebVector<WebAXObject> WebAXObject::RadioButtonsInGroup() const {
if (IsDetached())
return WebVector<WebAXObject>();
AXObject::AXObjectVector radio_buttons = private_->RadioButtonsInGroup();
WebVector<WebAXObject> web_radio_buttons(radio_buttons.size());
std::copy(radio_buttons.begin(), radio_buttons.end(),
web_radio_buttons.begin());
return web_radio_buttons;
}
ax::mojom::Role WebAXObject::Role() const {
if (IsDetached())
return ax::mojom::Role::kUnknown;
return private_->RoleValue();
}
static ax::mojom::TextAffinity ToAXAffinity(TextAffinity affinity) {
switch (affinity) {
case TextAffinity::kUpstream:
return ax::mojom::TextAffinity::kUpstream;
case TextAffinity::kDownstream:
return ax::mojom::TextAffinity::kDownstream;
default:
NOTREACHED();
return ax::mojom::TextAffinity::kDownstream;
}
}
bool WebAXObject::IsLoaded() const {
if (IsDetached())
return false;
return private_->IsLoaded();
}
double WebAXObject::EstimatedLoadingProgress() const {
if (IsDetached())
return 0.0;
return private_->EstimatedLoadingProgress();
}
WebAXObject WebAXObject::RootScroller() const {
if (IsDetached())
return WebAXObject();
return WebAXObject(private_->RootScroller());
}
void WebAXObject::Selection(bool& is_selection_backward,
WebAXObject& anchor_object,
int& anchor_offset,
ax::mojom::TextAffinity& anchor_affinity,
WebAXObject& focus_object,
int& focus_offset,
ax::mojom::TextAffinity& focus_affinity) const {
is_selection_backward = false;
anchor_object = WebAXObject();
anchor_offset = -1;
anchor_affinity = ax::mojom::TextAffinity::kDownstream;
focus_object = WebAXObject();
focus_offset = -1;
focus_affinity = ax::mojom::TextAffinity::kDownstream;
if (IsDetached() || GetDocument().IsNull())
return;
WebAXObject focus = FromWebDocumentFocused(GetDocument(), false);
if (focus.IsDetached())
return;
const auto ax_selection =
focus.private_->IsNativeTextControl()
? AXSelection::FromCurrentSelection(
ToTextControl(*focus.private_->GetNode()))
: AXSelection::FromCurrentSelection(*focus.private_->GetDocument());
if (!ax_selection)
return;
const AXPosition base = ax_selection.Base();
anchor_object = WebAXObject(const_cast<AXObject*>(base.ContainerObject()));
const AXPosition extent = ax_selection.Extent();
focus_object = WebAXObject(const_cast<AXObject*>(extent.ContainerObject()));
is_selection_backward = base > extent;
if (base.IsTextPosition()) {
anchor_offset = base.TextOffset();
anchor_affinity = ToAXAffinity(base.Affinity());
} else {
anchor_offset = base.ChildIndex();
}
if (extent.IsTextPosition()) {
focus_offset = extent.TextOffset();
focus_affinity = ToAXAffinity(extent.Affinity());
} else {
focus_offset = extent.ChildIndex();
}
}
bool WebAXObject::SetSelected(bool selected) const {
if (IsDetached())
return false;
ScopedActionAnnotator annotater(private_.Get(),
ax::mojom::blink::Action::kSetSelection);
return private_->RequestSetSelectedAction(selected);
}
bool WebAXObject::SetSelection(const WebAXObject& anchor_object,
int anchor_offset,
const WebAXObject& focus_object,
int focus_offset) const {
if (IsDetached() || anchor_object.IsDetached() || focus_object.IsDetached())
return false;
ScopedActionAnnotator annotater(private_.Get(),
ax::mojom::blink::Action::kSetSelection);
AXPosition ax_base, ax_extent;
if (static_cast<const AXObject*>(anchor_object)->IsTextObject() ||
static_cast<const AXObject*>(anchor_object)->IsNativeTextControl()) {
ax_base =
AXPosition::CreatePositionInTextObject(*anchor_object, anchor_offset);
} else if (anchor_offset <= 0) {
ax_base = AXPosition::CreateFirstPositionInObject(*anchor_object);
} else if (anchor_offset >= static_cast<int>(anchor_object.ChildCount())) {
ax_base = AXPosition::CreateLastPositionInObject(*anchor_object);
} else {
DCHECK_GE(anchor_offset, 0);
ax_base = AXPosition::CreatePositionBeforeObject(
*anchor_object.ChildAt(static_cast<unsigned int>(anchor_offset)));
}
if (static_cast<const AXObject*>(focus_object)->IsTextObject() ||
static_cast<const AXObject*>(focus_object)->IsNativeTextControl()) {
ax_extent =
AXPosition::CreatePositionInTextObject(*focus_object, focus_offset);
} else if (focus_offset <= 0) {
ax_extent = AXPosition::CreateFirstPositionInObject(*focus_object);
} else if (focus_offset >= static_cast<int>(focus_object.ChildCount())) {
ax_extent = AXPosition::CreateLastPositionInObject(*focus_object);
} else {
DCHECK_GE(focus_offset, 0);
ax_extent = AXPosition::CreatePositionBeforeObject(
*focus_object.ChildAt(static_cast<unsigned int>(focus_offset)));
}
AXSelection::Builder builder;
AXSelection ax_selection =
builder.SetBase(ax_base).SetExtent(ax_extent).Build();
return ax_selection.Select();
}
WebString WebAXObject::StringValue() const {
if (IsDetached())
return WebString();
return private_->StringValue();
}
ax::mojom::blink::WritingDirection WebAXObject::GetTextDirection() const {
if (IsDetached())
return ax::mojom::blink::WritingDirection::kLtr;
return private_->GetTextDirection();
}
WebURL WebAXObject::Url() const {
if (IsDetached())
return WebURL();
return private_->Url();
}
WebString WebAXObject::GetName(ax::mojom::NameFrom& out_name_from,
WebVector<WebAXObject>& out_name_objects) const {
out_name_from = ax::mojom::blink::NameFrom::kUninitialized;
if (IsDetached())
return WebString();
HeapVector<Member<AXObject>> name_objects;
WebString result = private_->GetName(out_name_from, &name_objects);
out_name_objects.reserve(name_objects.size());
out_name_objects.resize(name_objects.size());
std::copy(name_objects.begin(), name_objects.end(), out_name_objects.begin());
return result;
}
WebString WebAXObject::GetName() const {
if (IsDetached())
return WebString();
ax::mojom::NameFrom name_from;
HeapVector<Member<AXObject>> name_objects;
return private_->GetName(name_from, &name_objects);
}
WebString WebAXObject::Description(
ax::mojom::NameFrom name_from,
ax::mojom::DescriptionFrom& out_description_from,
WebVector<WebAXObject>& out_description_objects) const {
out_description_from = ax::mojom::blink::DescriptionFrom::kUninitialized;
if (IsDetached())
return WebString();
HeapVector<Member<AXObject>> description_objects;
String result = private_->Description(name_from, out_description_from,
&description_objects);
out_description_objects.reserve(description_objects.size());
out_description_objects.resize(description_objects.size());
std::copy(description_objects.begin(), description_objects.end(),
out_description_objects.begin());
return result;
}
WebString WebAXObject::Placeholder(ax::mojom::NameFrom name_from) const {
if (IsDetached())
return WebString();
return private_->Placeholder(name_from);
}
WebString WebAXObject::Title(ax::mojom::NameFrom name_from) const {
if (IsDetached())
return WebString();
return private_->Title(name_from);
}
bool WebAXObject::SupportsRangeValue() const {
if (IsDetached())
return false;
return private_->IsRangeValueSupported();
}
bool WebAXObject::ValueForRange(float* out_value) const {
if (IsDetached())
return false;
return private_->ValueForRange(out_value);
}
bool WebAXObject::MaxValueForRange(float* out_value) const {
if (IsDetached())
return false;
return private_->MaxValueForRange(out_value);
}
bool WebAXObject::MinValueForRange(float* out_value) const {
if (IsDetached())
return false;
return private_->MinValueForRange(out_value);
}
bool WebAXObject::StepValueForRange(float* out_value) const {
if (IsDetached())
return false;
return private_->StepValueForRange(out_value);
}
WebNode WebAXObject::GetNode() const {
if (IsDetached())
return WebNode();
Node* node = private_->GetNode();
if (!node)
return WebNode();
return WebNode(node);
}
WebDocument WebAXObject::GetDocument() const {
if (IsDetached())
return WebDocument();
Document* document = private_->GetDocument();
if (!document)
return WebDocument();
return WebDocument(document);
}
WebString WebAXObject::ComputedStyleDisplay() const {
if (IsDetached())
return WebString();
#if DCHECK_IS_ON()
CheckLayoutClean(private_->GetDocument());
#endif
Node* node = private_->GetNode();
if (!node || node->IsDocumentNode())
return WebString();
const ComputedStyle* computed_style = node->GetComputedStyle();
if (!computed_style)
return WebString();
return WebString(CSSProperty::Get(CSSPropertyID::kDisplay)
.CSSValueFromComputedStyle(
*computed_style, /* layout_object */ nullptr,
/* allow_visited_style */ false)
->CssText());
}
bool WebAXObject::AccessibilityIsIgnored() const {
if (IsDetached())
return false;
return private_->AccessibilityIsIgnored();
}
bool WebAXObject::AccessibilityIsIncludedInTree() const {
if (IsDetached())
return false;
return private_->AccessibilityIsIncludedInTree();
}
unsigned WebAXObject::ColumnCount() const {
if (IsDetached())
return false;
return private_->IsTableLikeRole() ? private_->ColumnCount() : 0;
}
unsigned WebAXObject::RowCount() const {
if (IsDetached())
return 0;
if (!private_->IsTableLikeRole())
return 0;
return private_->RowCount();
}
WebAXObject WebAXObject::CellForColumnAndRow(unsigned column,
unsigned row) const {
if (IsDetached())
return WebAXObject();
if (!private_->IsTableLikeRole())
return WebAXObject();
return WebAXObject(private_->CellForColumnAndRow(column, row));
}
unsigned WebAXObject::RowIndex() const {
if (IsDetached())
return 0;
return private_->IsTableRowLikeRole() ? private_->RowIndex() : 0;
}
WebAXObject WebAXObject::RowHeader() const {
if (IsDetached())
return WebAXObject();
if (!private_->IsTableRowLikeRole())
return WebAXObject();
return WebAXObject(private_->HeaderObject());
}
void WebAXObject::RowHeaders(
WebVector<WebAXObject>& row_header_elements) const {
if (IsDetached())
return;
if (!private_->IsTableLikeRole())
return;
AXObject::AXObjectVector headers;
private_->RowHeaders(headers);
row_header_elements.reserve(headers.size());
row_header_elements.resize(headers.size());
std::copy(headers.begin(), headers.end(), row_header_elements.begin());
}
unsigned WebAXObject::ColumnIndex() const {
if (IsDetached())
return 0;
if (private_->RoleValue() != ax::mojom::Role::kColumn)
return 0;
return private_->ColumnIndex();
}
WebAXObject WebAXObject::ColumnHeader() const {
if (IsDetached())
return WebAXObject();
if (private_->RoleValue() != ax::mojom::Role::kColumn)
return WebAXObject();
return WebAXObject(private_->HeaderObject());
}
void WebAXObject::ColumnHeaders(
WebVector<WebAXObject>& column_header_elements) const {
if (IsDetached())
return;
if (!private_->IsTableLikeRole())
return;
AXObject::AXObjectVector headers;
private_->ColumnHeaders(headers);
column_header_elements.reserve(headers.size());
column_header_elements.resize(headers.size());
std::copy(headers.begin(), headers.end(), column_header_elements.begin());
}
unsigned WebAXObject::CellColumnIndex() const {
if (IsDetached())
return 0;
return private_->IsTableCellLikeRole() ? private_->ColumnIndex() : 0;
}
unsigned WebAXObject::CellColumnSpan() const {
if (IsDetached())
return 0;
return private_->IsTableCellLikeRole() ? private_->ColumnSpan() : 0;
}
unsigned WebAXObject::CellRowIndex() const {
if (IsDetached())
return 0;
return private_->IsTableCellLikeRole() ? private_->RowIndex() : 0;
}
unsigned WebAXObject::CellRowSpan() const {
if (IsDetached())
return 0;
return private_->IsTableCellLikeRole() ? private_->RowSpan() : 0;
}
ax::mojom::SortDirection WebAXObject::SortDirection() const {
if (IsDetached())
return ax::mojom::SortDirection::kNone;
return private_->GetSortDirection();
}
void WebAXObject::LoadInlineTextBoxes() const {
if (IsDetached())
return;
private_->LoadInlineTextBoxes();
}
WebAXObject WebAXObject::NextOnLine() const {
if (IsDetached())
return WebAXObject();
return WebAXObject(private_.Get()->NextOnLine());
}
WebAXObject WebAXObject::PreviousOnLine() const {
if (IsDetached())
return WebAXObject();
return WebAXObject(private_.Get()->PreviousOnLine());
}
void WebAXObject::CharacterOffsets(WebVector<int>& offsets) const {
if (IsDetached())
return;
Vector<int> offsets_vector;
private_->TextCharacterOffsets(offsets_vector);
offsets = offsets_vector;
}
void WebAXObject::GetWordBoundaries(WebVector<int>& starts,
WebVector<int>& ends) const {
if (IsDetached())
return;
Vector<int> src_starts;
Vector<int> src_ends;
private_->GetWordBoundaries(src_starts, src_ends);
DCHECK_EQ(src_starts.size(), src_ends.size());
WebVector<int> word_start_offsets(src_starts.size());
WebVector<int> word_end_offsets(src_ends.size());
for (wtf_size_t i = 0; i < src_starts.size(); ++i) {
word_start_offsets[i] = src_starts[i];
word_end_offsets[i] = src_ends[i];
}
starts.Swap(word_start_offsets);
ends.Swap(word_end_offsets);
}
bool WebAXObject::IsScrollableContainer() const {
if (IsDetached())
return false;
return private_->IsScrollableContainer();
}
gfx::Point WebAXObject::GetScrollOffset() const {
if (IsDetached())
return gfx::Point();
return private_->GetScrollOffset();
}
gfx::Point WebAXObject::MinimumScrollOffset() const {
if (IsDetached())
return gfx::Point();
return private_->MinimumScrollOffset();
}
gfx::Point WebAXObject::MaximumScrollOffset() const {
if (IsDetached())
return gfx::Point();
return private_->MaximumScrollOffset();
}
void WebAXObject::SetScrollOffset(const gfx::Point& offset) const {
if (IsDetached())
return;
private_->SetScrollOffset(IntPoint(offset));
}
void WebAXObject::Dropeffects(
WebVector<ax::mojom::Dropeffect>& dropeffects) const {
if (IsDetached())
return;
Vector<ax::mojom::Dropeffect> enum_dropeffects;
private_->Dropeffects(enum_dropeffects);
WebVector<ax::mojom::Dropeffect> web_dropeffects(enum_dropeffects.size());
for (wtf_size_t i = 0; i < enum_dropeffects.size(); ++i) {
web_dropeffects[i] = enum_dropeffects[i];
}
dropeffects.Swap(web_dropeffects);
}
void WebAXObject::GetRelativeBounds(WebAXObject& offset_container,
gfx::RectF& bounds_in_container,
SkMatrix44& container_transform,
bool* clips_children) const {
if (IsDetached())
return;
#if DCHECK_IS_ON()
CheckLayoutClean(private_->GetDocument());
#endif
AXObject* container = nullptr;
FloatRect bounds;
private_->GetRelativeBounds(&container, bounds, container_transform,
clips_children);
offset_container = WebAXObject(container);
bounds_in_container = gfx::RectF(bounds);
}
void WebAXObject::GetAllObjectsWithChangedBounds(
WebVector<WebAXObject>& out_changed_bounds_objects) const {
if (IsDetached())
return;
HeapVector<Member<AXObject>> changed_bounds_objects =
private_->AXObjectCache().GetAllObjectsWithChangedBounds();
out_changed_bounds_objects.reserve(changed_bounds_objects.size());
out_changed_bounds_objects.resize(changed_bounds_objects.size());
std::copy(changed_bounds_objects.begin(), changed_bounds_objects.end(),
out_changed_bounds_objects.begin());
}
bool WebAXObject::ScrollToMakeVisible() const {
if (IsDetached())
return false;
ScopedActionAnnotator annotater(
private_.Get(), ax::mojom::blink::Action::kScrollToMakeVisible);
return private_->RequestScrollToMakeVisibleAction();
}
bool WebAXObject::ScrollToMakeVisibleWithSubFocus(
const gfx::Rect& subfocus,
ax::mojom::ScrollAlignment horizontal_scroll_alignment,
ax::mojom::ScrollAlignment vertical_scroll_alignment,
ax::mojom::ScrollBehavior scroll_behavior) const {
if (IsDetached())
return false;
ScopedActionAnnotator annotater(
private_.Get(), ax::mojom::blink::Action::kScrollToMakeVisible);
auto horizontal_behavior =
ToBlinkScrollAlignmentBehavior(horizontal_scroll_alignment);
auto vertical_behavior =
ToBlinkScrollAlignmentBehavior(vertical_scroll_alignment);
mojom::blink::ScrollAlignment::Behavior visible_horizontal_behavior =
scroll_behavior == ax::mojom::ScrollBehavior::kScrollIfVisible
? horizontal_behavior
: mojom::blink::ScrollAlignment::Behavior::kNoScroll;
mojom::blink::ScrollAlignment::Behavior visible_vertical_behavior =
scroll_behavior == ax::mojom::ScrollBehavior::kScrollIfVisible
? vertical_behavior
: mojom::blink::ScrollAlignment::Behavior::kNoScroll;
blink::mojom::blink::ScrollAlignment blink_horizontal_scroll_alignment = {
visible_horizontal_behavior, horizontal_behavior, horizontal_behavior};
blink::mojom::blink::ScrollAlignment blink_vertical_scroll_alignment = {
visible_vertical_behavior, vertical_behavior, vertical_behavior};
return private_->RequestScrollToMakeVisibleWithSubFocusAction(
IntRect(subfocus), blink_horizontal_scroll_alignment,
blink_vertical_scroll_alignment);
}
void WebAXObject::Swap(WebAXObject& other) {
if (IsDetached() || other.IsDetached())
return;
AXObject* temp = private_.Get();
DCHECK(temp) << "|private_| should not be null.";
this->Assign(other);
other = temp;
}
void WebAXObject::HandleAutofillStateChanged(
const blink::WebAXAutofillState state) const {
if (IsDetached() || !private_->IsAXLayoutObject())
return;
private_->HandleAutofillStateChanged(state);
}
WebString WebAXObject::ToString(bool verbose) const {
return private_->ToString(verbose);
}
WebAXObject::WebAXObject(AXObject* object) : private_(object) {}
WebAXObject& WebAXObject::operator=(AXObject* object) {
private_ = object;
return *this;
}
bool WebAXObject::operator==(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ == *other.private_;
}
bool WebAXObject::operator!=(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ != *other.private_;
}
bool WebAXObject::operator<(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ < *other.private_;
}
bool WebAXObject::operator<=(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ <= *other.private_;
}
bool WebAXObject::operator>(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ > *other.private_;
}
bool WebAXObject::operator>=(const WebAXObject& other) const {
if (IsDetached() || other.IsDetached())
return false;
return *private_ >= *other.private_;
}
WebAXObject::operator AXObject*() const {
return private_.Get();
}
// static
WebAXObject WebAXObject::FromWebNode(const WebNode& web_node) {
WebDocument web_document = web_node.GetDocument();
const Document* doc = web_document.ConstUnwrap<Document>();
auto* cache = To<AXObjectCacheImpl>(doc->ExistingAXObjectCache());
const Node* node = web_node.ConstUnwrap<Node>();
return cache ? WebAXObject(cache->Get(node)) : WebAXObject();
}
// static
WebAXObject WebAXObject::FromWebDocument(const WebDocument& web_document) {
if (!MaybeUpdateLayoutAndCheckValidity(web_document))
return WebAXObject();
const Document* document = web_document.ConstUnwrap<Document>();
auto* cache = To<AXObjectCacheImpl>(document->ExistingAXObjectCache());
return cache ? WebAXObject(cache->GetOrCreate(document->GetLayoutView()))
: WebAXObject();
}
// static
WebAXObject WebAXObject::FromWebDocumentByID(const WebDocument& web_document,
int ax_id) {
const Document* document = web_document.ConstUnwrap<Document>();
auto* cache = To<AXObjectCacheImpl>(document->ExistingAXObjectCache());
return cache ? WebAXObject(cache->ObjectFromAXID(ax_id)) : WebAXObject();
}
// static
WebAXObject WebAXObject::FromWebDocumentFocused(
const WebDocument& web_document,
bool update_layout_if_necessary) {
if (update_layout_if_necessary &&
!MaybeUpdateLayoutAndCheckValidity(web_document)) {
return WebAXObject();
}
const Document* document = web_document.ConstUnwrap<Document>();
auto* cache = To<AXObjectCacheImpl>(document->ExistingAXObjectCache());
return cache ? WebAXObject(cache->FocusedObject()) : WebAXObject();
}
// static
void WebAXObject::UpdateLayout(const WebDocument& web_document) {
const Document* document = web_document.ConstUnwrap<Document>();
if (!document || !document->View() || !document->ExistingAXObjectCache())
return;
if (document->NeedsLayoutTreeUpdate() || document->View()->NeedsLayout() ||
document->Lifecycle().GetState() <
DocumentLifecycle::kCompositingAssignmentsClean ||
document->ExistingAXObjectCache()->IsDirty()) {
document->View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kAccessibility);
}
}
// static
bool WebAXObject::MaybeUpdateLayoutAndCheckValidity(
const WebDocument& web_document) {
const Document* document = web_document.ConstUnwrap<Document>();
if (!document || !document->View())
return false;
if (document->NeedsLayoutTreeUpdate() || document->View()->NeedsLayout() ||
document->Lifecycle().GetState() < DocumentLifecycle::kPrePaintClean) {
// Note: this always alters the lifecycle, because
// RunAccessibilityLifecyclePhase() will be called.
if (!document->View()->UpdateAllLifecyclePhasesExceptPaint(
DocumentUpdateReason::kAccessibility)) {
return false;
}
} else {
#if DCHECK_IS_ON()
CheckLayoutClean(document);
#endif
}
return true;
}
// static
void WebAXObject::Freeze(const WebDocument& web_document) {
const Document* doc = web_document.ConstUnwrap<Document>();
auto* cache = To<AXObjectCacheImpl>(doc->ExistingAXObjectCache());
if (cache)
cache->Freeze();
}
// static
void WebAXObject::Thaw(const WebDocument& web_document) {
const Document* doc = web_document.ConstUnwrap<Document>();
auto* cache = To<AXObjectCacheImpl>(doc->ExistingAXObjectCache());
if (cache)
cache->Thaw();
}
} // namespace blink