// Copyright 2016 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/accessibility/ax_node_position.h" #include "build/build_config.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/base/buildflags.h" namespace ui { // On some platforms, most objects are represented in the text of their parents // with a special "embedded object character" and not with their actual text // contents. Also on the same platforms, if a node has only ignored descendants, // i.e., it appears to be empty to assistive software, we need to treat it as a // character and a word boundary. AXEmbeddedObjectBehavior g_ax_embedded_object_behavior = #if BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK) AXEmbeddedObjectBehavior::kExposeCharacter; #else AXEmbeddedObjectBehavior::kSuppressCharacter; #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK) ScopedAXEmbeddedObjectBehaviorSetter::ScopedAXEmbeddedObjectBehaviorSetter( AXEmbeddedObjectBehavior behavior) { prev_behavior_ = g_ax_embedded_object_behavior; g_ax_embedded_object_behavior = behavior; } ScopedAXEmbeddedObjectBehaviorSetter::~ScopedAXEmbeddedObjectBehaviorSetter() { g_ax_embedded_object_behavior = prev_behavior_; } std::string ToString(const AXPositionKind kind) { static constexpr auto kKindToString = base::MakeFixedFlatMap( {{AXPositionKind::NULL_POSITION, "NullPosition"}, {AXPositionKind::TREE_POSITION, "TreePosition"}, {AXPositionKind::TEXT_POSITION, "TextPosition"}}); const auto iter = kKindToString.find(kind); if (iter == std::end(kKindToString)) return std::string(); return iter->second; } // static AXNodePosition::AXPositionInstance AXNodePosition::CreatePosition( const AXNode& node, int child_index_or_text_offset, ax::mojom::TextAffinity affinity) { if (!node.tree()) return CreateNullPosition(); AXTreeID tree_id = node.tree()->GetAXTreeID(); if (IsTextPositionAnchor(node)) { // TODO(accessibility) It is a mistake for the to caller try to create a // text position with BEFORE_TEXT as the text offset. Correct the callers // that are doing this. // DCHECK_NE(child_index_or_text_offset, BEFORE_TEXT) // << "Creating a text position with BEFORE_TEXT as the offset is illegal " // "and disallowed."; int text_offset = child_index_or_text_offset == BEFORE_TEXT ? 0 : child_index_or_text_offset; return CreateTextPosition(tree_id, node.id(), text_offset, affinity); } DCHECK_LE(child_index_or_text_offset, static_cast(node.GetChildCountCrossingTreeBoundary())) << "\n* Trying to create a tree position with a child index that is too " "large. Maybe a text position should have been created instead?\n" << "\n* Anchor node: " << node << "\n* IsLeaf(): " << node.IsLeaf() << "\n* Child offset: " << child_index_or_text_offset << "\n* IsLeafNodeForTreePosition(): " << IsLeafNodeForTreePosition(node) << "\n* Tree: " << node.tree()->ToString(); return CreateTreePosition(*node.tree(), node, child_index_or_text_offset); } // static bool AXNodePosition::IsTextPositionAnchor(const AXNode& node) { // TODO(accessibility) Simplify. Not actually sure if this is the correct // thing for the case where IsLeaf() == false but IsLeafNodeForTreePosition() // is true. if (node.IsLeaf()) return true; // TODO(accessibility) Try to remove this condition. Text positions for a // selection operation should only be created inside selectable text. // A list marker for example is not selectable text: it would either be // selected as a whole or not selected, and you can't select half of it. if (IsLeafNodeForTreePosition(node)) return true; if (node.GetRole() == ax::mojom::Role::kSpinButton) { // TODO(benjamin.beaudry) Please look into whether this code needs to // remain, or can be simplified. return true; } // Ignored atomic text fields and spin buttons are not considered leaves by // AXNode::IsLeaf(), but should always use a text position. if (node.data().IsAtomicTextField()) { // Ignored atomic text fields and spin buttons are not considered leaves by // AXNode::IsLeaf(), but should always use a text position. // TODO(accessibility) Nobody should be creating a text position on an // ignored text field. DCHECK(node.IsIgnored()) << "Returned false from IsLeaf(): " << node; return true; } return false; } AXNodePosition::AXNodePosition() = default; AXNodePosition::~AXNodePosition() = default; AXNodePosition::AXNodePosition(const AXNodePosition& other) : AXPosition(other) {} AXNodePosition::AXPositionInstance AXNodePosition::Clone() const { return AXPositionInstance(new AXNodePosition(*this)); } } // namespace ui