// Copyright 2022 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 "ui/accessibility/ax_selection.h" #include "ui/accessibility/ax_node_position.h" namespace ui { namespace { // Helper for GetUnignoredSelection. Creates a position using |node_id|, // |offset| and |affinity|, and if it's ignored, updates these arguments so // that they represent a non-null non-ignored position, according to // |adjustment_behavior|. Returns true on success, false on failure. Note that // if the position is initially null, it's not ignored and it's a success. bool ComputeUnignoredSelectionEndpoint( const AXTree* tree, AXPositionAdjustmentBehavior adjustment_behavior, AXNodeID& node_id, int32_t& offset, ax::mojom::TextAffinity& affinity) { AXNode* node = tree ? tree->GetFromId(node_id) : nullptr; if (!node) { node_id = kInvalidAXNodeID; offset = AXNodePosition::INVALID_OFFSET; affinity = ax::mojom::TextAffinity::kDownstream; return false; } AXNodePosition::AXPositionInstance position = AXNodePosition::CreatePosition(*node, offset, affinity); // Null positions are never ignored, but must be considered successful, or // these Android tests would fail: // org.chromium.content.browser.accessibility.AssistViewStructureTest#* // The reason is that |position| becomes null because no AXTreeManager is // registered for that |tree|'s AXTreeID. // TODO(accessibility): investigate and fix this if needed. if (!position->IsIgnored()) return true; // We assume that unignored positions are already valid. position = position->AsValidPosition()->AsUnignoredPosition(adjustment_behavior); // Moving to an unignored position might have placed the position on a leaf // node. Any selection endpoint that is inside a leaf node is expressed as a // text position in AXTreeData. (Note that in this context "leaf node" means // a node with no children or with only ignored children. This does not // refer to a platform leaf.) if (position->IsLeafTreePosition()) position = position->AsTextPosition(); // We do not expect the selection to have an endpoint on an inline text // box as this will create issues with parts of the code that don't use // inline text boxes. if (position->IsTextPosition() && position->GetRole() == ax::mojom::Role::kInlineTextBox) { position = position->CreateParentPosition(); } switch (position->kind()) { case AXPositionKind::NULL_POSITION: node_id = kInvalidAXNodeID; offset = AXNodePosition::INVALID_OFFSET; affinity = ax::mojom::TextAffinity::kDownstream; return false; case AXPositionKind::TREE_POSITION: node_id = position->anchor_id(); offset = position->child_index(); affinity = ax::mojom::TextAffinity::kDownstream; return true; case AXPositionKind::TEXT_POSITION: node_id = position->anchor_id(); offset = position->text_offset(); affinity = position->affinity(); return true; } } } // namespace AXSelection::AXSelection() = default; AXSelection::AXSelection(const AXSelection&) = default; AXSelection::~AXSelection() = default; AXSelection::AXSelection(const AXTree& tree) : is_backward(tree.data().sel_is_backward), anchor_object_id(tree.data().sel_anchor_object_id), anchor_offset(tree.data().sel_anchor_offset), anchor_affinity(tree.data().sel_anchor_affinity), focus_object_id(tree.data().sel_focus_object_id), focus_offset(tree.data().sel_focus_offset), focus_affinity(tree.data().sel_focus_affinity), tree_id_(tree.GetAXTreeID()) {} AXSelection& AXSelection::ToUnignoredSelection() { DCHECK_NE(tree_id_, AXTreeIDUnknown()) << "Tree is not registered with a tree manager"; const AXTreeManager* manager = AXTreeManager::FromID(tree_id_); if (!manager) return *this; // If one of the selection endpoints is invalid, then the other endpoint // should also be unset. if (!ComputeUnignoredSelectionEndpoint( manager->ax_tree(), is_backward ? AXPositionAdjustmentBehavior::kMoveForward : AXPositionAdjustmentBehavior::kMoveBackward, anchor_object_id, anchor_offset, anchor_affinity)) { focus_object_id = kInvalidAXNodeID; focus_offset = AXNodePosition::INVALID_OFFSET; focus_affinity = ax::mojom::TextAffinity::kDownstream; } else if (!ComputeUnignoredSelectionEndpoint( manager->ax_tree(), is_backward ? AXPositionAdjustmentBehavior::kMoveBackward : AXPositionAdjustmentBehavior::kMoveForward, focus_object_id, focus_offset, focus_affinity)) { anchor_object_id = kInvalidAXNodeID; anchor_offset = AXNodePosition::INVALID_OFFSET; anchor_affinity = ax::mojom::TextAffinity::kDownstream; } return *this; } } // namespace ui